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
62 changes: 62 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -2210,6 +2210,68 @@ def CIR_CallOp : CIR_CallOpBase<"call", [NoRegionArguments]> {
];
}

//===----------------------------------------------------------------------===//
// ReturnAddrOp and FrameAddrOp
//===----------------------------------------------------------------------===//

class CIR_FuncAddrBuiltinOp<string mnemonic> : CIR_Op<mnemonic, []> {
let arguments = (ins CIR_UInt32:$level);
let results = (outs CIR_VoidPtrType:$result);
let assemblyFormat = [{
`(` $level `)` attr-dict
}];
}

def CIR_ReturnAddrOp : CIR_FuncAddrBuiltinOp<"return_address"> {
let summary =
"The return address of the current function, or of one of its callers";

let description = [{
Represents a call to builtin function ` __builtin_return_address` in CIR.
This builtin function returns the return address of the current function,
or of one of its callers.

The `level` argument is number of frames to scan up the call stack.
For instance, value of 0 yields the return address of the current function,
value of 1 yields the return address of the caller of the current function,
and so forth.

Examples:

```mlir
%p = return_address(%level) -> !cir.ptr<!void>
```
}];
}

def CIR_FrameAddrOp : CIR_FuncAddrBuiltinOp<"frame_address"> {
let summary =
"The frame address of the current function, or of one of its callers";

let description = [{
Represents a call to builtin function ` __builtin_frame_address` in CIR.
This builtin function returns the frame address of the current function,
or of one of its callers. The frame is the area on the stack that holds
local variables and saved registers. The frame address is normally the
address of the first word pushed on to the stack by the function.
However, the exact definition depends upon the processor and the calling
convention. If the processor has a dedicated frame pointer register, and
the function has a frame, then __builtin_frame_address returns the value of
the frame pointer register.

The `level` argument is number of frames to scan up the call stack.
For instance, value of 0 yields the frame address of the current function,
value of 1 yields the frame address of the caller of the current function,
and so forth.

Examples:

```mlir
%p = frame_address(%level) -> !cir.ptr<!void>
```
}];
}

//===----------------------------------------------------------------------===//
// StackSaveOp & StackRestoreOp
//===----------------------------------------------------------------------===//
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
cir::ConstantOp getSInt32(int32_t c, mlir::Location loc) {
return getConstantInt(loc, getSInt32Ty(), c);
}
cir::ConstantOp getUInt32(uint32_t c, mlir::Location loc) {
return getConstantInt(loc, getUInt32Ty(), c);
}

