From 15efe733a223c2f0faa7c9444bdf6fa7bd36c9fa Mon Sep 17 00:00:00 2001 From: Andrey Penechko Date: Mon, 3 Apr 2023 17:39:37 +0300 Subject: [PATCH] Add pragma(musttail) --- dmd/expression.d | 4 ++++ dmd/expression.h | 3 +++ dmd/id.d | 1 + dmd/id.h | 1 + dmd/statementsem.d | 39 ++++++++++++++++++++++++++++++++++++++ gen/dpragma.d | 3 ++- gen/llvmhelpers.h | 3 ++- gen/pragma.cpp | 9 +++++++++ gen/pragma.h | 3 ++- gen/tocall.cpp | 15 ++++++++++++++- gen/toir.cpp | 2 +- tests/codegen/musttail_1.d | 14 ++++++++++++++ 12 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 tests/codegen/musttail_1.d diff --git a/dmd/expression.d b/dmd/expression.d index e0c4493ac0c..6c838598c62 100644 --- a/dmd/expression.d +++ b/dmd/expression.d @@ -5077,6 +5077,10 @@ extern (C++) final class CallExp : UnaExp bool directcall; // true if a virtual call is devirtualized bool inDebugStatement; /// true if this was in a debug statement bool ignoreAttributes; /// don't enforce attributes (e.g. call @gc function in @nogc code) +version (IN_LLVM) +{ + bool isMustTail; // If marked with pragma(musttail) +} VarDeclaration vthis2; // container for multi-context extern (D) this(const ref Loc loc, Expression e, Expressions* exps) diff --git a/dmd/expression.h b/dmd/expression.h index 89a3980b222..b46303abcde 100644 --- a/dmd/expression.h +++ b/dmd/expression.h @@ -863,6 +863,9 @@ class CallExp final : public UnaExp bool directcall; // true if a virtual call is devirtualized bool inDebugStatement; // true if this was in a debug statement bool ignoreAttributes; // don't enforce attributes (e.g. call @gc function in @nogc code) +#if IN_LLVM + bool isMustTail; // If marked with pragma(musttail) +#endif VarDeclaration *vthis2; // container for multi-context static CallExp *create(const Loc &loc, Expression *e, Expressions *exps); diff --git a/dmd/id.d b/dmd/id.d index 2b19c5ca72d..d3a0403af63 100644 --- a/dmd/id.d +++ b/dmd/id.d @@ -555,6 +555,7 @@ immutable Msgtable[] msgtable = { "LDC_global_crt_dtor" }, { "LDC_extern_weak" }, { "LDC_profile_instr" }, + { "musttail" }, // IN_LLVM: LDC-specific traits { "targetCPU" }, diff --git a/dmd/id.h b/dmd/id.h index 28255e68572..3683991fb71 100644 --- a/dmd/id.h +++ b/dmd/id.h @@ -76,6 +76,7 @@ struct Id static Identifier *LDC_inline_ir; static Identifier *LDC_extern_weak; static Identifier *LDC_profile_instr; + static Identifier *musttail; static Identifier *dcReflect; static Identifier *opencl; static Identifier *criticalenter; diff --git a/dmd/statementsem.d b/dmd/statementsem.d index ef387ac5640..b94ee91742e 100644 --- a/dmd/statementsem.d +++ b/dmd/statementsem.d @@ -2135,6 +2135,13 @@ else return setError(); } } + else if (ps.ident == Id.musttail) + { + version (IN_LLVM) + { + pragmaMustTailSemantic(ps); + } + } else if (!global.params.ignoreUnsupportedPragmas) { ps.error("unrecognized `pragma(%s)`", ps.ident.toChars()); @@ -2153,6 +2160,38 @@ else result = ps._body; } + version (IN_LLVM) + private void pragmaMustTailSemantic(PragmaStatement ps) + { + if (!ps._body) + { + ps.error("`pragma(musttail)` must be attached to a return statement"); + return setError(); + } + + auto rs = ps._body.isReturnStatement(); + if (!rs) + { + ps.error("`pragma(musttail)` must be attached to a return statement"); + return setError(); + } + + if (!rs.exp) + { + ps.error("`pragma(musttail)` must be attached to a return statement returning result of a function call"); + return setError(); + } + + auto ce = rs.exp.isCallExp(); + if (!ce) + { + ps.error("`pragma(musttail)` must be attached to a return statement returning result of a function call"); + return setError(); + } + + ce.isMustTail = true; + } + override void visit(StaticAssertStatement s) { s.sa.semantic2(sc); diff --git a/gen/dpragma.d b/gen/dpragma.d index 36c30bb988c..db69285b3be 100644 --- a/gen/dpragma.d +++ b/gen/dpragma.d @@ -44,7 +44,8 @@ extern (C++) enum LDCPragma : int { LLVMbitop_bts, LLVMbitop_vld, LLVMbitop_vst, - LLVMextern_weak + LLVMextern_weak, + LLVMmusttail, }; extern (C++) LDCPragma DtoGetPragma(Scope* sc, PragmaDeclaration decl, ref const(char)* arg1str); diff --git a/gen/llvmhelpers.h b/gen/llvmhelpers.h index 7dbacbefda6..ec0b04c6046 100644 --- a/gen/llvmhelpers.h +++ b/gen/llvmhelpers.h @@ -206,7 +206,8 @@ bool DtoLowerMagicIntrinsic(IRState *p, FuncDeclaration *fndecl, CallExp *e, /// DValue *DtoCallFunction(const Loc &loc, Type *resulttype, DValue *fnval, - Expressions *arguments, LLValue *sretPointer = nullptr); + Expressions *arguments, LLValue *sretPointer = nullptr, + bool isMustTail = false); Type *stripModifiers(Type *type, bool transitive = false); diff --git a/gen/pragma.cpp b/gen/pragma.cpp index 0702d1c7b1f..08b176e695e 100644 --- a/gen/pragma.cpp +++ b/gen/pragma.cpp @@ -331,6 +331,15 @@ LDCPragma DtoGetPragma(Scope *sc, PragmaDeclaration *decl, return LLVMprofile_instr; } + // pragma(musttail) + if (ident == Id::musttail) { + if (args && args->length > 0) { + decl->error("takes no parameters"); + fatal(); + } + return LLVMmusttail; + } + return LLVMnone; } diff --git a/gen/pragma.h b/gen/pragma.h index 4fae84e5fae..8691b3e301b 100644 --- a/gen/pragma.h +++ b/gen/pragma.h @@ -48,7 +48,8 @@ enum LDCPragma { LLVMbitop_vld, LLVMbitop_vst, LLVMextern_weak, - LLVMprofile_instr + LLVMprofile_instr, + LLVMmusttail, }; LDCPragma DtoGetPragma(Scope *sc, PragmaDeclaration *decl, const char *&arg1str); diff --git a/gen/tocall.cpp b/gen/tocall.cpp index 16da4ba086d..e2062f2e324 100644 --- a/gen/tocall.cpp +++ b/gen/tocall.cpp @@ -841,7 +841,8 @@ static LLValue *DtoCallableValue(llvm::FunctionType * ft,DValue *fn) { // FIXME: this function is a mess ! DValue *DtoCallFunction(const Loc &loc, Type *resulttype, DValue *fnval, - Expressions *arguments, LLValue *sretPointer) { + Expressions *arguments, LLValue *sretPointer, + bool isMustTail) { IF_LOG Logger::println("DtoCallFunction()"); LOG_SCOPE @@ -1065,6 +1066,18 @@ DValue *DtoCallFunction(const Loc &loc, Type *resulttype, DValue *fnval, llvm::AttrBuilder(call->getAttributes(), LLAttributeList::FunctionIndex)); #endif call->setAttributes(attrlist); + if (isMustTail) { + if (auto ci = llvm::dyn_cast(call)) { + ci->setTailCallKind(llvm::CallInst::TCK_MustTail); + } else { + if (!tf->isnothrow()) { + error(loc, "cannot perform tail-call - callee must be nothrow"); + } else { + error(loc, "cannot perform tail-call - no code like destructors or scope(exit) should run after the call"); + } + fatal(); + } + } // Special case for struct constructor calls: For temporaries, using the // this pointer value returned from the constructor instead of the alloca diff --git a/gen/toir.cpp b/gen/toir.cpp index 6a2c00052e0..805e42675ec 100644 --- a/gen/toir.cpp +++ b/gen/toir.cpp @@ -777,7 +777,7 @@ class ToElemVisitor : public Visitor { } DValue *result = - DtoCallFunction(e->loc, e->type, fnval, e->arguments, sretPointer); + DtoCallFunction(e->loc, e->type, fnval, e->arguments, sretPointer, e->isMustTail); if (delayedDtorVar) { delayedDtorVar->edtor = delayedDtorExp; diff --git a/tests/codegen/musttail_1.d b/tests/codegen/musttail_1.d new file mode 100644 index 00000000000..b28fe2c0e98 --- /dev/null +++ b/tests/codegen/musttail_1.d @@ -0,0 +1,14 @@ +// Tests successfull musttail application + +// RUN: %ldc -output-ll -of=%t.ll %s && FileCheck %s < %t.ll + +// CHECK-LABEL: define{{.*}} @{{.*}}foo +int foo(int x) nothrow +{ + // CHECK: musttail call{{.*}} @{{.*}}bar + // CHECK-NEXT: ret i32 + pragma(musttail) return bar(x); +} + +// CHECK-LABEL: define{{.*}} @{{.*}}bar +int bar(int x) nothrow { return x; }