Skip to content
Open
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
1 change: 1 addition & 0 deletions clang/include/clang/CIR/Dialect/IR/CIRDialect.td
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def CIR_Dialect : Dialect {
static llvm::StringRef getModuleLevelAsmAttrName() { return "cir.module_asm"; }
static llvm::StringRef getGlobalCtorsAttrName() { return "cir.global_ctors"; }
static llvm::StringRef getGlobalDtorsAttrName() { return "cir.global_dtors"; }
static llvm::StringRef getExceptionAttrName() { return "exception"; }
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't really like this name. I think "mayThrow" would be better, and it aligns with LLVM IR function attribute flag of the same name.

Copy link
Member

Choose a reason for hiding this comment

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

Yea, the existing name might not convey much more information indeed!


void registerAttributes();
void registerTypes();
Expand Down
16 changes: 14 additions & 2 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -2685,24 +2685,36 @@ def CIR_CallOp : CIR_CallOpBase<"call", [NoRegionArguments]> {
empty. The first operand of this operation must be a pointer to the callee
function. The rest operands are arguments to the callee function.

If the `cir.call` has the `exception` keyword, the call can throw.

Example:

```mlir
// Direct call
%0 = cir.call @foo()

// Indirect call
%20 = cir.call %18(%17)

// Call that might throw
cir.call exception @foo_that_might_throw() -> ()
```
}];

let results = (outs Optional<CIR_AnyType>:$result);
let arguments = commonArgs;
let arguments = !con((ins UnitAttr:$exception), commonArgs);

