Skip to content
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
80 changes: 80 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Copy link
Member

Choose a reason for hiding this comment

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

We need to mark this operation with some type of side-effect otherwise we'll make the situation with canonicalization worse (it will silently strip those away). We should probably have a simple CIR to CIR test using the canonicalizer and checking they don't get stripped away.

Looks like both AssumeOp and AssumeSepStorageOp need that (but perhaps not AssumeAlignedOp given the result is usually used?)

Copy link
Member Author

Choose a reason for hiding this comment

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

Added a test in clang/test/CIR/Transforms/builtin-assume.cir but it seems that neither -cir-simplify nor --canonicalize erases these operations. Are there any other canonicalization passes that could do such optimization?

Copy link
Member

Choose a reason for hiding this comment

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

That's interesting, I'd have expected these to be doing it (as they usually do for other operations), well at least there's a test to prevent that in case something off is going on in the meantime. Should be good for now, thanks for adding these.

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<CIR_IntType>:$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
//===----------------------------------------------------------------------===//
Expand Down
39 changes: 39 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include "CIRGenCXXABI.h"
#include "CIRGenCall.h"
#include "CIRGenCstEmitter.h"
#include "CIRGenFunction.h"
#include "CIRGenModule.h"
#include "TargetInfo.h"
Expand Down Expand Up @@ -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<mlir::cir::IntAttr>(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<mlir::cir::AssumeOp>(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<mlir::cir::AssumeSepStorageOp>(getLoc(E->getExprLoc()),
value0, value1);
return RValue::get(nullptr);
}

case Builtin::BI__builtin_prefetch: {
auto evaluateOperandAsInt = [&](const Expr *Arg) {
Expr::EvalResult Res;
Expand Down
19 changes: 19 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<mlir::cir::AssumeAlignedOp>(
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);
}
11 changes: 11 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
55 changes: 55 additions & 0 deletions clang/test/CIR/CodeGen/builtin-assume.cpp
Original file line number Diff line number Diff line change
@@ -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>, !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<!s32i>>, !cir.ptr<!s32i>
// CIR-NEXT: %[[#aligned:]] = cir.assume.aligned %[[#ptr]] : !cir.ptr<!s32i>[alignment 8]
// CIR-NEXT: cir.store %[[#aligned]], %[[#aligned_slot:]] : !cir.ptr<!s32i>, !cir.ptr<!cir.ptr<!s32i>>
// CIR-NEXT: %[[#aligned2:]] = cir.load deref %[[#aligned_slot]] : !cir.ptr<!cir.ptr<!s32i>>, !cir.ptr<!s32i>
// CIR-NEXT: %{{.+}} = cir.load %[[#aligned2]] : !cir.ptr<!s32i>, !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<!s32i>>, !cir.ptr<!s32i>
// 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<!s32i>[alignment 8, offset %[[#offset2]] : !u64i]
// CIR-NEXT: cir.store %[[#aligned]], %[[#aligned_slot:]] : !cir.ptr<!s32i>, !cir.ptr<!cir.ptr<!s32i>>
// CIR-NEXT: %[[#aligned2:]] = cir.load deref %[[#aligned_slot]] : !cir.ptr<!cir.ptr<!s32i>>, !cir.ptr<!s32i>
// CIR-NEXT: %{{.+}} = cir.load %[[#aligned2]] : !cir.ptr<!s32i>, !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<!s32i>>, !cir.ptr<!s32i>
// CIR-NEXT: %[[#p1_voidptr:]] = cir.cast(bitcast, %[[#p1]] : !cir.ptr<!s32i>), !cir.ptr<!void>
// CIR-NEXT: %[[#p2:]] = cir.load %{{.+}} : !cir.ptr<!cir.ptr<!s32i>>, !cir.ptr<!s32i>
// CIR-NEXT: %[[#p2_voidptr:]] = cir.cast(bitcast, %[[#p2]] : !cir.ptr<!s32i>), !cir.ptr<!void>
// CIR-NEXT: cir.assume.separate_storage %[[#p1_voidptr]], %[[#p2_voidptr]] : !cir.ptr<!void>
// CIR: }
40 changes: 40 additions & 0 deletions clang/test/CIR/Transforms/builtin-assume.cir
Original file line number Diff line number Diff line change
@@ -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<s, 32>
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<!s32i>) -> !cir.ptr<!s32i> {
%0 = cir.assume.aligned %arg0 : !cir.ptr<!s32i>[alignment 8]
cir.return %0 : !cir.ptr<!s32i>
}
// CHECK: cir.func @assume_aligned(%arg0: !cir.ptr<!s32i>) -> !cir.ptr<!s32i> {
// CHECK-NEXT: %0 = cir.assume.aligned %arg0 : !cir.ptr<!s32i>[alignment 8]
// CHECK-NEXT: cir.return %0 : !cir.ptr<!s32i>
// CHECK-NEXT: }

cir.func @assume_separate_storage(%arg0: !cir.ptr<!cir.void>, %arg1: !cir.ptr<!cir.void>) {
cir.assume.separate_storage %arg0, %arg1 : !cir.ptr<!cir.void>
cir.return
}
// CHECK: cir.func @assume_separate_storage(%arg0: !cir.ptr<!void>, %arg1: !cir.ptr<!void>) {
// CHECK-NEXT: cir.assume.separate_storage %arg0, %arg1 : !cir.ptr<!void>
// CHECK-NEXT: cir.return
// CHECK-NEXT: }
}