https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/221067
>From 708017d2380314bb8bfec88e0fd2aac2a865bc2f Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Thu, 3 Sep 2026 15:13:17 -0700 Subject: [PATCH] [CIR] Widen memory effects for ABI-introduced argument memory Both memory-effect sites in LowerToLLVM now check `arg_attrs` and add argument memory, as classic does in `AddPotentialArgAccess`. Variadic definitions widen too, since their parameters do not cover every argument. CallConvLowering also carries `side_effect` across when it rebuilds a call. Assisted-by: Cursor / claude-opus-5 --- .../TargetLowering/CIRABIRewriteContext.cpp | 23 ++- .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 118 +++++++-------- clang/test/CIR/CodeGen/side-effect.cpp | 139 +++++++++++++++--- .../abi-lowering/side-effect-argmem.cir | 132 +++++++++++++++++ 4 files changed, 326 insertions(+), 86 deletions(-) create mode 100644 clang/test/CIR/Transforms/abi-lowering/side-effect-argmem.cir diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp index 918f3f17b9933..4e8a04cb8ffbf 100644 --- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp +++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp @@ -811,7 +811,8 @@ SmallVector<mlir::NamedAttribute> buildSretSlotAttrs(mlir::OpBuilder &builder, // it, and once the CIR `!cir.ptr<retTy>` lowers to an opaque LLVM `ptr` the // pointee type can no longer be recovered from the pointer. attrs.push_back( - builder.getNamedAttr("llvm.sret", mlir::TypeAttr::get(retTy))); + builder.getNamedAttr(mlir::LLVM::LLVMDialect::getStructRetAttrName(), + mlir::TypeAttr::get(retTy))); attrs.push_back( builder.getNamedAttr("llvm.align", builder.getI64IntegerAttr(align))); if (withNoalias) @@ -823,6 +824,18 @@ SmallVector<mlir::NamedAttribute> buildSretSlotAttrs(mlir::OpBuilder &builder, return attrs; } +/// Carry \p call's attributes over to the rewritten \p newCall, leaving +/// attributes already on \p newCall, such as its callee, alone. +/// +/// side_effect is set explicitly because it is a DefaultValuedAttr: a fresh +/// call already carries `all`, so the copy sees it as present and skips it. +void carryCallAttrs(cir::CallOp call, cir::CallOp newCall) { + for (mlir::NamedAttribute attr : call->getAttrs()) + if (!newCall->hasAttr(attr.getName())) + newCall->setAttr(attr.getName(), attr.getValue()); + newCall.setSideEffect(call.getSideEffect()); +} + /// Prepend the sret slot's attrs at position 0 of newCall's arg_attrs. /// Called after the call has been rewritten with the sret pointer at /// operand 0, so the operand count now includes the sret slot. \p argAttrs @@ -937,9 +950,7 @@ void rewriteIndirectReturnCall(cir::CallOp call, prependIndirectCallee(call, sretArgs, sretVoidTy, builder); auto newCall = cir::CallOp::create( builder, call.getLoc(), call.getCalleeAttr(), sretVoidTy, sretArgs); - for (mlir::NamedAttribute attr : call->getAttrs()) - if (!newCall->hasAttr(attr.getName())) - newCall->setAttr(attr.getName(), attr.getValue()); + carryCallAttrs(call, newCall); // Shape the per-argument attrs exactly as the non-sret path does // (signext / zeroext for Extend, drop Ignore slots, byval / align for @@ -1300,9 +1311,7 @@ CIRABIRewriteContext::rewriteCallSite(mlir::Operation *callOp, prependIndirectCallee(call, newArgs, callRetTy, builder); auto newCall = cir::CallOp::create(builder, call.getLoc(), call.getCalleeAttr(), callRetTy, newArgs); - for (mlir::NamedAttribute attr : call->getAttrs()) - if (!newCall->hasAttr(attr.getName())) - newCall->setAttr(attr.getName(), attr.getValue()); + carryCallAttrs(call, newCall); // Direct return with coercion: the new call returns the coerced type; // emit a coercion back to the original type for the call's existing uses. diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index 233e6aaa15adb..2cc8bc31a8176 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -410,45 +410,66 @@ mlir::Value lowerCirAttrAsValue(mlir::Operation *parentOp, return value; } -void convertSideEffectForCall(mlir::Operation *callOp, bool isNothrow, - cir::SideEffect sideEffect, - mlir::LLVM::MemoryEffectsAttr &memoryEffect, - bool &noUnwind, bool &willReturn, - bool &noReturn) { +/// Whether the ABI hands the callee memory through a pointer argument: an +/// sret slot for an indirect return, or byval / byref for an indirect +/// argument. Such a function reaches argument memory no matter what its +/// source-level attributes say, so `const` and `pure` cannot lower to a +/// memory effect that excludes it. +static bool hasABIIndirectMemoryArg(mlir::ArrayAttr argAttrs) { + if (!argAttrs) + return false; + return llvm::any_of(argAttrs, [](mlir::Attribute a) { + auto dict = mlir::cast<mlir::DictionaryAttr>(a); + return dict.contains(mlir::LLVM::LLVMDialect::getStructRetAttrName()) || + dict.contains(mlir::LLVM::LLVMDialect::getByValAttrName()) || + dict.contains(mlir::LLVM::LLVMDialect::getByRefAttrName()); + }); +} + +/// Lower a CIR `side_effect` to an LLVM memory effect, widening argument +/// memory to ModRef when \p accessesArgMemory, since a widened slot may be +/// either written or read. A null result means unknown effects, which is +/// how `All` is represented. +static mlir::LLVM::MemoryEffectsAttr +buildMemoryEffects(mlir::MLIRContext *ctx, cir::SideEffect sideEffect, + bool accessesArgMemory) { using mlir::LLVM::ModRefInfo; + ModRefInfo other; switch (sideEffect) { case cir::SideEffect::All: - memoryEffect = {}; - noUnwind = isNothrow; - willReturn = false; - break; - + return {}; case cir::SideEffect::Pure: - memoryEffect = mlir::LLVM::MemoryEffectsAttr::get( - callOp->getContext(), /*other=*/ModRefInfo::Ref, - /*argMem=*/ModRefInfo::Ref, - /*inaccessibleMem=*/ModRefInfo::Ref, - /*errnoMem=*/ModRefInfo::Ref, - /*targetMem0=*/ModRefInfo::Ref, - /*targetMem1=*/ModRefInfo::Ref); - noUnwind = true; - willReturn = true; + other = ModRefInfo::Ref; break; - case cir::SideEffect::Const: - memoryEffect = mlir::LLVM::MemoryEffectsAttr::get( - callOp->getContext(), /*other=*/ModRefInfo::NoModRef, - /*argMem=*/ModRefInfo::NoModRef, - /*inaccessibleMem=*/ModRefInfo::NoModRef, - /*errnoMem=*/ModRefInfo::NoModRef, - /*targetMem0=*/ModRefInfo::NoModRef, - /*targetMem1=*/ModRefInfo::NoModRef); - noUnwind = true; - willReturn = true; + other = ModRefInfo::NoModRef; break; } + ModRefInfo argMem = accessesArgMemory ? ModRefInfo::ModRef : other; + return mlir::LLVM::MemoryEffectsAttr::get(ctx, /*other=*/other, + /*argMem=*/argMem, + /*inaccessibleMem=*/other, + /*errnoMem=*/other, + /*targetMem0=*/other, + /*targetMem1=*/other); +} + +void convertSideEffectForCall(mlir::Operation *callOp, bool isNothrow, + cir::SideEffect sideEffect, + mlir::LLVM::MemoryEffectsAttr &memoryEffect, + bool &noUnwind, bool &willReturn, + bool &noReturn) { + bool accessesArgMemory = + hasABIIndirectMemoryArg(callOp->getAttrOfType<mlir::ArrayAttr>( + CIRDialect::getArgAttrsAttrName())); + memoryEffect = + buildMemoryEffects(callOp->getContext(), sideEffect, accessesArgMemory); + + bool isConstOrPure = sideEffect != cir::SideEffect::All; + noUnwind = isConstOrPure || isNothrow; + willReturn = isConstOrPure; noReturn = callOp->hasAttr(CIRDialect::getNoReturnAttrName()); } @@ -2752,35 +2773,16 @@ mlir::LogicalResult CIRToLLVMFuncOpLowering::matchAndRewrite( assert(!cir::MissingFeatures::opFuncMultipleReturnVals()); - if (std::optional<cir::SideEffect> sideEffectKind = op.getSideEffect()) { - switch (*sideEffectKind) { - case cir::SideEffect::All: - break; - case cir::SideEffect::Pure: - fn.setMemoryEffectsAttr(mlir::LLVM::MemoryEffectsAttr::get( - fn.getContext(), - /*other=*/mlir::LLVM::ModRefInfo::Ref, - /*argMem=*/mlir::LLVM::ModRefInfo::Ref, - /*inaccessibleMem=*/mlir::LLVM::ModRefInfo::Ref, - /*errnoMem=*/mlir::LLVM::ModRefInfo::Ref, - /*targetMem0=*/mlir::LLVM::ModRefInfo::Ref, - /*targetMem1=*/mlir::LLVM::ModRefInfo::Ref)); - fn.setNoUnwind(true); - fn.setWillReturn(true); - break; - case cir::SideEffect::Const: - fn.setMemoryEffectsAttr(mlir::LLVM::MemoryEffectsAttr::get( - fn.getContext(), - /*other=*/mlir::LLVM::ModRefInfo::NoModRef, - /*argMem=*/mlir::LLVM::ModRefInfo::NoModRef, - /*inaccessibleMem=*/mlir::LLVM::ModRefInfo::NoModRef, - /*errnoMem=*/mlir::LLVM::ModRefInfo::NoModRef, - /*targetMem0=*/mlir::LLVM::ModRefInfo::NoModRef, - /*targetMem1=*/mlir::LLVM::ModRefInfo::NoModRef)); - fn.setNoUnwind(true); - fn.setWillReturn(true); - break; - } + if (std::optional<cir::SideEffect> sideEffectKind = op.getSideEffect(); + sideEffectKind && *sideEffectKind != cir::SideEffect::All) { + // A variadic callee's declared parameters do not cover every argument, so + // its argument memory widens too. + bool accessesArgMemory = hasABIIndirectMemoryArg(op.getArgAttrsAttr()) || + op.getFunctionType().isVarArg(); + fn.setMemoryEffectsAttr(buildMemoryEffects(fn.getContext(), *sideEffectKind, + accessesArgMemory)); + fn.setNoUnwind(true); + fn.setWillReturn(true); } if (op->hasAttr(CIRDialect::getNoReturnAttrName())) diff --git a/clang/test/CIR/CodeGen/side-effect.cpp b/clang/test/CIR/CodeGen/side-effect.cpp index a1e0fbbeb0e2c..a53f5b3376e86 100644 --- a/clang/test/CIR/CodeGen/side-effect.cpp +++ b/clang/test/CIR/CodeGen/side-effect.cpp @@ -1,43 +1,140 @@ // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir // RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll -// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll -// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG +// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM + +struct Big { long a, b, c, d; }; +struct Small { int x; }; + extern "C" { +__attribute__((const)) Big const_sret(); +__attribute__((pure)) Big pure_sret(); +__attribute__((const)) int const_byval(Big b); +__attribute__((pure)) int pure_byval(Big b); +__attribute__((const)) Small const_small(); +__attribute__((const)) int const_ptr(const int *p); +__attribute__((const)) int const_variadic_decl(int n, ...); +__attribute__((const)) int const_byval2(int n, Big b); +__attribute__((const)) int const_bigref(const Big &b); + // FIXME: We should figure out how to better print this on functions in the // future. // CIR: cir.func{{.*}}@pure_func() -> !s32i side_effect(pure) attributes {{{.*}}nothrow} { -// LLVM: Function Attrs: {{.*}}nounwind{{.*}}willreturn{{.*}}memory(read) -// LLVM: define{{.*}} @pure_func() #{{.*}} { -// OGCG: Function Attrs: {{.*}}nounwind{{.*}}willreturn{{.*}}memory(read) -// OGCG: define{{.*}} @pure_func() #{{.*}} { __attribute__((pure)) int pure_func() { return 2;} // CIR: cir.func{{.*}}@const_func() -> !s32i side_effect(const) attributes {{{.*}}nothrow} { -// LLVM: Function Attrs: {{.*}}nounwind{{.*}}willreturn{{.*}}memory(none) -// LLVM: define{{.*}} @const_func() #{{.*}} { -// OGCG: Function Attrs: {{.*}}nounwind{{.*}}willreturn{{.*}}memory(none) -// OGCG: define{{.*}} @const_func() #{{.*}} { __attribute__((const)) int const_func() { return 1;} +// Variadic definition: widened with no indirect slot in the signature. +// CIR: cir.func{{.*}}@const_variadic(%arg0: !s32i {{.*}}, ...) -> !s32i side_effect(const) +__attribute__((const)) +int const_variadic(int n, ...) { return n; } + +// A definition also gets llvm.noalias on the sret slot, so this is where +// noalias, writable and the widened effect have to coexist. +// CIR: cir.func{{.*}}@const_sret_def(%arg0: !cir.ptr<!rec_Big> {{{.*}}llvm.noalias{{.*}}llvm.sret = !rec_Big{{.*}}llvm.writable{{.*}}) side_effect(const) +__attribute__((const)) +Big const_sret_def() { Big r{}; return r; } + void use() { - // CIR: cir.call @pure_func() side_effect(pure) : () -> !s32i - // LLVM: call i32 @pure_func() #[[PURE_ATTR:.*]] - // OGCG: call i32 @pure_func() #[[PURE_ATTR:.*]] + // Unwidened at a call site: neither takes an indirect slot. + // CIR: cir.call @pure_func() side_effect(pure) pure_func(); - // CIR: cir.call @const_func() side_effect(const) : () -> !s32i - // LLVM: call i32 @const_func() #[[CONST_ATTR:.*]] - // OGCG: call i32 @const_func() #[[CONST_ATTR:.*]] + // CIR: cir.call @const_func() side_effect(const) const_func(); + + // The pass has already given these calls their sret operand. + // CIR: cir.call @const_sret(%{{.+}}) side_effect(const) + const_sret(); + // CIR: cir.call @pure_sret(%{{.+}}) side_effect(pure) + pure_sret(); + + Big b{}; + int i = 0; + // CIR: cir.call @const_byval({{.*}}) side_effect(const) + const_byval(b); + // CIR: cir.call @pure_byval({{.*}}) side_effect(pure) + pure_byval(b); + + // CIR: cir.call @const_small() side_effect(const) + const_small(); + // A source-level pointer is not ABI-introduced memory, so the effect stays + // memory(none). + // CIR: cir.call @const_ptr({{.*}}) side_effect(const) + const_ptr(&i); + + // A call site sees the arguments it passes, so it is not widened for being + // variadic. + // CIR: cir.call @const_variadic({{.*}}) side_effect(const) + const_variadic(1); + // CIR: cir.call @const_variadic_decl({{.*}}) side_effect(const) + const_variadic_decl(1); + + // CIR: cir.call @const_byval2({{.*}}) side_effect(const) : (!s32i{{.*}}, !cir.ptr<!rec_Big> {{{.*}}llvm.byval{{.*}}}) -> !s32i + const_byval2(1, b); + // CIR: cir.call @const_sret_def(%{{.+}}) side_effect(const) + const_sret_def(); + + // A reference is a direct pointer slot that still carries align, so neither + // align nor a record pointee can stand in for an indirect slot. + // CIR: cir.call @const_bigref({{.*}}) side_effect(const) + const_bigref(b); } -// LLVM: attributes #[[PURE_ATTR]] = {{{.*}}nounwind{{.*}}willreturn{{.*}}memory(read) } -// OGCG: attributes #[[PURE_ATTR]] = {{{.*}}nounwind{{.*}}willreturn{{.*}}memory(read) } -// LLVM: attributes #[[CONST_ATTR]] = {{{.*}}nounwind{{.*}}willreturn{{.*}}memory(none) } -// OGCG: attributes #[[CONST_ATTR]] = {{{.*}}nounwind{{.*}}willreturn{{.*}}memory(none) } } +// The named functions appear in the same relative order in both emits, so +// these checks are ordered. The intrinsic declarations do not, so each +// attribute group is captured off its own define or declare line instead of +// matched adjacent to it. + +// Definitions. +// LLVM: define{{.*}} i32 @pure_func() #[[READ_DEF:[0-9]+]] { +// LLVM: define{{.*}} i32 @const_func() #[[NONE_DEF:[0-9]+]] { +// LLVM: define{{.*}} i32 @const_variadic(i32 noundef %{{[^,)]+}}, ...) #[[ARGMEM_DEF:[0-9]+]] { +// LLVM: define{{.*}} void @const_sret_def(ptr dead_on_unwind noalias writable sret(%struct.Big) align 8 %{{[^,)]+}}) #[[ARGMEM_DEF]] { + +// Call sites. +// LLVM: define{{.*}} void @use() +// LLVM: call i32 @pure_func() #[[READ_CALL:[0-9]+]] +// LLVM: call i32 @const_func() #[[NONE_CALL:[0-9]+]] +// LLVM: call void @const_sret(ptr dead_on_unwind writable sret(%struct.Big) align 8 %{{.+}}) #[[ARGMEM_CALL:[0-9]+]] +// LLVM: call void @pure_sret(ptr dead_on_unwind writable sret(%struct.Big) align 8 %{{.+}}) #[[READ_ARGMEM_CALL:[0-9]+]] +// LLVM: call i32 @const_byval(ptr noundef byval(%struct.Big) align 8 %{{.+}}) #[[ARGMEM_CALL]] +// LLVM: call i32 @pure_byval(ptr noundef byval(%struct.Big) align 8 %{{.+}}) #[[READ_ARGMEM_CALL]] +// LLVM: call i32 @const_small() #[[NONE_CALL]] +// LLVM: call i32 @const_ptr(ptr noundef %{{.+}}) #[[NONE_CALL]] +// LLVM: call i32 (i32, ...) @const_variadic(i32 noundef 1) #[[NONE_CALL]] +// LLVM: call i32 (i32, ...) @const_variadic_decl(i32 noundef 1) #[[NONE_CALL]] +// LLVM: call i32 @const_byval2(i32 noundef 1, ptr noundef byval(%struct.Big) align 8 %{{.+}}) #[[ARGMEM_CALL]] +// LLVM: call void @const_sret_def(ptr dead_on_unwind writable sret(%struct.Big) align 8 %{{.+}}) #[[ARGMEM_CALL]] +// LLVM: call i32 @const_bigref(ptr noundef nonnull align 8 dereferenceable(32) %{{.+}}) #[[NONE_CALL]] + +// Declarations. +// LLVM: declare void @const_sret(ptr dead_on_unwind writable sret(%struct.Big) align 8) #[[ARGMEM_DECL:[0-9]+]] +// LLVM: declare void @pure_sret(ptr dead_on_unwind writable sret(%struct.Big) align 8) #[[READ_ARGMEM_DECL:[0-9]+]] +// LLVM: declare i32 @const_byval(ptr noundef byval(%struct.Big) align 8) #[[ARGMEM_DECL]] +// LLVM: declare i32 @pure_byval(ptr noundef byval(%struct.Big) align 8) #[[READ_ARGMEM_DECL]] +// LLVM: declare i32 @const_small() #[[NONE_DECL:[0-9]+]] +// LLVM: declare i32 @const_ptr(ptr noundef) #[[NONE_DECL]] +// LLVM: declare i32 @const_variadic_decl(i32 noundef, ...) #[[ARGMEM_DECL]] +// LLVM: declare i32 @const_byval2(i32 noundef, ptr noundef byval(%struct.Big) align 8) #[[ARGMEM_DECL]] +// LLVM: declare i32 @const_bigref(ptr noundef nonnull align 8 dereferenceable(32)) #[[NONE_DECL]] + +// The trailing wildcard covers target-features and the other codegen-option +// strings, which differ between the emits. +// LLVM-DAG: attributes #[[READ_DEF]] = { {{.*}}nounwind{{.*}}willreturn memory(read) {{.*}}} +// LLVM-DAG: attributes #[[NONE_DEF]] = { {{.*}}nounwind{{.*}}willreturn memory(none) {{.*}}} +// LLVM-DAG: attributes #[[ARGMEM_DEF]] = { {{.*}}nounwind{{.*}}willreturn memory(argmem: readwrite) {{.*}}} +// LLVM-DAG: attributes #[[ARGMEM_DECL]] = { nounwind willreturn memory(argmem: readwrite) {{.*}}} +// LLVM-DAG: attributes #[[READ_ARGMEM_DECL]] = { nounwind willreturn memory(read, argmem: readwrite) {{.*}}} +// LLVM-DAG: attributes #[[NONE_DECL]] = { nounwind willreturn memory(none) {{.*}}} +// LLVM-DAG: attributes #[[READ_CALL]] = { nounwind willreturn memory(read) } +// LLVM-DAG: attributes #[[NONE_CALL]] = { nounwind willreturn memory(none) } +// LLVM-DAG: attributes #[[ARGMEM_CALL]] = { nounwind willreturn memory(argmem: readwrite) } +// LLVM-DAG: attributes #[[READ_ARGMEM_CALL]] = { nounwind willreturn memory(read, argmem: readwrite) } diff --git a/clang/test/CIR/Transforms/abi-lowering/side-effect-argmem.cir b/clang/test/CIR/Transforms/abi-lowering/side-effect-argmem.cir new file mode 100644 index 0000000000000..b213dbf13f9db --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/side-effect-argmem.cir @@ -0,0 +1,132 @@ +// RUN: cir-opt %s -cir-call-conv-lowering="classification-attr=test_classify" \ +// RUN: | FileCheck %s +// The pass leaves test_classify on the lowered func, and its coerced_type is a +// CIR type, so the alias outlives every cir op and mlir-translate has to +// accept it. +// RUN: cir-opt %s -cir-call-conv-lowering="classification-attr=test_classify" \ +// RUN: -cir-to-llvm -o - \ +// RUN: | mlir-translate -mlir-to-llvmir --allow-unregistered-dialect \ +// RUN: | FileCheck %s --check-prefix=LLVM + +!s8i = !cir.int<s, 8> +!s32i = !cir.int<s, 32> +!s64i = !cir.int<s, 64> +!rec_Big = !cir.struct<"Big" {data !s64i, data !s64i, data !s64i}> + +// The leading direct argument puts the byval slot at index 1. +#byval_arg = { + return = { kind = "direct" }, + args = [ { kind = "direct" }, + { kind = "indirect", indirect_align = 8 } ] +} + +#byref_arg = { + return = { kind = "direct" }, + args = [ { kind = "indirect", indirect_align = 8, byval = false } ] +} + +#sret_ret = { + return = { kind = "indirect", indirect_align = 8 }, + args = [ ] +} + +#extend_arg = { + return = { kind = "direct" }, + args = [ { kind = "extend", + coerced_type = !cir.int<s, 32>, + sign_extend = true } ] +} + +#passthrough = { + return = { kind = "direct" }, + args = [ ] +} + +module attributes { + cir.triple = "x86_64-unknown-linux-gnu", + dlti.dl_spec = #dlti.dl_spec< + #dlti.dl_entry<i8, dense<8>: vector<2xi64>>, + #dlti.dl_entry<i32, dense<32>: vector<2xi64>>, + #dlti.dl_entry<i64, dense<64>: vector<2xi64>>> +} { + + cir.func private @const_sret() -> !rec_Big side_effect(const) + attributes { test_classify = #sret_ret } + + // CHECK: cir.func{{.*}} @const_sret(!cir.ptr<!rec_Big> {{{.*}}llvm.sret = !rec_Big{{.*}}}) side_effect(const) + // LLVM: ; Function Attrs: nounwind willreturn memory(argmem: readwrite) + // LLVM-NEXT: declare void @const_sret(ptr dead_on_unwind writable sret(%struct.Big) align 8) + + cir.func private @pure_sret() -> !rec_Big side_effect(pure) + attributes { test_classify = #sret_ret } + + // CHECK: cir.func{{.*}} @pure_sret(!cir.ptr<!rec_Big> {{{.*}}llvm.sret = !rec_Big{{.*}}}) side_effect(pure) + // LLVM: ; Function Attrs: nounwind willreturn memory(read, argmem: readwrite) + // LLVM-NEXT: declare void @pure_sret(ptr dead_on_unwind writable sret(%struct.Big) align 8) + + cir.func private @const_byval(!s32i, !rec_Big) side_effect(const) + attributes { test_classify = #byval_arg } + + // CHECK: cir.func{{.*}} @const_byval(!s32i, !cir.ptr<!rec_Big> {{{.*}}llvm.byval = !rec_Big{{.*}}}) side_effect(const) + // LLVM: ; Function Attrs: nounwind willreturn memory(argmem: readwrite) + // LLVM-NEXT: declare void @const_byval(i32, ptr noundef byval(%struct.Big) align 8) + + cir.func private @const_byref(!rec_Big) side_effect(const) + attributes { test_classify = #byref_arg } + + // CHECK: cir.func{{.*}} @const_byref(!cir.ptr<!rec_Big> {{{.*}}llvm.byref = !rec_Big{{.*}}}) side_effect(const) + // LLVM: ; Function Attrs: nounwind willreturn memory(argmem: readwrite) + // LLVM-NEXT: declare void @const_byref(ptr byref(%struct.Big) align 8) + + cir.func private @const_variadic(!s8i, ...) -> !s32i side_effect(const) + attributes { test_classify = #extend_arg } + + // CHECK: cir.func{{.*}} @const_variadic(!s8i {{{.*}}llvm.signext{{.*}}}, ...) -> !s32i side_effect(const) + // LLVM: ; Function Attrs: nounwind willreturn memory(argmem: readwrite) + // LLVM-NEXT: declare i32 @const_variadic(i8 signext, ...) + + // Negative: an Extend argument carries a non-empty attribute dictionary but + // no ABI-introduced memory, so the effect stays memory(none). + cir.func private @const_extend(!s8i) -> !s32i side_effect(const) + attributes { test_classify = #extend_arg } + + // CHECK: cir.func{{.*}} @const_extend(!s8i {{{.*}}llvm.signext{{.*}}}) -> !s32i side_effect(const) + // LLVM: ; Function Attrs: nounwind willreturn memory(none) + // LLVM-NEXT: declare i32 @const_extend(i8 signext) + + // A callee with no side_effect must still come out with none, so that + // forwarding the original is distinguishable from hard-coding a value. + cir.func private @plain_sret() -> !rec_Big + attributes { test_classify = #sret_ret } + + cir.func @call_plain_sret() attributes { test_classify = #passthrough } { + %r = cir.call @plain_sret() : () -> !rec_Big + cir.return + } + + // CHECK: cir.call @plain_sret(%{{[^)]+}}) : (!cir.ptr<!rec_Big>{{.*}}) -> () + // LLVM: call void @plain_sret(ptr dead_on_unwind writable sret(%struct.Big) align 8 %{{.+}}){{$}} + + // nothrow without const or pure still reaches nounwind, and still gets no + // memory effect. + cir.func @call_plain_sret_nothrow() attributes { test_classify = #passthrough } { + %r = cir.call @plain_sret() nothrow : () -> !rec_Big + cir.return + } + + // CHECK: cir.call @plain_sret(%{{[^)]+}}) nothrow : (!cir.ptr<!rec_Big>{{.*}}) -> () + // LLVM: call void @plain_sret(ptr dead_on_unwind writable sret(%struct.Big) align 8 %{{.+}}) #[[NOTHROW_CALL:[0-9]+]] + + // The call site keeps its side_effect across the rewrite. + cir.func @call_const_sret() attributes { test_classify = #passthrough } { + %r = cir.call @const_sret() side_effect(const) : () -> !rec_Big + cir.return + } + + // CHECK: cir.func{{.*}} @call_const_sret() + // CHECK: cir.call @const_sret(%{{.+}}) side_effect(const) + // LLVM: define void @call_const_sret() + // LLVM: call void @const_sret(ptr dead_on_unwind writable sret(%struct.Big) align 8 %{{.+}}) #[[CALL_ATTRS:[0-9]+]] + // LLVM-DAG: attributes #[[CALL_ATTRS]] = { nounwind willreturn memory(argmem: readwrite) } + // LLVM-DAG: attributes #[[NOTHROW_CALL]] = { nounwind } +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
