Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CIR][CIRGen][builtin] handle __lzcnt #1382

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

FantasqueX
Copy link
Collaborator

@FantasqueX FantasqueX commented Feb 21, 2025

Traditional clang implementation:

case Builtin::BI__lzcnt16:
case Builtin::BI__lzcnt:
case Builtin::BI__lzcnt64: {
Value *ArgValue = EmitScalarExpr(E->getArg(0));
llvm::Type *ArgType = ArgValue->getType();
Function *F = CGM.getIntrinsic(Intrinsic::ctlz, ArgType);
llvm::Type *ResultType = ConvertType(E->getType());
Value *Result = Builder.CreateCall(F, {ArgValue, Builder.getFalse()});
if (Result->getType() != ResultType)
Result = Builder.CreateIntCast(Result, ResultType, /*isSigned*/true,
"cast");
return RValue::get(Result);
}

The problem here is that __builtin_clz allows undefined result, while __lzcnt doesn't. As a result, I have to create a new CIR for __lzcnt. Since the return type of those two builtin differs, I decided to change return type of current CIR_BitOp to allow new CIR_LzcntOp to inherit from it.

I would like to hear your suggestions. C.c. @Lancern

@Lancern
Copy link
Member

Lancern commented Feb 22, 2025

The problem here is that __builtin_clz allows undefined result, while __lzcnt doesn't. As a result, I have to create a new CIR for __lzcnt.

Have you considered updating the existing cir.bit.clz operation and add a unit attribute that specifies whether an input value of 0 is poisoned?

@FantasqueX FantasqueX marked this pull request as draft February 23, 2025 09:58
@@ -1714,6 +1714,30 @@ def BitPopcountOp
}];
}

def BitLzcntOp : CIR_BitOp<"bit.lzcnt", AnyTypeOf<[UInt16, UInt32, UInt64]>> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can share a tablegen class with BitPopcountOp.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why BitPopcount? Do you mean BitClzOp? The reason is their different poison bit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was actually curious if you could reuse the CIR_BitOp if you make it a bit more generic.

@FantasqueX FantasqueX marked this pull request as ready for review March 1, 2025 14:17
@FantasqueX FantasqueX requested a review from Lancern March 1, 2025 14:18
Copy link

github-actions bot commented Mar 1, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@FantasqueX
Copy link
Collaborator Author

The new LzcntOp doesn't inherent BitOp. So, I can avoid touching original code.

Copy link
Member

@Lancern Lancern left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still unclear about why we don't just update cir.bit.clz for __lzcnt. AFAIK, we may have two problems in doing so:

  • The result type of cir.bit.clz is fixed to be !s32i. Have you tried update the TableGen definition of cir.bit.clz so you could relax this constraint?
  • cir.bit.clz would invoke undefined behaviors if the operand is 0. To overcome this, we could add a UnitAttr to the parameters of cir.bit.clz that indicates whether the operation should poison an input value of 0. Have you tried this?

Are there any other concerns I'm missing here?

Comment on lines 1721 to 1727
Compute the number of leading 0-bits in the input.

The input integer must be an unsigned integer. The `cir.bit.lzcnt` operation
returns the number of consecutive 0-bits at the most significant bit
position in the input.

If the input value is 0, the return value is the size of the input value.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add some more description about the differences between the cir.bit.clz operation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that'd be great

@bcardosolopes
Copy link
Member

+1 on all comments from @Lancern

@FantasqueX
Copy link
Collaborator Author

@Lancern @bcardosolopes Thanks for your review. I'm going to make some changes. First, changing the return type of CIR_BitOp to match input. Second, adding a new base class for CIR_ClzOp and CIR_CtzOp which contains a unit attribute to indicate poison. For future work, specifically handling BI__builtin_clzg, we can add more logic in CodeGen stage and reuse CIR_ClzOp. I would like to hear your suggestions❤️

@FantasqueX
Copy link
Collaborator Author

FantasqueX commented Mar 6, 2025

I have reworked the patch. Creating a base class for ClzOp and CtzOp which contains an I1Attr just like MLIR LLVMIR. And change BitOp to require same operand and result type. So, at CodeGen stage, clz will emit a ClzOp and a CastOp. Also changing lowering stage to emit llvm.intr.ctlz directly install of calling intrinsics. This patch also fixes an IR difference for ffs like op. Before, CIR casts after ctlz and uses sint32 to calculate. However, OG calculates first and then casts. This patch removes ThroughMLIR lowering for ClzOp and CtzOp if is_zero_poison is true.

@FantasqueX
Copy link
Collaborator Author

I also noticed that MLIR LLVMIR doesn't require SameOperandsAndResultType for llvm.intr.ctlz, llvm.intr.cttz and llvm.intr.ctpop.

Copy link
Member

@Lancern Lancern left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another round of review! Some comments inline.


let assemblyFormat = [{
`(` $input `:` type($input) `)` `:` type($result) attr-dict
}];
}

class CIR_CountZerosBitOp<string mnemonic, TypeConstraint inputTy>
: CIR_BitOp<mnemonic, inputTy> {
let arguments = (ins inputTy:$input, I1Attr:$is_zero_poison);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not use I1Attr; use UnitAttr instead. Then use optional groups in the assembly format for is_zero_poison.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use I1Attr here to be consist with MLIR

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe CIR always uses UnitAttr for representing boolean flags.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants