Propagate "nothrow" to LLVM IR (#1202)

Invoke nothrow callees only in try-blocks with at least 1 catch-block,
otherwise call them directly.

Errors thrown by nothrow callees can thus still be caught inside a
try-catch-statement (and this is apparently required for release builds
too).

Most calls will be direct calls though, and this small change will lead
to substantially less IR as we then skip the clean-ups after an Error.
Proper clean-up when unwinding after an Error seems not to be guaranteed
anyway. There are apparent RAII front-end optimizations for structs with
nothrow dtor - see PR #1656.
This commit is contained in:
Martin 2016-07-29 20:23:02 +02:00
parent 798cda0649
commit f4a22f232b
5 changed files with 97 additions and 5 deletions

View file

@ -375,6 +375,20 @@ void ScopeStack::popCatch() {
} }
} }
bool ScopeStack::hasCatches() const {
if (useMSVCEH()) {
#if LDC_LLVM_VER >= 308
for (const auto &c : cleanupScopes) {
if (isCatchSwitchBlock(c.beginBlock))
return true;
}
#endif
return false;
}
return !catchScopes.empty();
}
void ScopeStack::pushLoopTarget(Statement *loopStatement, void ScopeStack::pushLoopTarget(Statement *loopStatement,
llvm::BasicBlock *continueTarget, llvm::BasicBlock *continueTarget,
llvm::BasicBlock *breakTarget) { llvm::BasicBlock *breakTarget) {

View file

@ -260,6 +260,9 @@ public:
/// Unregisters the last registered catch block. /// Unregisters the last registered catch block.
void popCatch(); void popCatch();
/// Indicates whether there are any registered catch blocks.
bool hasCatches() const;
size_t currentCatchScope() { return catchScopes.size(); } size_t currentCatchScope() { return catchScopes.size(); }
#if LDC_LLVM_VER >= 308 #if LDC_LLVM_VER >= 308
@ -305,7 +308,7 @@ public:
/// are catches/cleanups active or not. /// are catches/cleanups active or not.
template <typename T> template <typename T>
llvm::CallSite callOrInvoke(llvm::Value *callee, const T &args, llvm::CallSite callOrInvoke(llvm::Value *callee, const T &args,
const char *name = ""); const char *name = "", bool isNothrow = false);
/// Terminates the current basic block with an unconditional branch to the /// Terminates the current basic block with an unconditional branch to the
/// given label, along with the cleanups to execute on the way there. /// given label, along with the cleanups to execute on the way there.
@ -406,14 +409,20 @@ private:
template <typename T> template <typename T>
llvm::CallSite ScopeStack::callOrInvoke(llvm::Value *callee, const T &args, llvm::CallSite ScopeStack::callOrInvoke(llvm::Value *callee, const T &args,
const char *name) { const char *name, bool isNothrow) {
// If this is a direct call, we might be able to use the callee attributes // If this is a direct call, we might be able to use the callee attributes
// to our advantage. // to our advantage.
llvm::Function *calleeFn = llvm::dyn_cast<llvm::Function>(callee); llvm::Function *calleeFn = llvm::dyn_cast<llvm::Function>(callee);
// Ignore 'nothrow' inside try-blocks with at least 1 catch block to allow
// catching Errors.
if (isNothrow)
isNothrow = !hasCatches();
// Intrinsics don't support invoking and 'nounwind' functions don't need it. // Intrinsics don't support invoking and 'nounwind' functions don't need it.
const bool doesNotThrow = const bool doesNotThrow =
calleeFn && (calleeFn->isIntrinsic() || calleeFn->doesNotThrow()); isNothrow ||
(calleeFn && (calleeFn->isIntrinsic() || calleeFn->doesNotThrow()));
#if LDC_LLVM_VER >= 308 #if LDC_LLVM_VER >= 308
// calls inside a funclet must be annotated with its value // calls inside a funclet must be annotated with its value

View file

@ -886,7 +886,8 @@ DValue *DtoCallFunction(Loc &loc, Type *resulttype, DValue *fnval,
} }
// call the function // call the function
LLCallSite call = gIR->funcGen().scopes.callOrInvoke(callable, args); LLCallSite call =
gIR->funcGen().scopes.callOrInvoke(callable, args, "", tf->isnothrow);
#if LDC_LLVM_VER >= 309 #if LDC_LLVM_VER >= 309
// PGO: Insert instrumentation or attach profile metadata at indirect call // PGO: Insert instrumentation or attach profile metadata at indirect call

View file

@ -14,7 +14,6 @@
#include "gen/tollvm.h" #include "gen/tollvm.h"
#include "ir/irdsymbol.h" #include "ir/irdsymbol.h"
IrFunction::IrFunction(FuncDeclaration *fd) : FMF() { IrFunction::IrFunction(FuncDeclaration *fd) : FMF() {
decl = fd; decl = fd;

69
tests/codegen/nothrow.d Normal file
View file

@ -0,0 +1,69 @@
// RUN: %ldc -c -output-ll -of=%t.ll %s && FileCheck %s < %t.ll
struct S
{
~this() nothrow {}
void foo() nothrow { throw new Error("foo"); }
}
struct Throwing
{
~this() {}
void bar() { throw new Exception("bar"); }
}
// CHECK-LABEL: define{{.*}} @{{.*}}_D7nothrow10inTryCatchFZv
void inTryCatch()
{
try
{
// make sure the nothrow functions S.foo() and S.~this()
// are invoked in try-blocks with at least 1 catch block
S a;
// CHECK: invoke {{.*}}_D7nothrow1S3fooMFNbZv{{.*}} %a
a.foo();
// CHECK: invoke {{.*}}_D7nothrow1S6__dtorMFNbZv{{.*}} %a
}
catch (Error) {}
}
// CHECK-LABEL: define{{.*}} @{{.*}}_D7nothrow12inTryFinallyFZv
void inTryFinally()
{
// make sure the nothrow functions are never invoked
// CHECK-NOT: invoke {{.*}}_D7nothrow1S3fooMFNbZv
// CHECK-NOT: invoke {{.*}}_D7nothrow1S6__dtorMFNbZv
try
{
S a;
a.foo();
}
finally
{
S b;
b.foo();
}
}
// CHECK-LABEL: define{{.*}} @{{.*}}_Dmain
void main()
{
// make sure the nothrow functions are never invoked
// CHECK-NOT: invoke {{.*}}_D7nothrow1S3fooMFNbZv
// CHECK-NOT: invoke {{.*}}_D7nothrow1S6__dtorMFNbZv
Throwing t;
S a;
a.foo();
t.bar();
{
S b;
t.bar();
b.foo();
S().foo();
}
}