// Creates constant nullptr for pointer type ty.
cir::ConstantOp getNullPtr(mlir::Type ty, mlir::Location loc) {
Expand Down
14 changes: 14 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,20 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
case Builtin::BI__builtin_rotateright64:
return emitRotate(e, /*isRotateLeft=*/false);

case Builtin::BI__builtin_return_address:
case Builtin::BI__builtin_frame_address: {
mlir::Location loc = getLoc(e->getExprLoc());
llvm::APSInt level = e->getArg(0)->EvaluateKnownConstInt(getContext());
if (builtinID == Builtin::BI__builtin_return_address) {
return RValue::get(cir::ReturnAddrOp::create(
builder, loc,
builder.getConstAPInt(loc, builder.getUInt32Ty(), level)));
}
return RValue::get(cir::FrameAddrOp::create(
builder, loc,
builder.getConstAPInt(loc, builder.getUInt32Ty(), level)));
}

case Builtin::BI__builtin_trap:
emitTrap(loc, /*createNewBlock=*/true);
return RValue::get(nullptr);
Expand Down
6 changes: 4 additions & 2 deletions clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class ConstantEmitter {
// initializer or to propagate to another context; for example,
// side effects, or emitting an initialization that requires a
// reference to its current location.
mlir::Attribute emitForMemory(mlir::Attribute c, QualType t);
mlir::Attribute emitForMemory(mlir::Attribute c, QualType destType);

/// Try to emit the initializer of the given declaration as an abstract
/// constant.
Expand All @@ -90,8 +90,9 @@ class ConstantEmitter {
/// asserting that it succeeded. This is only safe to do when the
/// expression is known to be a constant expression with either a fairly
/// simple type or a known simple form.
mlir::Attribute emitAbstract(const Expr *e, QualType destType);
mlir::Attribute emitAbstract(SourceLocation loc, const APValue &value,
QualType t);
QualType destType);

mlir::Attribute tryEmitConstantExpr(const ConstantExpr *ce);

Expand All @@ -101,6 +102,7 @@ class ConstantEmitter {

mlir::Attribute tryEmitPrivateForVarInit(const VarDecl &d);

mlir::TypedAttr tryEmitPrivate(const Expr *e, QualType destType);
mlir::Attribute tryEmitPrivate(const APValue &value, QualType destType);
mlir::Attribute tryEmitPrivateForMemory(const APValue &value, QualType t);

Expand Down
36 changes: 36 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,16 @@ mlir::Attribute ConstantEmitter::tryEmitPrivateForMemory(const APValue &value,
return (c ? emitForMemory(c, destType) : nullptr);
}

mlir::Attribute ConstantEmitter::emitAbstract(const Expr *e,
QualType destType) {
AbstractStateRAII state{*this, true};
mlir::Attribute c = mlir::cast<mlir::Attribute>(tryEmitPrivate(e, destType));
if (!c)
cgm.errorNYI(e->getSourceRange(),
"emitAbstract failed, emit null constaant");
return c;
}

mlir::Attribute ConstantEmitter::emitAbstract(SourceLocation loc,
const APValue &value,
QualType destType) {
Expand All @@ -721,6 +731,32 @@ mlir::Attribute ConstantEmitter::emitForMemory(mlir::Attribute c,
return c;
}

mlir::TypedAttr ConstantEmitter::tryEmitPrivate(const Expr *e,
QualType destType) {
assert(!destType->isVoidType() && "can't emit a void constant");

if (mlir::Attribute c =
ConstExprEmitter(*this).Visit(const_cast<Expr *>(e), destType))
return llvm::dyn_cast<mlir::TypedAttr>(c);

Expr::EvalResult result;

bool success = false;

if (destType->isReferenceType())
success = e->EvaluateAsLValue(result, cgm.getASTContext());
else
success =
e->EvaluateAsRValue(result, cgm.getASTContext(), inConstantContext);

if (success && !result.hasSideEffects()) {
mlir::Attribute c = tryEmitPrivate(result.Val, destType);
return llvm::dyn_cast<mlir::TypedAttr>(c);
}

return nullptr;
}

mlir::Attribute ConstantEmitter::tryEmitPrivate(const APValue &value,
QualType destType) {
auto &builder = cgm.getBuilder();
Expand Down
40 changes: 40 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,26 @@ void convertSideEffectForCall(mlir::Operation *callOp, bool isNothrow,
}
}

static mlir::LLVM::CallIntrinsicOp
createCallLLVMIntrinsicOp(mlir::ConversionPatternRewriter &rewriter,
mlir::Location loc, const llvm::Twine &intrinsicName,
mlir::Type resultTy, mlir::ValueRange operands) {
auto intrinsicNameAttr =
mlir::StringAttr::get(rewriter.getContext(), intrinsicName);
return mlir::LLVM::CallIntrinsicOp::create(rewriter, loc, resultTy,
intrinsicNameAttr, operands);
}

static mlir::LLVM::CallIntrinsicOp replaceOpWithCallLLVMIntrinsicOp(
mlir::ConversionPatternRewriter &rewriter, mlir::Operation *op,
const llvm::Twine &intrinsicName, mlir::Type resultTy,
mlir::ValueRange operands) {
mlir::LLVM::CallIntrinsicOp callIntrinOp = createCallLLVMIntrinsicOp(
rewriter, op->getLoc(), intrinsicName, resultTy, operands);
rewriter.replaceOp(op, callIntrinOp.getOperation());
return callIntrinOp;
}

/// IntAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::IntAttr intAttr) {
mlir::Location loc = parentOp->getLoc();
Expand Down Expand Up @@ -1097,6 +1117,24 @@ mlir::LogicalResult CIRToLLVMCallOpLowering::matchAndRewrite(
getTypeConverter(), op.getCalleeAttr());
}

mlir::LogicalResult CIRToLLVMReturnAddrOpLowering::matchAndRewrite(
cir::ReturnAddrOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
auto llvmPtrTy = mlir::LLVM::LLVMPointerType::get(rewriter.getContext());
replaceOpWithCallLLVMIntrinsicOp(rewriter, op, "llvm.returnaddress",
Copy link
Contributor

Choose a reason for hiding this comment

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

Given that we're lowering these to an LLVM intrinsic call anyway, I wonder if we should just use cir.llvm.intrinsic rather than create dedicated ops for them.

@bcardosolopes Were these useful for some kind of analysis?

Copy link
Member

Choose a reason for hiding this comment

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

The information coming from these builtins are likely useful in order to mark some function as unsafe for certain optimizations (e.g. where the return address or stack addressed in general might escape), they feel very different IMO than random shift vectors for ARM SVE. That said, you are right we don't use them right now, but I'd argue for keeping them because their behavior could be more intrusive than regular intrinsics.

llvmPtrTy, adaptor.getOperands());
return mlir::success();
}

mlir::LogicalResult CIRToLLVMFrameAddrOpLowering::matchAndRewrite(
cir::FrameAddrOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
auto llvmPtrTy = mlir::LLVM::LLVMPointerType::get(rewriter.getContext());
replaceOpWithCallLLVMIntrinsicOp(rewriter, op, "llvm.frameaddress", llvmPtrTy,
adaptor.getOperands());
return mlir::success();
}

mlir::LogicalResult CIRToLLVMLoadOpLowering::matchAndRewrite(
cir::LoadOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
Expand Down Expand Up @@ -2307,10 +2345,12 @@ void ConvertCIRToLLVMPass::runOnOperation() {
CIRToLLVMConstantOpLowering,
CIRToLLVMExpectOpLowering,
CIRToLLVMFAbsOpLowering,
CIRToLLVMFrameAddrOpLowering,
CIRToLLVMFuncOpLowering,
CIRToLLVMGetBitfieldOpLowering,
CIRToLLVMGetGlobalOpLowering,
CIRToLLVMGetMemberOpLowering,
CIRToLLVMReturnAddrOpLowering,
CIRToLLVMRotateOpLowering,
CIRToLLVMSelectOpLowering,
CIRToLLVMSetBitfieldOpLowering,
Expand Down
20 changes: 20 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,26 @@ class CIRToLLVMCallOpLowering : public mlir::OpConversionPattern<cir::CallOp> {
mlir::ConversionPatternRewriter &rewriter) const override;
};

class CIRToLLVMReturnAddrOpLowering
: public mlir::OpConversionPattern<cir::ReturnAddrOp> {
public:
using mlir::OpConversionPattern<cir::ReturnAddrOp>::OpConversionPattern;

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

class CIRToLLVMFrameAddrOpLowering
: public mlir::OpConversionPattern<cir::FrameAddrOp> {
public:
using mlir::OpConversionPattern<cir::FrameAddrOp>::OpConversionPattern;

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

class CIRToLLVMAllocaOpLowering
: public mlir::OpConversionPattern<cir::AllocaOp> {
mlir::DataLayout const &dataLayout;
Expand Down
28 changes: 28 additions & 0 deletions clang/test/CIR/CodeGen/builtins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,31 @@ double fabs(double x) {
// CIR: {{.*}} = cir.fabs {{.*}} : !cir.double
// LLVM: {{.*}} = call double @llvm.fabs.f64(double {{.*}})
// OGCG: {{.*}} = call double @llvm.fabs.f64(double {{.*}})

extern "C" void *test_return_address(void) {
return __builtin_return_address(1);

// CIR-LABEL: test_return_address
// CIR: [[ARG:%.*]] = cir.const #cir.int<1> : !u32i
// CIR: {{%.*}} = cir.return_address([[ARG]])

// LLVM-LABEL: @test_return_address
// LLVM: {{%.*}} = call ptr @llvm.returnaddress(i32 1)

// OGCG-LABEL: @test_return_address
// OGCG: {{%.*}} = call ptr @llvm.returnaddress(i32 1)
}

extern "C" void *test_frame_address(void) {
return __builtin_frame_address(1);

// CIR-LABEL: test_frame_address
// CIR: [[ARG:%.*]] = cir.const #cir.int<1> : !u32i
// CIR: {{%.*}} = cir.frame_address([[ARG]])

// LLVM-LABEL: @test_frame_address
// LLVM: {{%.*}} = call ptr @llvm.frameaddress.p0(i32 1)

// OGCG-LABEL: @test_frame_address
// OGCG: {{%.*}} = call ptr @llvm.frameaddress.p0(i32 1)
}