Skip to content

[CIR] Add support for __builtin_expect #144726

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

Merged
merged 1 commit into from
Jun 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -2446,4 +2446,41 @@ def AssumeOp : CIR_Op<"assume"> {
}];
}

//===----------------------------------------------------------------------===//
// Branch Probability Operations
//===----------------------------------------------------------------------===//

def ExpectOp : CIR_Op<"expect",
[Pure, AllTypesMatch<["result", "val", "expected"]>]> {
let summary = "Tell the optimizer that two values are likely to be equal.";
let description = [{
The `cir.expect` operation may take 2 or 3 arguments.

When the argument `prob` is missing, this operation effectively models the
`__builtin_expect` builtin function. It tells the optimizer that `val` and
`expected` are likely to be equal.

When the argument `prob` is present, this operation effectively models the
`__builtin_expect_with_probability` builtin function. It tells the
optimizer that `val` and `expected` are equal to each other with a certain
probability.

`val` and `expected` must be integers and their types must match.
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like in practice these will only be i64, since that's what the __builtin_expect functions take as arguments, but I like the idea of being able to reduce the size to eliminate casts during optimization, and it seems like it would be useful to be able to have bool arguments as well. I'm not saying this needs to change now. I'm just commenting for possible future reference.

Copy link
Member Author

Choose a reason for hiding this comment

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

The @llvm.expect intrinsic function accepts i1, i32, and i64 as arguments. We may take it as a reference.


The result of this operation is always equal to `val`.
}];

let arguments = (ins
CIR_AnyFundamentalIntType:$val,
CIR_AnyFundamentalIntType:$expected,
OptionalAttr<F64Attr>:$prob
);

let results = (outs CIR_AnyFundamentalIntType:$result);

let assemblyFormat = [{
`(` $val`,` $expected (`,` $prob^)? `)` `:` type($val) attr-dict
}];
}

#endif // CLANG_CIR_DIALECT_IR_CIROPS_TD
26 changes: 26 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,32 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
mlir::Value complex = builder.createComplexCreate(loc, real, imag);
return RValue::get(complex);
}

case Builtin::BI__builtin_expect:
case Builtin::BI__builtin_expect_with_probability: {
mlir::Value argValue = emitScalarExpr(e->getArg(0));
mlir::Value expectedValue = emitScalarExpr(e->getArg(1));

mlir::FloatAttr probAttr;
if (builtinIDIfNoAsmLabel == Builtin::BI__builtin_expect_with_probability) {
llvm::APFloat probability(0.0);
const Expr *probArg = e->getArg(2);
[[maybe_unused]] bool evalSucceeded =
probArg->EvaluateAsFloat(probability, cgm.getASTContext());
assert(evalSucceeded &&
"probability should be able to evaluate as float");
bool loseInfo = false; // ignored
probability.convert(llvm::APFloat::IEEEdouble(),
llvm::RoundingMode::Dynamic, &loseInfo);
probAttr = mlir::FloatAttr::get(mlir::Float64Type::get(&getMLIRContext()),
probability);
}

auto result = builder.create<cir::ExpectOp>(getLoc(e->getSourceRange()),
argValue.getType(), argValue,
expectedValue, probAttr);
return RValue::get(result);
}
}

cgm.errorNYI(e->getSourceRange(), "unimplemented builtin call");
Expand Down
17 changes: 17 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -989,6 +989,22 @@ mlir::LogicalResult CIRToLLVMConstantOpLowering::matchAndRewrite(
return mlir::success();
}

mlir::LogicalResult CIRToLLVMExpectOpLowering::matchAndRewrite(
cir::ExpectOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
// TODO(cir): do not generate LLVM intrinsics under -O0
assert(!cir::MissingFeatures::optInfoAttr());

std::optional<llvm::APFloat> prob = op.getProb();
if (prob)
rewriter.replaceOpWithNewOp<mlir::LLVM::ExpectWithProbabilityOp>(
op, adaptor.getVal(), adaptor.getExpected(), prob.value());
else
rewriter.replaceOpWithNewOp<mlir::LLVM::ExpectOp>(op, adaptor.getVal(),
adaptor.getExpected());
return mlir::success();
}

