diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index 1e0fb038b19d8..47ff9389e8028 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -1026,4 +1026,29 @@ def CIR_UnwindAttr : CIR_UnitAttr<"Unwind", "unwind"> { let storageType = [{ CatchUnwind }]; } +//===----------------------------------------------------------------------===// +// CIR_BlockAddrInfoAttr +//===----------------------------------------------------------------------===// + +def CIR_BlockAddrInfoAttr : CIR_Attr<"BlockAddrInfo", "block_addr_info"> { + let summary = "Block Addres attribute"; + let description = [{ + This attribute is used to represent the address of a basic block + within a function. It combines the symbol reference to a function + with the name of a label inside that function. + }]; + let parameters = (ins "mlir::FlatSymbolRefAttr":$func, + "mlir::StringAttr":$label); + + let assemblyFormat = "`<` $func `,` $label `>`"; + let builders = [ + AttrBuilder<(ins "llvm::StringRef":$func_name, + "llvm::StringRef":$label_name + ), [{ + return $_get($_ctxt, mlir::FlatSymbolRefAttr::get($_ctxt, func_name), + mlir::StringAttr::get($_ctxt, label_name)); + }]> + ]; +} + #endif // CLANG_CIR_DIALECT_IR_CIRATTRS_TD diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 16258513239d9..10c801c4762ea 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -4800,4 +4800,38 @@ def CIR_AtomicClearOp : CIR_Op<"atomic.clear"> { }]; } +//===----------------------------------------------------------------------===// +// BlockAddressOp +//===----------------------------------------------------------------------===// + +def CIR_BlockAddressOp : CIR_Op<"block_address", [Pure]> { + let summary = "Get the address of a cir.label within a function"; + let description = [{ + The `cir.blockaddress` operation takes a function name and a label and + produces a pointer value that represents the address of that cir.label + within the specified function. + + This operation models GCC's "labels as values" extension (`&&label`), which + allows taking the address of a local label and using it as a computed + jump target (e.g., with `goto *addr;`). + + Example: + ```mlir + %1 = cir.alloca !cir.ptr, !cir.ptr>, ["ptr", init] + {alignment = 8 : i64} + %addr = cir.block_address <@c, "label1"> : !cir.ptr + cir.store align(8) %addr, %1 : !cir.ptr, !cir.ptr> + cir.br ^bb1 + ^bb1: + cir.label "label" + ``` + }]; + + let arguments = (ins CIR_BlockAddrInfoAttr:$block_addr_info); + let results = (outs CIR_VoidPtrType:$addr); + let assemblyFormat = [{ + $block_addr_info `:` qualified(type($addr)) attr-dict + }]; +} + #endif // CLANG_CIR_DIALECT_IR_CIROPS_TD diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index 4461875fcf678..e3e8f2827d841 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -166,6 +166,15 @@ class ScalarExprEmitter : public StmtVisitor { return emitLoadOfLValue(e); } + mlir::Value VisitAddrLabelExpr(const AddrLabelExpr *e) { + auto func = cast(cgf.curFn); + auto blockInfoAttr = cir::BlockAddrInfoAttr::get( + &cgf.getMLIRContext(), func.getSymName(), e->getLabel()->getName()); + return cir::BlockAddressOp::create(builder, cgf.getLoc(e->getSourceRange()), + cgf.convertType(e->getType()), + blockInfoAttr); + } + mlir::Value VisitIntegerLiteral(const IntegerLiteral *e) { mlir::Type type = cgf.convertType(e->getType()); return cir::ConstantOp::create(builder, cgf.getLoc(e->getExprLoc()), diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 7ba03ce40140c..95dfcc7ce1c13 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -1902,22 +1902,45 @@ mlir::LogicalResult cir::FuncOp::verify() { llvm::SmallSet labels; llvm::SmallSet gotos; - + llvm::SmallSet blockAddresses; + bool invalidBlockAddress = false; getOperation()->walk([&](mlir::Operation *op) { if (auto lab = dyn_cast(op)) { labels.insert(lab.getLabel()); } else if (auto goTo = dyn_cast(op)) { gotos.insert(goTo.getLabel()); + } else if (auto blkAdd = dyn_cast(op)) { + if (blkAdd.getBlockAddrInfoAttr().getFunc().getAttr() != getSymName()) { + // Stop the walk early, no need to continue + invalidBlockAddress = true; + return mlir::WalkResult::interrupt(); + } + blockAddresses.insert(blkAdd.getBlockAddrInfoAttr().getLabel()); } + return mlir::WalkResult::advance(); }); + if (invalidBlockAddress) + return emitOpError() << "blockaddress references a different function"; + + llvm::SmallSet mismatched; if (!labels.empty() || !gotos.empty()) { - llvm::SmallSet mismatched = - llvm::set_difference(gotos, labels); + mismatched = llvm::set_difference(gotos, labels); if (!mismatched.empty()) return emitOpError() << "goto/label mismatch"; } + + mismatched.clear(); + + if (!labels.empty() || !blockAddresses.empty()) { + mismatched = llvm::set_difference(blockAddresses, labels); + + if (!mismatched.empty()) + return emitOpError() + << "expects an existing label target in the referenced function"; + } + return success(); } diff --git a/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp b/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp index 00972b6976295..d590ccce1f540 100644 --- a/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp +++ b/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp @@ -8,6 +8,7 @@ #include "PassDetail.h" #include "clang/CIR/Dialect/IR/CIRDialect.h" #include "clang/CIR/Dialect/Passes.h" +#include "llvm/ADT/SmallSet.h" #include "llvm/Support/TimeProfiler.h" #include @@ -30,17 +31,29 @@ static void process(cir::FuncOp func) { mlir::OpBuilder rewriter(func.getContext()); llvm::StringMap labels; llvm::SmallVector gotos; + llvm::SmallSet blockAddrLabel; func.getBody().walk([&](mlir::Operation *op) { if (auto lab = dyn_cast(op)) { - // Will construct a string copy inplace. Safely erase the label labels.try_emplace(lab.getLabel(), lab->getBlock()); - lab.erase(); } else if (auto goTo = dyn_cast(op)) { gotos.push_back(goTo); + } else if (auto blockAddr = dyn_cast(op)) { + blockAddrLabel.insert(blockAddr.getBlockAddrInfo().getLabel()); } }); + for (auto &lab : labels) { + StringRef labelName = lab.getKey(); + Block *block = lab.getValue(); + if (!blockAddrLabel.contains(labelName)) { + // erase the LabelOp inside the block if safe + if (auto lab = dyn_cast(&block->front())) { + lab.erase(); + } + } + } + for (auto goTo : gotos) { mlir::OpBuilder::InsertionGuard guard(rewriter); rewriter.setInsertionPoint(goTo); diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index b4afed7019417..13835949789ce 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -3778,6 +3778,12 @@ mlir::LogicalResult CIRToLLVMVAArgOpLowering::matchAndRewrite( return mlir::success(); } +mlir::LogicalResult CIRToLLVMBlockAddressOpLowering::matchAndRewrite( + cir::BlockAddressOp op, OpAdaptor adaptor, + mlir::ConversionPatternRewriter &rewriter) const { + return mlir::failure(); +} + std::unique_ptr createConvertCIRToLLVMPass() { return std::make_unique(); } diff --git a/clang/test/CIR/CodeGen/label-values.c b/clang/test/CIR/CodeGen/label-values.c new file mode 100644 index 0000000000000..41178e3f62f20 --- /dev/null +++ b/clang/test/CIR/CodeGen/label-values.c @@ -0,0 +1,76 @@ +// 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 + +void A(void) { + void *ptr = &&LABEL_A; +LABEL_A: + return; +} +// CIR: cir.func dso_local @A +// CIR: [[PTR:%.*]] = cir.alloca !cir.ptr, !cir.ptr>, ["ptr", init] {alignment = 8 : i64} +// CIR: [[BLOCK:%.*]] = cir.block_address <@A, "LABEL_A"> : !cir.ptr +// CIR: cir.store align(8) [[BLOCK]], [[PTR]] : !cir.ptr, !cir.ptr> +// CIR: cir.br ^bb1 +// CIR: ^bb1: // pred: ^bb0 +// CIR: cir.label "LABEL_A" +// CIR: cir.return + +void B(void) { +LABEL_B: + void *ptr = &&LABEL_B; +} + +// CIR: cir.func dso_local @B() +// CIR: [[PTR:%.*]] = cir.alloca !cir.ptr, !cir.ptr>, ["ptr", init] {alignment = 8 : i64} +// CIR: cir.br ^bb1 +// CIR: ^bb1: +// CIR: cir.label "LABEL_B" +// CIR: [[BLOCK:%.*]] = cir.block_address <@B, "LABEL_B"> : !cir.ptr +// CIR: cir.store align(8) [[BLOCK]], [[PTR]] : !cir.ptr, !cir.ptr> +// CIR: cir.return + +void C(int x) { + void *ptr = (x == 0) ? &&LABEL_A : &&LABEL_B; +LABEL_A: + return; +LABEL_B: + return; +} + +// CIR: cir.func dso_local @C +// CIR: [[BLOCK1:%.*]] = cir.block_address <@C, "LABEL_A"> : !cir.ptr +// CIR: [[BLOCK2:%.*]] = cir.block_address <@C, "LABEL_B"> : !cir.ptr +// CIR: [[COND:%.*]] = cir.select if [[CMP:%.*]] then [[BLOCK1]] else [[BLOCK2]] : (!cir.bool, !cir.ptr, !cir.ptr) -> !cir.ptr +// CIR: cir.store align(8) [[COND]], [[PTR:%.*]] : !cir.ptr, !cir.ptr> +// CIR: cir.br ^bb1 +// CIR: ^bb1: // pred: ^bb0 +// CIR: cir.label "LABEL_A" +// CIR: cir.br ^bb2 +// CIR: ^bb2: // 2 preds: ^bb1, ^bb3 +// CIR: cir.return +// CIR: ^bb3: // no predecessors +// CIR: cir.label "LABEL_B" +// CIR: cir.br ^bb2 + +void D(void) { + void *ptr = &&LABEL_A; + void *ptr2 = &&LABEL_A; +LABEL_A: + void *ptr3 = &&LABEL_A; + return; +} + +// CIR: cir.func dso_local @D +// CIR: %[[PTR:.*]] = cir.alloca !cir.ptr, !cir.ptr>, ["ptr", init] +// CIR: %[[PTR2:.*]] = cir.alloca !cir.ptr, !cir.ptr>, ["ptr2", init] +// CIR: %[[PTR3:.*]] = cir.alloca !cir.ptr, !cir.ptr>, ["ptr3", init] +// CIR: %[[BLK1:.*]] = cir.block_address <@D, "LABEL_A"> : !cir.ptr +// CIR: cir.store align(8) %[[BLK1]], %[[PTR]] : !cir.ptr, !cir.ptr> +// CIR: %[[BLK2:.*]] = cir.block_address <@D, "LABEL_A"> : !cir.ptr +// CIR: cir.store align(8) %[[BLK2]], %[[PTR2]] : !cir.ptr, !cir.ptr> +// CIR: cir.br ^bb1 +// CIR: ^bb1: // pred: ^bb0 +// CIR: cir.label "LABEL_A" +// CIR: %[[BLK3:.*]] = cir.block_address <@D, "LABEL_A"> : !cir.ptr +// CIR: cir.store align(8) %[[BLK3]], %[[PTR3]] : !cir.ptr, !cir.ptr> +// CIR: cir.return diff --git a/clang/test/CIR/IR/block-adress.cir b/clang/test/CIR/IR/block-adress.cir new file mode 100644 index 0000000000000..9d6840819c2d4 --- /dev/null +++ b/clang/test/CIR/IR/block-adress.cir @@ -0,0 +1,34 @@ +// RUN: cir-opt %s --verify-roundtrip | FileCheck %s + +!void = !cir.void + +module { + cir.func @block_address(){ + %0 = cir.block_address <@block_address, "label"> : !cir.ptr + cir.br ^bb1 + ^bb1: + cir.label "label" + cir.return + } +// CHECK: cir.func @block_address +// CHECK: %0 = cir.block_address <@block_address, "label"> : !cir.ptr +// CHECK: cir.br ^bb1 +// CHECK: ^bb1: +// CHECK: cir.label "label" +// CHECK: cir.return + +cir.func @block_address_inside_scope() -> () { + cir.scope{ + %0 = cir.block_address <@block_address_inside_scope, "label"> : !cir.ptr + } + cir.br ^bb1 +^bb1: + cir.label "label" + cir.return +} +// CHECK: cir.func @block_address_inside_scope +// CHECK: cir.scope +// CHECK: %0 = cir.block_address <@block_address_inside_scope, "label"> : !cir.ptr +// CHECK: cir.label "label" +// CHECK: cir.return +} diff --git a/clang/test/CIR/IR/invalid-block-address.cir b/clang/test/CIR/IR/invalid-block-address.cir new file mode 100644 index 0000000000000..4519485c28803 --- /dev/null +++ b/clang/test/CIR/IR/invalid-block-address.cir @@ -0,0 +1,21 @@ +// RUN: cir-opt %s -verify-diagnostics -split-input-file + +!void = !cir.void + +// expected-error@+1 {{expects an existing label target in the referenced function}} +cir.func @bad_block_address() -> () { + %0 = cir.block_address <@bad_block_address, "label"> : !cir.ptr + cir.br ^bb1 + ^bb1: + cir.label "wrong_label" + cir.return +} + +// expected-error@+1 {{blockaddress references a different function}} +cir.func @bad_block_func() -> () { + %0 = cir.block_address <@mismatch_func, "label"> : !cir.ptr + cir.br ^bb1 + ^bb1: + cir.label "label" + cir.return +} diff --git a/clang/test/CIR/Transforms/goto_solver.cir b/clang/test/CIR/Transforms/goto_solver.cir new file mode 100644 index 0000000000000..6ae019b44a39e --- /dev/null +++ b/clang/test/CIR/Transforms/goto_solver.cir @@ -0,0 +1,62 @@ +// RUN: cir-opt %s -cir-goto-solver --verify-roundtrip -o - | FileCheck %s + +!void = !cir.void + +cir.func @a(){ + %0 = cir.alloca !cir.ptr, !cir.ptr>, ["ptr", init] {alignment = 8 : i64} + %1 = cir.block_address <@a, "label1"> : !cir.ptr + cir.store align(8) %1, %0 : !cir.ptr, !cir.ptr> + cir.br ^bb1 +^bb1: + cir.label "label1" + cir.br ^bb2 +^bb2: + // This label is not referenced by any blockaddressOp, so it should be removed + cir.label "label2" + cir.return +} + +// CHECK: cir.func @a() +// CHECK: %1 = cir.block_address <@a, "label1"> : !cir.ptr +// CHECK: ^bb1: +// CHECK: cir.label "label1" +// CHECK: cir.br ^bb2 +// CHECK: ^bb2: +// CHECK-NOT: cir.label "label2" + +cir.func @b(){ + %0 = cir.alloca !cir.ptr, !cir.ptr>, ["ptr", init] {alignment = 8 : i64} + %1 = cir.block_address <@b, "label1"> : !cir.ptr + cir.store align(8) %1, %0 : !cir.ptr, !cir.ptr> + cir.goto "label2" +^bb1: + cir.label "label1" + cir.br ^bb2 +^bb2: + // This label is not referenced by any blockaddressOp, so it should be removed + cir.label "label2" + cir.return +} + +// CHECK: cir.func @b() { +// CHECK: %1 = cir.block_address <@b, "label1"> : !cir.ptr +// CHECK: cir.store align(8) %1, {{.*}} : !cir.ptr, !cir.ptr> +// CHECK: cir.br ^bb2 +// CHECK: ^bb1: +// CHECK: cir.label "label1" +// CHECK: cir.br ^bb2 +// CHECK: ^bb2: +// CHECK-NOT: cir.label "label2" + +cir.func @c() { + cir.label "label1" + %0 = cir.alloca !cir.ptr, !cir.ptr>, ["ptr", init] {alignment = 8 : i64} + %1 = cir.block_address <@c, "label1"> : !cir.ptr + cir.store align(8) %1, %0 : !cir.ptr, !cir.ptr> + cir.return +} + +// CHECK: cir.func @c +// CHECK: cir.label "label1" +// CHECK: %1 = cir.block_address <@c, "label1"> : !cir.ptr +// CHECK: cir.store align(8) %1, {{.*}} : !cir.ptr, !cir.ptr> diff --git a/clang/tools/cir-opt/cir-opt.cpp b/clang/tools/cir-opt/cir-opt.cpp index c4d29a2117c75..ee42015bb38e9 100644 --- a/clang/tools/cir-opt/cir-opt.cpp +++ b/clang/tools/cir-opt/cir-opt.cpp @@ -58,6 +58,10 @@ int main(int argc, char **argv) { return mlir::createHoistAllocasPass(); }); + ::mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> { + return mlir::createGotoSolverPass(); + }); + mlir::registerTransformsPasses(); return mlir::asMainReturnCode(MlirOptMain(