let builders = [
OpBuilder<(ins "mlir::SymbolRefAttr":$callee, "mlir::Type":$resType,
"mlir::ValueRange":$operands), [{
"mlir::ValueRange":$operands,
CArg<"mlir::UnitAttr", "{}">:$exception), [{
$_state.addOperands(operands);
if (callee)
$_state.addAttribute("callee", callee);
if (resType && !isa<VoidType>(resType))
$_state.addTypes(resType);
if (exception)
$_state.addAttribute("exception", exception);
}]>
];
}
Expand Down
33 changes: 24 additions & 9 deletions clang/lib/CIR/Dialect/IR/CIRDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,12 @@ static mlir::ParseResult parseCallCommon(mlir::OpAsmParser &parser,
mlir::FlatSymbolRefAttr calleeAttr;
llvm::ArrayRef<mlir::Type> allResultTypes;

bool hasExceptions = false;
if (mlir::succeeded(parser.parseOptionalKeyword("exception"))) {
result.addAttribute("exception", parser.getBuilder().getUnitAttr());
hasExceptions = true;
}

// If we cannot parse a string callee, it means this is an indirect call.
if (!parser
.parseOptionalAttribute(calleeAttr, CIRDialect::getCalleeAttrName(),
Expand All @@ -729,9 +735,15 @@ static mlir::ParseResult parseCallCommon(mlir::OpAsmParser &parser,
if (parser.parseRParen())
return mlir::failure();

if (parser.parseOptionalKeyword("nothrow").succeeded())
llvm::SMLoc optionalNothrowLoc = parser.getCurrentLocation();
if (parser.parseOptionalKeyword("nothrow").succeeded()) {
if (hasExceptions)
return parser.emitError(
optionalNothrowLoc,
"should have either `exception` or `nothrow`, but not both");
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not clear to me why we need both of these. Can we not just assume that the absence of 'nothrow' means 'maythrow'?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think yes, this attribute was used as a marker to mark CallOp as invoked, and in TryOp, we convert it to TryCallOp, but I think we can use the absence of nothrow as a condition, the only difference will be the IR

In the incubator

cir.try {
   %0 = cir.call exception @foo() : () -> !s32i
   cir.yield
}

to

cir.try {
   %0 = cir.call @foo() : () -> !s32i
   cir.yield
}

Copy link
Member

Choose a reason for hiding this comment

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

(a) One tradeoff here is having to getParentTypeOf<T> every-time you want to find out whether this call is an "invoke". (b) if you are calling a nothrow function decl inside a try block, how do you differentiate between them at lowering time?

Copy link
Member Author

Choose a reason for hiding this comment

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

(b) if you are calling a nothrow function decl inside a try block, how do you differentiate between them at lowering time?

In FlattenCGF, we convert a call with !nothrow or hasException to TryCallOp, so at the lowering we have 2 kinds of calls: CallOp, TryCallOp 🤔

Copy link
Member

Choose a reason for hiding this comment

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

This is expected (given those are are the LLVM equivalents of call/invoke - I agree the existing names are a bit convoluted), but my point is that if the call is not tagged prior to flatten, you don't know which one to lower to during flattening, because relying on the surrounding cir.try won't be enough.

result.addAttribute(CIRDialect::getNoThrowAttrName(),
mlir::UnitAttr::get(parser.getContext()));
}

if (parser.parseOptionalKeyword("side_effect").succeeded()) {
if (parser.parseLParen().failed())
Expand Down Expand Up @@ -767,13 +779,16 @@ static mlir::ParseResult parseCallCommon(mlir::OpAsmParser &parser,
static void printCallCommon(mlir::Operation *op,
mlir::FlatSymbolRefAttr calleeSym,
mlir::Value indirectCallee,
mlir::OpAsmPrinter &printer, bool isNothrow,
cir::SideEffect sideEffect) {
mlir::OpAsmPrinter &printer, bool exception,
bool isNothrow, cir::SideEffect sideEffect) {
printer << ' ';

auto callLikeOp = mlir::cast<cir::CIRCallOpInterface>(op);
auto ops = callLikeOp.getArgOperands();

if (exception)
printer << "exception ";

if (calleeSym) {
// Direct calls
printer.printAttributeWithoutType(calleeSym);
Expand All @@ -793,10 +808,10 @@ static void printCallCommon(mlir::Operation *op,
printer << ")";
}

printer.printOptionalAttrDict(op->getAttrs(),
{CIRDialect::getCalleeAttrName(),
CIRDialect::getNoThrowAttrName(),
CIRDialect::getSideEffectAttrName()});
llvm::SmallVector<::llvm::StringRef, 4> elidedAttrs = {
CIRDialect::getCalleeAttrName(), CIRDialect::getNoThrowAttrName(),
CIRDialect::getSideEffectAttrName(), CIRDialect::getExceptionAttrName()};
printer.printOptionalAttrDict(op->getAttrs(), elidedAttrs);

printer << " : ";
printer.printFunctionalType(op->getOperands().getTypes(),
Expand All @@ -811,8 +826,8 @@ mlir::ParseResult cir::CallOp::parse(mlir::OpAsmParser &parser,
void cir::CallOp::print(mlir::OpAsmPrinter &p) {
mlir::Value indirectCallee = isIndirect() ? getIndirectCall() : nullptr;
cir::SideEffect sideEffect = getSideEffect();
printCallCommon(*this, getCalleeAttr(), indirectCallee, p, getNothrow(),
sideEffect);
printCallCommon(*this, getCalleeAttr(), indirectCallee, p, getException(),
getNothrow(), sideEffect);
}

static LogicalResult
Expand Down
28 changes: 28 additions & 0 deletions clang/test/CIR/IR/call.cir
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,32 @@ cir.func @f7(%arg0: !cir.ptr<!cir.func<(!s32i, !s32i) -> !s32i>>) -> !s32i {
// CHECK-NEXT: cir.return %[[#ret]] : !s32i
// CHECK-NEXT: }

cir.func private @division() -> !s32i

cir.func dso_local @call_with_exception_attribute() {
cir.scope {
cir.try {
%0 = cir.call exception @division() : () -> !s32i
cir.yield
} catch all {
cir.yield
}
}
cir.return
}

// CHECK: cir.func private @division() -> !s32i

// CHECK: cir.func dso_local @call_with_exception_attribute() {
// CHECK-NEXT: cir.scope {
// CHECK-NEXT: cir.try {
// CHECK-NEXT: %[[CALL:.*]] = cir.call exception @division() : () -> !s32i
// CHECK-NEXT: cir.yield
// CHECK-NEXT: } catch all {
// CHECK-NEXT: cir.yield
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: cir.return
// CHECK-NEXT: }

}
19 changes: 19 additions & 0 deletions clang/test/CIR/IR/invalid-call.cir
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,22 @@ cir.func @f13() {
cir.call @f12(%0) : (!s32i) -> ()
cir.return
}

// -----

!s32i = !cir.int<s, 32>

cir.func private @division() -> !s32i

cir.func dso_local @calling_division_inside_try_block() {
cir.scope {
cir.try {
// expected-error @below {{should have either `exception` or `nothrow`, but not both}}
%0 = cir.call exception @division() nothrow : () -> !s32i
cir.yield
} catch all {
cir.yield
}
}
cir.return
}