Skip to content

Commit 1dc0f01

Browse files
committed
[interop] add initial support for trapping on uncaught exceptions when making a foreign call
1 parent 0d624ec commit 1dc0f01

12 files changed

+475
-16
lines changed

lib/IRGen/CallEmission.h

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,25 @@ class CallEmission {
6363
/// RemainingArgsForCallee, at least between calls.
6464
bool EmittedCall;
6565

66+
/// The basic block to which the call to a potentially throwing foreign
67+
/// function should jump to continue normal execution of the program.
68+
llvm::BasicBlock *invokeNormalDest = nullptr;
69+
70+
/// The basic block to which the call to a potentially throwing foreign
71+
/// function should jump to in case an exception has been thrown during the
72+
/// invocation of the call.
73+
llvm::BasicBlock *invokeUnwindDest = nullptr;
74+
6675
virtual void setFromCallee();
6776
void emitToUnmappedMemory(Address addr);
6877
void emitToUnmappedExplosion(Explosion &out);
69-
virtual void emitCallToUnmappedExplosion(llvm::CallInst *call, Explosion &out) = 0;
78+
virtual void emitCallToUnmappedExplosion(llvm::CallBase *call,
79+
Explosion &out) = 0;
7080
void emitYieldsToExplosion(Explosion &out);
7181
virtual FunctionPointer getCalleeFunctionPointer() = 0;
72-
llvm::CallInst *emitCallSite();
82+
llvm::CallBase *emitCallSite();
7383

74-
virtual llvm::CallInst *createCall(const FunctionPointer &fn,
84+
virtual llvm::CallBase *createCall(const FunctionPointer &fn,
7585
ArrayRef<llvm::Value *> args) = 0;
7686

7787
CallEmission(IRGenFunction &IGF, llvm::Value *selfValue, Callee &&callee)
@@ -105,7 +115,7 @@ class CallEmission {
105115
bool isOutlined);
106116
void emitToExplosion(Explosion &out, bool isOutlined);
107117

108-
llvm::CallInst *emitCoroutineAsOrdinaryFunction() {
118+
llvm::CallBase *emitCoroutineAsOrdinaryFunction() {
109119
assert(IsCoroutine);
110120
IsCoroutine = false;
111121

lib/IRGen/Callee.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,10 @@ namespace irgen {
331331
llvm::Type *awaitSignature = nullptr;
332332
bool useSignature = false;
333333

334+
// True when this function pointer points to a non-throwing foreign
335+
// function.
336+
bool isForeignNoThrow = false;
337+
334338
explicit FunctionPointer(Kind kind, llvm::Value *value,
335339
const Signature &signature)
336340
: FunctionPointer(kind, value, PointerAuthInfo(), signature) {}
@@ -491,6 +495,12 @@ namespace irgen {
491495
bool shouldSuppressPolymorphicArguments() const {
492496
return kind.shouldSuppressPolymorphicArguments();
493497
}
498+
499+
void setForeignNoThrow() { isForeignNoThrow = true; }
500+
501+
bool canThrowForeignException() const {
502+
return getForeignInfo().canThrow && !isForeignNoThrow;
503+
}
494504
};
495505

496506
class Callee {

lib/IRGen/GenCall.cpp

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1982,6 +1982,17 @@ Signature SignatureExpansion::getSignature() {
19821982
result.Attributes = Attrs;
19831983
using ExtraData = Signature::ExtraData;
19841984
if (FnType->getLanguage() == SILFunctionLanguage::C) {
1985+
const auto &clangLangOpts =
1986+
IGM.Context.getClangModuleLoader()->getClangASTContext().getLangOpts();
1987+
bool exceptionHandlingEnabled = IGM.Context.LangOpts.EnableCXXInterop &&
1988+
clangLangOpts.Exceptions &&
1989+
!clangLangOpts.IgnoreExceptions;
1990+
// FIXME: Support exceptions on windows MSVC.
1991+
if (IGM.Triple.isWindowsMSVCEnvironment())
1992+
exceptionHandlingEnabled = false;
1993+
// This is a potentially throwing function. The use of 'noexcept' /
1994+
// 'nothrow' is applied at the call site in the \c FunctionPointer class.
1995+
ForeignInfo.canThrow = exceptionHandlingEnabled;
19851996
result.ExtraDataKind = ExtraData::kindForMember<ForeignFunctionInfo>();
19861997
result.ExtraDataStorage.emplace<ForeignFunctionInfo>(result.ExtraDataKind,
19871998
ForeignInfo);
@@ -2227,9 +2238,10 @@ class SyncCallEmission final : public CallEmission {
22272238
index, IGF.IGM.getMaximalTypeExpansionContext());
22282239
}
22292240

2230-
llvm::CallInst *createCall(const FunctionPointer &fn,
2241+
llvm::CallBase *createCall(const FunctionPointer &fn,
22312242
ArrayRef<llvm::Value *> args) override {
2232-
return IGF.Builder.CreateCall(fn, Args);
2243+
return IGF.Builder.CreateCallOrInvoke(fn, Args, invokeNormalDest,
2244+
invokeUnwindDest);
22332245
}
22342246

22352247
void begin() override { super::begin(); }
@@ -2375,7 +2387,8 @@ class SyncCallEmission final : public CallEmission {
23752387
}
23762388
super::setArgs(adjusted, isOutlined, witnessMetadata);
23772389
}
2378-
void emitCallToUnmappedExplosion(llvm::CallInst *call, Explosion &out) override {
2390+
void emitCallToUnmappedExplosion(llvm::CallBase *call,
2391+
Explosion &out) override {
23792392
// Bail out immediately on a void result.
23802393
llvm::Value *result = call;
23812394
if (result->getType()->isVoidTy())
@@ -2678,7 +2691,8 @@ class AsyncCallEmission final : public CallEmission {
26782691
saveValue(resumeParentField, fnVal, isOutlined);
26792692
}
26802693
}
2681-
void emitCallToUnmappedExplosion(llvm::CallInst *call, Explosion &out) override {
2694+
void emitCallToUnmappedExplosion(llvm::CallBase *call,
2695+
Explosion &out) override {
26822696
// Bail out on a void result type.
26832697
auto &IGM = IGF.IGM;
26842698
llvm::Value *result = call;
@@ -2770,7 +2784,7 @@ class AsyncCallEmission final : public CallEmission {
27702784
return IGF.getCalleeErrorResultSlot(errorType);
27712785
}
27722786

2773-
llvm::CallInst *createCall(const FunctionPointer &fn,
2787+
llvm::CallBase *createCall(const FunctionPointer &fn,
27742788
ArrayRef<llvm::Value *> args) override {
27752789
auto &IGM = IGF.IGM;
27762790
auto &Builder = IGF.Builder;
@@ -2900,7 +2914,7 @@ void CallEmission::emitToUnmappedMemory(Address result) {
29002914
}
29012915

29022916
/// The private routine to ultimately emit a call or invoke instruction.
2903-
llvm::CallInst *CallEmission::emitCallSite() {
2917+
llvm::CallBase *CallEmission::emitCallSite() {
29042918
assert(state == State::Emitting);
29052919
assert(LastArgWritten == 0);
29062920
assert(!EmittedCall);
@@ -2921,8 +2935,13 @@ llvm::CallInst *CallEmission::emitCallSite() {
29212935
Args[i] = IGF.coerceValue(Args[i], paramTy, IGF.IGM.DataLayout);
29222936
}
29232937

2924-
// TODO: exceptions!
2938+
if (fn.canThrowForeignException()) {
2939+
invokeNormalDest = IGF.createBasicBlock("invoke.cont");
2940+
invokeUnwindDest = IGF.createExceptionUnwindBlock();
2941+
}
29252942
auto call = createCall(fn, Args);
2943+
if (invokeNormalDest)
2944+
IGF.Builder.emitBlock(invokeNormalDest);
29262945

29272946
// Make coroutines calls opaque to LLVM analysis.
29282947
if (IsCoroutine) {
@@ -2984,8 +3003,9 @@ assertTypesInByValAndStructRetAttributes(llvm::FunctionType *fnType,
29843003
return attrList;
29853004
}
29863005

2987-
llvm::CallInst *IRBuilder::CreateCall(const FunctionPointer &fn,
2988-
ArrayRef<llvm::Value*> args) {
3006+
llvm::CallBase *IRBuilder::CreateCallOrInvoke(
3007+
const FunctionPointer &fn, ArrayRef<llvm::Value *> args,
3008+
llvm::BasicBlock *invokeNormalDest, llvm::BasicBlock *invokeUnwindDest) {
29893009
assert(fn.getKind() == FunctionPointer::Kind::Function);
29903010
SmallVector<llvm::OperandBundleDef, 1> bundles;
29913011

@@ -2999,8 +3019,13 @@ llvm::CallInst *IRBuilder::CreateCall(const FunctionPointer &fn,
29993019

30003020
assert(!isTrapIntrinsic(fn.getRawPointer()) && "Use CreateNonMergeableTrap");
30013021
auto fnTy = cast<llvm::FunctionType>(fn.getFunctionType());
3002-
llvm::CallInst *call =
3003-
IRBuilderBase::CreateCall(fnTy, fn.getRawPointer(), args, bundles);
3022+
llvm::CallBase *call;
3023+
if (!fn.canThrowForeignException())
3024+
call = IRBuilderBase::CreateCall(fnTy, fn.getRawPointer(), args, bundles);
3025+
else
3026+
call =
3027+
IRBuilderBase::CreateInvoke(fnTy, fn.getRawPointer(), invokeNormalDest,
3028+
invokeUnwindDest, args, bundles);
30043029

30053030
llvm::AttributeList attrs = fn.getAttributes();
30063031
// If a parameter of a function is SRet, the corresponding argument should be
@@ -3024,6 +3049,13 @@ llvm::CallInst *IRBuilder::CreateCall(const FunctionPointer &fn,
30243049
return call;
30253050
}
30263051

3052+
llvm::CallInst *IRBuilder::CreateCall(const FunctionPointer &fn,
3053+
ArrayRef<llvm::Value *> args) {
3054+
assert(!fn.canThrowForeignException());
3055+
return cast<llvm::CallInst>(CreateCallOrInvoke(
3056+
fn, args, /*invokeNormalDest=*/nullptr, /*invokeUnwindDest=*/nullptr));
3057+
}
3058+
30273059
/// Emit the result of this call to memory.
30283060
void CallEmission::emitToMemory(Address addr,
30293061
const LoadableTypeInfo &indirectedResultTI,
@@ -4416,6 +4448,31 @@ void IRGenFunction::emitBBForReturn() {
44164448
CurFn->getBasicBlockList().push_back(ReturnBB);
44174449
}
44184450

4451+
llvm::BasicBlock *IRGenFunction::createExceptionUnwindBlock() {
4452+
auto *result = createBasicBlock("exception.unwind");
4453+
IRBuilder::SavedInsertionPointRAII insertRAII(Builder, result);
4454+
4455+
// Create a catch-all landing pad.
4456+
// FIXME: Refactor Clang/lib/CodeGen to call into Clang here.
4457+
// FIXME: MSVC support.
4458+
llvm::LandingPadInst *lpad = Builder.CreateLandingPad(
4459+
llvm::StructType::get(IGM.Int8PtrTy, IGM.Int32Ty), 0);
4460+
lpad->addClause(llvm::ConstantPointerNull::get(IGM.Int8PtrTy));
4461+
4462+
// The trap with a message informs the user that the exception hasn't
4463+
// been caught. The trap creates a new debug inline frame for the message,
4464+
// so ensure that the current debug location is preserved.
4465+
auto oldDebugLoc = Builder.getCurrentDebugLocation();
4466+
emitTrap(IGM.Context.LangOpts.EnableObjCInterop
4467+
? "unhandled C++ / Objective-C exception"
4468+
: "unhandled C++ exception",
4469+
/*Unreachable=*/true);
4470+
Builder.SetCurrentDebugLocation(oldDebugLoc);
4471+
4472+
ExceptionUnwindBlocks.push_back(result);
4473+
return result;
4474+
}
4475+
44194476
/// Emit the prologue for the function.
44204477
void IRGenFunction::emitPrologue() {
44214478
// Set up the IRBuilder.
@@ -4466,12 +4523,31 @@ bool IRGenFunction::emitBranchToReturnBB() {
44664523
return true;
44674524
}
44684525

4526+
llvm::Function *IRGenModule::getForeignExceptionHandlingPersonalityFunc() {
4527+
if (foreignExceptionHandlingPersonalityFunc)
4528+
return foreignExceptionHandlingPersonalityFunc;
4529+
foreignExceptionHandlingPersonalityFunc = llvm::Function::Create(
4530+
llvm::FunctionType::get(Int32Ty, true), llvm::Function::ExternalLinkage,
4531+
"__gxx_personality_v0", getModule());
4532+
return foreignExceptionHandlingPersonalityFunc;
4533+
}
4534+
44694535
/// Emit the epilogue for the function.
44704536
void IRGenFunction::emitEpilogue() {
44714537
if (EarliestIP != AllocaIP)
44724538
EarliestIP->eraseFromParent();
44734539
// Destroy the alloca insertion point.
44744540
AllocaIP->eraseFromParent();
4541+
// Add exception unwind blocks and additional exception handling info
4542+
// if needed.
4543+
if (!ExceptionUnwindBlocks.empty()) {
4544+
// The function should have an unwind table when catching exceptions.
4545+
CurFn->addFnAttr(llvm::Attribute::getWithUWTableKind(
4546+
*IGM.LLVMContext, llvm::UWTableKind::Default));
4547+
CurFn->setPersonalityFn(IGM.getForeignExceptionHandlingPersonalityFunc());
4548+
for (auto *bb : ExceptionUnwindBlocks)
4549+
CurFn->getBasicBlockList().push_back(bb);
4550+
}
44754551
}
44764552

44774553
std::pair<Address, Size>

lib/IRGen/IRBuilder.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,11 @@ class IRBuilder : public IRBuilderBase {
337337
return Call;
338338
}
339339

340+
llvm::CallBase *CreateCallOrInvoke(const FunctionPointer &fn,
341+
ArrayRef<llvm::Value *> args,
342+
llvm::BasicBlock *invokeNormalDest,
343+
llvm::BasicBlock *invokeUnwindDest);
344+
340345
llvm::CallInst *CreateCall(const FunctionPointer &fn,
341346
ArrayRef<llvm::Value *> args);
342347

lib/IRGen/IRGenFunction.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ class IRGenFunction {
103103
void emitBBForReturn();
104104
bool emitBranchToReturnBB();
105105

106+
llvm::BasicBlock *createExceptionUnwindBlock();
107+
106108
void emitAllExtractValues(llvm::Value *aggValue, llvm::StructType *type,
107109
Explosion &out);
108110

@@ -196,6 +198,10 @@ class IRGenFunction {
196198
/// The unique block that calls @llvm.coro.end.
197199
llvm::BasicBlock *CoroutineExitBlock = nullptr;
198200

201+
/// The blocks that handle thrown exceptions from all throwing foreign calls
202+
/// in this function.
203+
llvm::SmallVector<llvm::BasicBlock *, 4> ExceptionUnwindBlocks;
204+
199205
public:
200206
void emitCoroutineOrAsyncExit();
201207

lib/IRGen/IRGenModule.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -955,6 +955,10 @@ class IRGenModule {
955955
// emitted.
956956
llvm::DenseMap<llvm::GlobalVariable*, llvm::CallInst*> AsyncCoroIDsForPadding;
957957

958+
/// The personality function used for foreign exception handling in this
959+
/// module.
960+
llvm::Function *foreignExceptionHandlingPersonalityFunc = nullptr;
961+
958962

959963
//--- Types -----------------------------------------------------------------
960964
public:
@@ -1801,6 +1805,8 @@ private: \
18011805
/// run on the SIL module that owns this function.
18021806
void lowerSILFunction(SILFunction *f);
18031807

1808+
llvm::Function *getForeignExceptionHandlingPersonalityFunc();
1809+
18041810
private:
18051811
llvm::Constant *
18061812
getAddrOfSharedContextDescriptor(LinkEntity entity,

lib/IRGen/IRGenSIL.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2731,7 +2731,15 @@ void IRGenSILFunction::visitFunctionRefBaseInst(FunctionRefBaseInst *i) {
27312731
}
27322732
FunctionPointer fp =
27332733
FunctionPointer::forDirect(fpKind, value, secondaryValue, sig);
2734-
2734+
// Update the foreign no-throw information if needed.
2735+
if (const auto *cd = fn->getClangDecl()) {
2736+
if (auto *cfd = dyn_cast<clang::FunctionDecl>(cd)) {
2737+
if (auto *cft = cfd->getType()->getAs<clang::FunctionProtoType>()) {
2738+
if (cft->isNothrow())
2739+
fp.setForeignNoThrow();
2740+
}
2741+
}
2742+
}
27352743
// Store the function as a FunctionPointer so we can avoid bitcasting
27362744
// or thunking if we don't need to.
27372745
setLoweredFunctionPointer(i, fp);

lib/IRGen/Signature.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ class TypeInfo;
5353
class ForeignFunctionInfo {
5454
public:
5555
const clang::CodeGen::CGFunctionInfo *ClangInfo = nullptr;
56+
/// True if the foreign function can throw an Objective-C / C++ exception.
57+
bool canThrow = false;
5658
};
5759

5860
/// An encapsulation of the extra lowering information we might want to
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: split-file %S/trap-on-exception-irgen-itanium.swift %t
3+
4+
// RUN: %target-swift-emit-ir %t/test.swift -I %t -enable-experimental-cxx-interop -Xcc -fignore-exceptions | %FileCheck %s
5+
6+
// note: The test sources are in 'trap-on-exception-irgen-itanium.swift'
7+
8+
// CHECK: define {{.*}} @"$s4test0A23FreeFunctionNoThrowOnlys5Int32VyF"() #[[#SWIFTMETA:]] {
9+
// CHECK-NEXT: :
10+
// CHECK-NEXT: call swiftcc i32 @"$s4test8makeCInts5Int32VyF"()
11+
// CHECK-NEXT: call i32 @{{_Z19freeFunctionNoThrowi|"\?freeFunctionNoThrow@@YAHH@Z"}}(i32 {{.*}})
12+
// CHECK-NEXT: ret i32
13+
// CHECK-NEXT: }
14+
15+
// CHECK: define {{.*}} @"$s4test0A17FreeFunctionCallss5Int32VyF"() #[[#SWIFTMETA]] {
16+
// CHECK: call i32 @{{_Z18freeFunctionThrowsi|"\?freeFunctionThrows@@YAHH@Z"}}(i32 0)
17+
// CHECK: call i32 @{{_Z19freeFunctionNoThrowi|"\?freeFunctionNoThrow@@YAHH@Z"}}(i32 1)
18+
// CHECK: call swiftcc i32 @"$s4test8makeCInts5Int32VyF"()
19+
// CHECK: call i32 @{{_Z18freeFunctionThrowsi|"\?freeFunctionThrows@@YAHH@Z"}}(i32
20+
// CHECK: ret i32
21+
// CHECK-NEXT: }

0 commit comments

Comments
 (0)