/// Convert the `cir.func` attributes to `llvm.func` attributes.
/// Only retain those attributes that are not constructed by
/// `LLVMFuncOp::build`. If `filterArgAttrs` is set, also filter out
Expand Down Expand Up @@ -1868,6 +1884,7 @@ void ConvertCIRToLLVMPass::runOnOperation() {
CIRToLLVMCallOpLowering,
CIRToLLVMCmpOpLowering,
CIRToLLVMConstantOpLowering,
CIRToLLVMExpectOpLowering,
CIRToLLVMFuncOpLowering,
CIRToLLVMGetGlobalOpLowering,
CIRToLLVMGetMemberOpLowering,
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ class CIRToLLVMCastOpLowering : public mlir::OpConversionPattern<cir::CastOp> {
mlir::ConversionPatternRewriter &) const override;
};

class CIRToLLVMExpectOpLowering
: public mlir::OpConversionPattern<cir::ExpectOp> {
public:
using mlir::OpConversionPattern<cir::ExpectOp>::OpConversionPattern;

mlir::LogicalResult
matchAndRewrite(cir::ExpectOp op, OpAdaptor,
mlir::ConversionPatternRewriter &) const override;
};

class CIRToLLVMReturnOpLowering
: public mlir::OpConversionPattern<cir::ReturnOp> {
public:
Expand Down
40 changes: 40 additions & 0 deletions clang/test/CIR/CodeGen/builtin_call.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,43 @@ void assume(bool arg) {
// OGCG: define {{.*}}void @_Z6assumeb
// OGCG: call void @llvm.assume(i1 %{{.+}})
// OGCG: }

void expect(int x, int y) {
__builtin_expect(x, y);
}

// CIR-LABEL: cir.func @_Z6expectii
// CIR: %[[X:.+]] = cir.load align(4) %{{.+}} : !cir.ptr<!s32i>, !s32i
// CIR-NEXT: %[[X_LONG:.+]] = cir.cast(integral, %[[X]] : !s32i), !s64i
// CIR-NEXT: %[[Y:.+]] = cir.load align(4) %{{.+}} : !cir.ptr<!s32i>, !s32i
// CIR-NEXT: %[[Y_LONG:.+]] = cir.cast(integral, %[[Y]] : !s32i), !s64i
// CIR-NEXT: %{{.+}} = cir.expect(%[[X_LONG]], %[[Y_LONG]]) : !s64i
// CIR: }

// LLVM-LABEL: define void @_Z6expectii
// LLVM: %[[X:.+]] = load i32, ptr %{{.+}}, align 4
// LLVM-NEXT: %[[X_LONG:.+]] = sext i32 %[[X]] to i64
// LLVM-NEXT: %[[Y:.+]] = load i32, ptr %{{.+}}, align 4
// LLVM-NEXT: %[[Y_LONG:.+]] = sext i32 %[[Y]] to i64
// LLVM-NEXT: %{{.+}} = call i64 @llvm.expect.i64(i64 %[[X_LONG]], i64 %[[Y_LONG]])
// LLVM: }

void expect_prob(int x, int y) {
__builtin_expect_with_probability(x, y, 0.25);
}

// CIR-LABEL: cir.func @_Z11expect_probii
// CIR: %[[X:.+]] = cir.load align(4) %{{.+}} : !cir.ptr<!s32i>, !s32i
// CIR-NEXT: %[[X_LONG:.+]] = cir.cast(integral, %[[X]] : !s32i), !s64i
// CIR-NEXT: %[[Y:.+]] = cir.load align(4) %{{.+}} : !cir.ptr<!s32i>, !s32i
// CIR-NEXT: %[[Y_LONG:.+]] = cir.cast(integral, %[[Y]] : !s32i), !s64i
// CIR-NEXT: %{{.+}} = cir.expect(%[[X_LONG]], %[[Y_LONG]], 2.500000e-01) : !s64i
// CIR: }

// LLVM: define void @_Z11expect_probii
// LLVM: %[[X:.+]] = load i32, ptr %{{.+}}, align 4
// LLVM-NEXT: %[[X_LONG:.+]] = sext i32 %[[X]] to i64
// LLVM-NEXT: %[[Y:.+]] = load i32, ptr %{{.+}}, align 4
// LLVM-NEXT: %[[Y_LONG:.+]] = sext i32 %[[Y]] to i64
// LLVM-NEXT: %{{.+}} = call i64 @llvm.expect.with.probability.i64(i64 %[[X_LONG]], i64 %[[Y_LONG]], double 2.500000e-01)
// LLVM: }
Loading