From ea067a1ffd2c1e99b3df6ad9a3cc0064e25d73f4 Mon Sep 17 00:00:00 2001 From: Sirui Mu Date: Sat, 14 Sep 2024 10:20:34 +0800 Subject: [PATCH] [CIR][CIRGen] add CIRGen support for assume builtins This patch adds CIRGen support for the following 3 builtins related to compile- time assumptions: - `__builtin_assume` - `__builtin_assume_aligned` - `__builtin_assume_separate_storage` 3 new operations are invented to represent the three builtins. LLVMIR lowering for these builtins cannot be implemented at this moment due to the lack of operand bundle support in LLVMIR dialect. --- clang/include/clang/CIR/Dialect/IR/CIROps.td | 80 ++++++++++++++++++++ clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp | 39 ++++++++++ clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 19 +++++ clang/lib/CIR/CodeGen/CIRGenFunction.h | 11 +++ clang/test/CIR/CodeGen/builtin-assume.cpp | 55 ++++++++++++++ clang/test/CIR/Transforms/builtin-assume.cir | 40 ++++++++++ 6 files changed, 244 insertions(+) create mode 100644 clang/test/CIR/CodeGen/builtin-assume.cpp create mode 100644 clang/test/CIR/Transforms/builtin-assume.cir diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 4eb86f8a1b82..fda44edf4f20 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -3882,6 +3882,86 @@ def FMinOp : BinaryFPToFPBuiltinOp<"fmin", "MinNumOp">; def FModOp : BinaryFPToFPBuiltinOp<"fmod", "FRemOp">; def PowOp : BinaryFPToFPBuiltinOp<"pow", "PowOp">; +//===----------------------------------------------------------------------===// +// Assume Operations +//===----------------------------------------------------------------------===// + +def AssumeOp : CIR_Op<"assume"> { + let summary = "Tell the optimizer that a boolean value is true"; + let description = [{ + The `cir.assume` operation takes a single boolean prediate as its only + argument and does not have any results. The operation tells the optimizer + that the predicate's value is true. + + This operation corresponds to the `__assume` and the `__builtin_assume` + builtin function. + }]; + + let arguments = (ins CIR_BoolType:$predicate); + let results = (outs); + + let assemblyFormat = [{ + $predicate `:` type($predicate) attr-dict + }]; +} + +def AssumeAlignedOp + : CIR_Op<"assume.aligned", [Pure, AllTypesMatch<["pointer", "result"]>]> { + let summary = "Tell the optimizer that a pointer is aligned"; + let description = [{ + The `cir.assume.aligned` operation takes two or three arguments. + + When the 3rd argument `offset` is absent, this operation tells the optimizer + that the pointer given by the `pointer` argument is aligned to the alignment + given by the `align` argument. + + When the `offset` argument is given, it represents an offset from the + alignment. This operation then tells the optimizer that the pointer given by + the `pointer` argument is always misaligned by the alignment given by the + `align` argument by `offset` bytes, a.k.a. the pointer yielded by + `(char *)pointer - offset` is aligned to the specified alignment. + + The `align` argument is a constant integer represented as an integer + attribute instead of an SSA value. It must be a positive integer. + + The result of this operation has the same value as the `pointer` argument, + but the optimizer has additional knowledge about its alignment. + + This operation corresponds to the `__builtin_assume_aligned` builtin + function. + }]; + + let arguments = (ins CIR_PointerType:$pointer, + I64Attr:$alignment, + Optional:$offset); + let results = (outs CIR_PointerType:$result); + + let assemblyFormat = [{ + $pointer `:` qualified(type($pointer)) + `[` `alignment` $alignment (`,` `offset` $offset^ `:` type($offset))? `]` + attr-dict + }]; +} + +def AssumeSepStorageOp : CIR_Op<"assume.separate_storage", [SameTypeOperands]> { + let summary = + "Tell the optimizer that two pointers point to different allocations"; + let description = [{ + The `cir.assume.separate_storage` operation takes two pointers as arguments, + and the operation tells the optimizer that these two pointers point to + different allocations. + + This operation corresponds to the `__builtin_assume_separate_storage` + builtin function. + }]; + + let arguments = (ins VoidPtr:$ptr1, VoidPtr:$ptr2); + + let assemblyFormat = [{ + $ptr1 `,` $ptr2 `:` qualified(type($ptr1)) attr-dict + }]; +} + //===----------------------------------------------------------------------===// // Branch Probability Operations //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp index c24888362865..ab4525d38b55 100644 --- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp @@ -13,6 +13,7 @@ #include "CIRGenCXXABI.h" #include "CIRGenCall.h" +#include "CIRGenCstEmitter.h" #include "CIRGenFunction.h" #include "CIRGenModule.h" #include "TargetInfo.h" @@ -823,6 +824,44 @@ RValue CIRGenFunction::buildBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, return RValue::get(buildScalarExpr(E->getArg(0))); } + case Builtin::BI__builtin_assume_aligned: { + const Expr *ptr = E->getArg(0); + mlir::Value ptrValue = buildScalarExpr(ptr); + mlir::Value offsetValue = + (E->getNumArgs() > 2) ? buildScalarExpr(E->getArg(2)) : nullptr; + + mlir::Attribute alignmentAttr = ConstantEmitter(*this).emitAbstract( + E->getArg(1), E->getArg(1)->getType()); + std::int64_t alignment = cast(alignmentAttr).getSInt(); + + ptrValue = buildAlignmentAssumption(ptrValue, ptr, ptr->getExprLoc(), + builder.getI64IntegerAttr(alignment), + offsetValue); + return RValue::get(ptrValue); + } + + case Builtin::BI__assume: + case Builtin::BI__builtin_assume: { + if (E->getArg(0)->HasSideEffects(getContext())) + return RValue::get(nullptr); + + mlir::Value argValue = buildScalarExpr(E->getArg(0)); + builder.create(getLoc(E->getExprLoc()), argValue); + return RValue::get(nullptr); + } + + case Builtin::BI__builtin_assume_separate_storage: { + const Expr *arg0 = E->getArg(0); + const Expr *arg1 = E->getArg(1); + + mlir::Value value0 = buildScalarExpr(arg0); + mlir::Value value1 = buildScalarExpr(arg1); + + builder.create(getLoc(E->getExprLoc()), + value0, value1); + return RValue::get(nullptr); + } + case Builtin::BI__builtin_prefetch: { auto evaluateOperandAsInt = [&](const Expr *Arg) { Expr::EvalResult Res; diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 0c7ac712284d..410fcc2a316d 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -1854,3 +1854,22 @@ CIRGenFunction::buildArrayLength(const clang::ArrayType *origArrayType, return numElements; } + +mlir::Value CIRGenFunction::buildAlignmentAssumption( + mlir::Value ptrValue, QualType ty, SourceLocation loc, + SourceLocation assumptionLoc, mlir::IntegerAttr alignment, + mlir::Value offsetValue) { + if (SanOpts.has(SanitizerKind::Alignment)) + llvm_unreachable("NYI"); + return builder.create( + getLoc(assumptionLoc), ptrValue, alignment, offsetValue); +} + +mlir::Value CIRGenFunction::buildAlignmentAssumption( + mlir::Value ptrValue, const Expr *expr, SourceLocation assumptionLoc, + mlir::IntegerAttr alignment, mlir::Value offsetValue) { + QualType ty = expr->getType(); + SourceLocation loc = expr->getExprLoc(); + return buildAlignmentAssumption(ptrValue, ty, loc, assumptionLoc, alignment, + offsetValue); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 7703b6cd6ee7..c09860cb85fc 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -969,6 +969,17 @@ class CIRGenFunction : public CIRGenTypeCache { ReturnValueSlot ReturnValue, llvm::Triple::ArchType Arch); + mlir::Value buildAlignmentAssumption(mlir::Value ptrValue, QualType ty, + SourceLocation loc, + SourceLocation assumptionLoc, + mlir::IntegerAttr alignment, + mlir::Value offsetValue = nullptr); + + mlir::Value buildAlignmentAssumption(mlir::Value ptrValue, const Expr *expr, + SourceLocation assumptionLoc, + mlir::IntegerAttr alignment, + mlir::Value offsetValue = nullptr); + /// Build a debug stoppoint if we are emitting debug info. void buildStopPoint(const Stmt *S); diff --git a/clang/test/CIR/CodeGen/builtin-assume.cpp b/clang/test/CIR/CodeGen/builtin-assume.cpp new file mode 100644 index 000000000000..da807994f4b1 --- /dev/null +++ b/clang/test/CIR/CodeGen/builtin-assume.cpp @@ -0,0 +1,55 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-cir %s -o %t.cir +// RUN: FileCheck %s --check-prefix=CIR --input-file=%t.cir + +int test_assume(int x) { + __builtin_assume(x > 0); + return x; +} + +// CIR: cir.func @_Z11test_assumei +// CIR: %[[#x:]] = cir.load %{{.+}} : !cir.ptr, !s32i +// CIR-NEXT: %[[#zero:]] = cir.const #cir.int<0> : !s32i +// CIR-NEXT: %[[#cond:]] = cir.cmp(gt, %[[#x]], %[[#zero]]) : !s32i, !cir.bool +// CIR-NEXT: cir.assume %[[#cond]] : !cir.bool +// CIR: } + +int test_assume_aligned(int *ptr) { + int *aligned = (int *)__builtin_assume_aligned(ptr, 8); + return *aligned; +} + +// CIR: cir.func @_Z19test_assume_alignedPi +// CIR: %[[#ptr:]] = cir.load %{{.+}} : !cir.ptr>, !cir.ptr +// CIR-NEXT: %[[#aligned:]] = cir.assume.aligned %[[#ptr]] : !cir.ptr[alignment 8] +// CIR-NEXT: cir.store %[[#aligned]], %[[#aligned_slot:]] : !cir.ptr, !cir.ptr> +// CIR-NEXT: %[[#aligned2:]] = cir.load deref %[[#aligned_slot]] : !cir.ptr>, !cir.ptr +// CIR-NEXT: %{{.+}} = cir.load %[[#aligned2]] : !cir.ptr, !s32i +// CIR: } + +int test_assume_aligned_offset(int *ptr) { + int *aligned = (int *)__builtin_assume_aligned(ptr, 8, 4); + return *aligned; +} + +// CIR: cir.func @_Z26test_assume_aligned_offsetPi +// CIR: %[[#ptr:]] = cir.load %{{.+}} : !cir.ptr>, !cir.ptr +// CIR-NEXT: %[[#offset:]] = cir.const #cir.int<4> : !s32i +// CIR-NEXT: %[[#offset2:]] = cir.cast(integral, %[[#offset]] : !s32i), !u64i +// CIR-NEXT: %[[#aligned:]] = cir.assume.aligned %[[#ptr]] : !cir.ptr[alignment 8, offset %[[#offset2]] : !u64i] +// CIR-NEXT: cir.store %[[#aligned]], %[[#aligned_slot:]] : !cir.ptr, !cir.ptr> +// CIR-NEXT: %[[#aligned2:]] = cir.load deref %[[#aligned_slot]] : !cir.ptr>, !cir.ptr +// CIR-NEXT: %{{.+}} = cir.load %[[#aligned2]] : !cir.ptr, !s32i +// CIR: } + +int test_separate_storage(int *p1, int *p2) { + __builtin_assume_separate_storage(p1, p2); + return *p1 + *p2; +} + +// CIR: cir.func @_Z21test_separate_storagePiS_ +// CIR: %[[#p1:]] = cir.load %{{.+}} : !cir.ptr>, !cir.ptr +// CIR-NEXT: %[[#p1_voidptr:]] = cir.cast(bitcast, %[[#p1]] : !cir.ptr), !cir.ptr +// CIR-NEXT: %[[#p2:]] = cir.load %{{.+}} : !cir.ptr>, !cir.ptr +// CIR-NEXT: %[[#p2_voidptr:]] = cir.cast(bitcast, %[[#p2]] : !cir.ptr), !cir.ptr +// CIR-NEXT: cir.assume.separate_storage %[[#p1_voidptr]], %[[#p2_voidptr]] : !cir.ptr +// CIR: } diff --git a/clang/test/CIR/Transforms/builtin-assume.cir b/clang/test/CIR/Transforms/builtin-assume.cir new file mode 100644 index 000000000000..72afb3812e53 --- /dev/null +++ b/clang/test/CIR/Transforms/builtin-assume.cir @@ -0,0 +1,40 @@ +// RUN: cir-opt --canonicalize -o %t.cir %s +// RUN: FileCheck --input-file %t.cir %s +// RUN: cir-opt -cir-simplify -o %t.cir %s +// RUN: FileCheck --input-file %t.cir %s + +!s32i = !cir.int +module { + // Make sure canonicalizers don't erase assume builtins. + + cir.func @assume(%arg0: !s32i) { + %0 = cir.const #cir.int<0> : !s32i + %1 = cir.cmp(gt, %arg0, %0) : !s32i, !cir.bool + cir.assume %1 : !cir.bool + cir.return + } + // CHECK: cir.func @assume(%arg0: !s32i) { + // CHECK-NEXT: %0 = cir.const #cir.int<0> : !s32i + // CHECK-NEXT: %1 = cir.cmp(gt, %arg0, %0) : !s32i, !cir.bool + // CHECK-NEXT: cir.assume %1 : !cir.bool + // CHECK-NEXT: cir.return + // CHECK-NEXT: } + + cir.func @assume_aligned(%arg0: !cir.ptr) -> !cir.ptr { + %0 = cir.assume.aligned %arg0 : !cir.ptr[alignment 8] + cir.return %0 : !cir.ptr + } + // CHECK: cir.func @assume_aligned(%arg0: !cir.ptr) -> !cir.ptr { + // CHECK-NEXT: %0 = cir.assume.aligned %arg0 : !cir.ptr[alignment 8] + // CHECK-NEXT: cir.return %0 : !cir.ptr + // CHECK-NEXT: } + + cir.func @assume_separate_storage(%arg0: !cir.ptr, %arg1: !cir.ptr) { + cir.assume.separate_storage %arg0, %arg1 : !cir.ptr + cir.return + } + // CHECK: cir.func @assume_separate_storage(%arg0: !cir.ptr, %arg1: !cir.ptr) { + // CHECK-NEXT: cir.assume.separate_storage %arg0, %arg1 : !cir.ptr + // CHECK-NEXT: cir.return + // CHECK-NEXT: } +} \ No newline at end of file