Skip to content
Open
33 changes: 33 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -6118,6 +6118,39 @@ def CIR_LinkerOptionsOp : CIR_Op<"linker_options", [
}];
}

//===----------------------------------------------------------------------===//
// BlockAddressOp
//===----------------------------------------------------------------------===//

def CIR_BlockAddressOp : CIR_Op<"blockaddress", [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<!void>, !cir.ptr<!cir.ptr<!void>>, ["ptr", init] {alignment = 8 : i64}
%addr = cir.blockaddress("foo", "label") -> !cir.ptr<!cir.void>
cir.store align(8) %addr, %1 : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>
cir.br ^bb1
^bb1:
cir.label "label"
```
}];

let arguments = (ins FlatSymbolRefAttr:$func, StrAttr:$label);
let results = (outs CIR_VoidPtrType:$addr);
let assemblyFormat = [{
`(` $func `,` $label `)` `->` qualified(type($addr)) attr-dict
}];
}

//===----------------------------------------------------------------------===//
// Standard library function calls
//===----------------------------------------------------------------------===//
Expand Down
14 changes: 12 additions & 2 deletions clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,18 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
}

mlir::Value VisitUnaryExprOrTypeTraitExpr(const UnaryExprOrTypeTraitExpr *E);
mlir::Value VisitAddrLabelExpr(const AddrLabelExpr *E) {
llvm_unreachable("NYI");

mlir::Value VisitAddrLabelExpr(const AddrLabelExpr *e) {
auto func = cast<cir::FuncOp>(CGF.CurFn);
llvm::StringRef symName = func.getSymName();
mlir::FlatSymbolRefAttr funName =
mlir::FlatSymbolRefAttr::get(&CGF.getMLIRContext(), symName);
mlir::StringAttr labelName =
mlir::StringAttr::get(&CGF.getMLIRContext(), e->getLabel()->getName());
return cir::BlockAddressOp::create(Builder, CGF.getLoc(e->getSourceRange()),
CGF.convertType(e->getType()), funName,
labelName);
;
}
mlir::Value VisitSizeOfPackExpr(SizeOfPackExpr *E) {
llvm_unreachable("NYI");
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/CIR/CodeGen/CIRGenStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,6 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *S,
case Stmt::CXXForRangeStmtClass:
return emitCXXForRangeStmt(cast<CXXForRangeStmt>(*S), Attrs);

case Stmt::IndirectGotoStmtClass:
case Stmt::ReturnStmtClass:
// When implemented, GCCAsmStmtClass should fall-through to MSAsmStmtClass.
case Stmt::GCCAsmStmtClass:
Expand All @@ -196,6 +195,7 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *S,
case Stmt::OMPBarrierDirectiveClass:
return emitOMPBarrierDirective(cast<OMPBarrierDirective>(*S));
// Unsupported AST nodes:
case Stmt::IndirectGotoStmtClass:
case Stmt::CapturedStmtClass:
case Stmt::ObjCAtTryStmtClass:
case Stmt::ObjCAtThrowStmtClass:
Expand Down
46 changes: 35 additions & 11 deletions clang/lib/CIR/Dialect/IR/CIRDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
#include "clang/CIR/Dialect/IR/CIRTypes.h"
#include "clang/CIR/Interfaces/CIRLoopOpInterface.h"
#include "clang/CIR/MissingFeatures.h"
#include "llvm/ADT/SetOperations.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/LogicalResult.h"
#include <numeric>
#include <optional>
#include <set>

#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/LLVMIR/LLVMTypes.h"
Expand Down Expand Up @@ -2928,23 +2929,46 @@ LogicalResult cir::FuncOp::verify() {
<< "' must have empty body";
}

std::set<llvm::StringRef> labels;
std::set<llvm::StringRef> gotos;

llvm::SmallSet<llvm::StringRef, 16> labels;
llvm::SmallSet<llvm::StringRef, 16> gotos;
llvm::SmallSet<llvm::StringRef, 16> blockAddresses;
bool invalidBlockAddress = false;
getOperation()->walk([&](mlir::Operation *op) {
if (auto lab = dyn_cast<cir::LabelOp>(op)) {
labels.emplace(lab.getLabel());
labels.insert(lab.getLabel());
} else if (auto goTo = dyn_cast<cir::GotoOp>(op)) {
gotos.emplace(goTo.getLabel());
gotos.insert(goTo.getLabel());
} else if (auto blkAdd = dyn_cast<cir::BlockAddressOp>(op)) {
if (blkAdd.getFunc() != getSymName()) {
// Stop the walk early, no need to continue
invalidBlockAddress = true;
return mlir::WalkResult::interrupt();
}
blockAddresses.insert(blkAdd.getLabel());
}
return mlir::WalkResult::advance();
});

std::vector<llvm::StringRef> mismatched;
std::set_difference(gotos.begin(), gotos.end(), labels.begin(), labels.end(),
std::back_inserter(mismatched));
if (invalidBlockAddress)
return emitOpError() << "blockaddress references a different function";

llvm::SmallSet<llvm::StringRef, 16> mismatched;
if (!labels.empty() || !gotos.empty()) {
mismatched = llvm::set_difference(gotos, labels);

if (!mismatched.empty())
return emitOpError() << "goto/label mismatch";
}

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();
}
Expand Down
19 changes: 16 additions & 3 deletions clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/Dialect/Passes.h"

#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
Expand All @@ -24,17 +25,29 @@ static void process(cir::FuncOp func) {
mlir::OpBuilder rewriter(func.getContext());
llvm::StringMap<Block *> labels;
llvm::SmallVector<cir::GotoOp, 4> gotos;
llvm::SmallSet<StringRef, 4> blockAddrLabel;

func.getBody().walk([&](mlir::Operation *op) {
if (auto lab = dyn_cast<cir::LabelOp>(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<cir::GotoOp>(op)) {
gotos.push_back(goTo);
} else if (auto blockAddr = dyn_cast<cir::BlockAddressOp>(op)) {
blockAddrLabel.insert(blockAddr.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<cir::LabelOp>(&block->front())) {
lab.erase();
}
}
}

for (auto goTo : gotos) {
mlir::OpBuilder::InsertionGuard guard(rewriter);
rewriter.setInsertionPoint(goTo);
Expand All @@ -54,4 +67,4 @@ void GotoSolverPass::runOnOperation() {

std::unique_ptr<Pass> mlir::createGotoSolverPass() {
return std::make_unique<GotoSolverPass>();
}
}
74 changes: 74 additions & 0 deletions clang/test/CIR/CodeGen/label-values.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// 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 = &&A;
A:
return;
}
// CIR: cir.func dso_local @A
// CIR: [[PTR:%.*]] = cir.alloca !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>, ["ptr", init] {alignment = 8 : i64}
// CIR: [[BLOCK:%.*]] = cir.blockaddress(@A, "A") -> !cir.ptr<!void>
// CIR: cir.store align(8) [[BLOCK]], [[PTR]] : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>
// CIR: cir.br ^bb1
// CIR: ^bb1: // pred: ^bb0
// CIR: cir.label "A"
// CIR: cir.return

void B(void) {
B:
void *ptr = &&B;
}

// CIR: cir.func dso_local @B()
// CIR: cir.label "B"
// CIR: [[PTR:%.*]] = cir.alloca !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>, ["ptr", init] {alignment = 8 : i64}
// CIR: [[BLOCK:%.*]] = cir.blockaddress(@B, "B") -> !cir.ptr<!void>
// CIR: cir.store align(8) [[BLOCK]], [[PTR]] : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>
// CIR: cir.return

void C(int x) {
void *ptr = (x == 0) ? &&A : &&B;
A:
return;
B:
return;
}

// CIR: cir.func dso_local @C
// CIR: [[BLOCK1:%.*]] = cir.blockaddress(@C, "A") -> !cir.ptr<!void>
// CIR: [[BLOCK2:%.*]] = cir.blockaddress(@C, "B") -> !cir.ptr<!void>
// CIR: [[COND:%.*]] = cir.select if [[CMP:%.*]] then [[BLOCK1]] else [[BLOCK2]] : (!cir.bool, !cir.ptr<!void>, !cir.ptr<!void>) -> !cir.ptr<!void>
// CIR: cir.store align(8) [[COND]], [[PTR:%.*]] : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>
// CIR: cir.br ^bb2
// CIR: ^bb1: // 2 preds: ^bb2, ^bb3
// CIR: cir.return
// CIR: ^bb2: // pred: ^bb0
// CIR: cir.label "A"
// CIR: cir.br ^bb1
// CIR: ^bb3: // no predecessors
// CIR: cir.label "B"
// CIR: cir.br ^bb1

void D(void) {
void *ptr = &&A;
void *ptr2 = &&A;
A:
void *ptr3 = &&A;
return;
}

// CIR: cir.func dso_local @D
// CIR: %[[PTR:.*]] = cir.alloca !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>, ["ptr", init]
// CIR: %[[PTR2:.*]] = cir.alloca !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>, ["ptr2", init]
// CIR: %[[PTR3:.*]] = cir.alloca !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>, ["ptr3", init]
// CIR: %[[BLK1:.*]] = cir.blockaddress(@D, "A") -> !cir.ptr<!void>
// CIR: cir.store align(8) %[[BLK1]], %[[PTR]] : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>
// CIR: %[[BLK2:.*]] = cir.blockaddress(@D, "A") -> !cir.ptr<!void>
// CIR: cir.store align(8) %[[BLK2]], %[[PTR2]] : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>
// CIR: cir.br ^bb1
// CIR: ^bb1: // pred: ^bb0
// CIR: cir.label "A"
// CIR: %[[BLK3:.*]] = cir.blockaddress(@D, "A") -> !cir.ptr<!void>
// CIR: cir.store align(8) %[[BLK3]], %[[PTR3]] : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>
// CIR: cir.return
34 changes: 34 additions & 0 deletions clang/test/CIR/IR/block-adress.cir
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// RUN: cir-opt %s | cir-opt | FileCheck %s

!void = !cir.void

module {
cir.func @block_address(){
%0 = cir.blockaddress(@block_address, "label") -> !cir.ptr<!void>
cir.br ^bb1
^bb1:
cir.label "label"
cir.return
}
// CHECK: cir.func @block_address
// CHECK: %0 = cir.blockaddress(@block_address, "label") -> !cir.ptr<!void>
// CHECK: cir.br ^bb1
// CHECK: ^bb1:
// CHECK: cir.label "label"
// CHECK: cir.return

cir.func @block_address_inside_scope() -> () {
cir.scope{
%0 = cir.blockaddress(@block_address_inside_scope, "label") -> !cir.ptr<!void>
}
cir.br ^bb1
^bb1:
cir.label "label"
cir.return
}
// CHECK: cir.func @block_address_inside_scope
// CHECK: cir.scope
// CHECK: %0 = cir.blockaddress(@block_address_inside_scope, "label") -> !cir.ptr<!void>
// CHECK: cir.label "label"
// CHECK: cir.return
}
21 changes: 21 additions & 0 deletions clang/test/CIR/IR/invalid-block-address.cir
Original file line number Diff line number Diff line change
@@ -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.blockaddress(@bad_block_address, "label") -> !cir.ptr<!void>
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.blockaddress(@mismatch_func, "label") -> !cir.ptr<!void>
cir.br ^bb1
^bb1:
cir.label "label"
cir.return
}
63 changes: 63 additions & 0 deletions clang/test/CIR/Transforms/goto_solver.cir
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// RUN: cir-opt %s -cir-goto-solver -o - | FileCheck %s

!void = !cir.void

cir.func @a(){
%0 = cir.alloca !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>, ["ptr", init] {alignment = 8 : i64}
%1 = cir.blockaddress(@a, "label1") -> !cir.ptr<!void>
cir.store align(8) %1, %0 : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>
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.blockaddress(@a, "label1") -> !cir.ptr<!void>
// 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<!void>, !cir.ptr<!cir.ptr<!void>>, ["ptr", init] {alignment = 8 : i64}
%1 = cir.blockaddress(@b, "label1") -> !cir.ptr<!void>
cir.store align(8) %1, %0 : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>
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.blockaddress(@b, "label1") -> !cir.ptr<!void>
// CHECK: cir.store align(8) %1, {{.*}} : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>
// 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<!void>, !cir.ptr<!cir.ptr<!void>>, ["ptr", init] {alignment = 8 : i64}
%1 = cir.blockaddress(@c, "label1") -> !cir.ptr<!void>
cir.store align(8) %1, %0 : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>
cir.return
}

// CHECK: cir.func @c
// CHECK: cir.label "label1"
// CHECK: %1 = cir.blockaddress(@c, "label1") -> !cir.ptr<!void>
// CHECK: cir.store align(8) %1, {{.*}} : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>

Loading
Loading