Skip to content

Commit 3fcf10c

Browse files
authored
[clang][bytecode] Reapply "Use tailcalls via [[clang::musttail]]" (#188419)
1) Disable tailcalls on powerpc and MSVC 2) Disable the `preserve_none` calling convention on aarch64 and i386. For aarch64, it works but causes problems under asan: #177519
1 parent 4814a9f commit 3fcf10c

File tree

9 files changed

+306
-151
lines changed

9 files changed

+306
-151
lines changed

clang/lib/AST/ByteCode/Compiler.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2819,7 +2819,7 @@ bool Compiler<Emitter>::VisitAbstractConditionalOperator(
28192819
LabelTy LabelFalse = this->getLabel(); // Label for the false expr.
28202820

28212821
if (IsBcpCall) {
2822-
if (!this->emitStartSpeculation(E))
2822+
if (!this->emitPushIgnoreDiags(E))
28232823
return false;
28242824
}
28252825

@@ -2851,7 +2851,7 @@ bool Compiler<Emitter>::VisitAbstractConditionalOperator(
28512851
this->emitLabel(LabelEnd);
28522852

28532853
if (IsBcpCall)
2854-
return this->emitEndSpeculation(E);
2854+
return this->emitPopIgnoreDiags(E);
28552855
return true;
28562856
}
28572857

@@ -5417,9 +5417,9 @@ bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E,
54175417
LabelTy EndLabel = this->getLabel();
54185418
if (!this->speculate(E, EndLabel))
54195419
return false;
5420-
this->fallthrough(EndLabel);
54215420
if (!this->emitEndSpeculation(E))
54225421
return false;
5422+
this->fallthrough(EndLabel);
54235423
if (DiscardResult)
54245424
return this->emitPop(classifyPrim(E), E);
54255425
return true;

clang/lib/AST/ByteCode/EvalEmitter.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "IntegralAP.h"
1212
#include "Interp.h"
1313
#include "clang/AST/DeclCXX.h"
14+
#include "llvm/ADT/ScopeExit.h"
1415

1516
using namespace clang;
1617
using namespace clang::interp;
@@ -161,6 +162,10 @@ bool EvalEmitter::fallthrough(const LabelTy &Label) {
161162
bool EvalEmitter::speculate(const CallExpr *E, const LabelTy &EndLabel) {
162163
if (!isActive())
163164
return true;
165+
166+
PushIgnoreDiags(S, OpPC);
167+
auto _ = llvm::scope_exit([&]() { PopIgnoreDiags(S, OpPC); });
168+
164169
size_t StackSizeBefore = S.Stk.size();
165170
const Expr *Arg = E->getArg(0);
166171
if (!this->visit(Arg)) {

clang/lib/AST/ByteCode/Interp.cpp

Lines changed: 159 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,27 @@
2828
using namespace clang;
2929
using namespace clang::interp;
3030

31-
static bool RetValue(InterpState &S, CodePtr &Pt) {
31+
#if __has_cpp_attribute(clang::musttail)
32+
#define MUSTTAIL [[clang::musttail]]
33+
#elif __has_cpp_attribute(msvc::musttail)
34+
#define MUSTTAIL [[msvc::musttail]]
35+
#elif __has_attribute(musttail)
36+
#define MUSTTAIL __attribute__((musttail))
37+
#endif
38+
39+
// On MSVC, musttail does not guarantee tail calls in debug mode.
40+
// We disable it on MSVC generally since it doesn't seem to be able
41+
// to handle the way we use tailcalls.
42+
// PPC can't tail-call external calls, which is a problem for InterpNext.
43+
#if defined(_MSC_VER) || defined(__powerpc__) || !defined(MUSTTAIL)
44+
#undef MUSTTAIL
45+
#define MUSTTAIL
46+
#define USE_TAILCALLS 0
47+
#else
48+
#define USE_TAILCALLS 1
49+
#endif
50+
51+
PRESERVE_NONE static bool RetValue(InterpState &S, CodePtr &Ptr) {
3252
llvm::report_fatal_error("Interpreter cannot return values");
3353
}
3454

@@ -55,76 +75,6 @@ static bool Jf(InterpState &S, CodePtr &PC, int32_t Offset) {
5575
return S.noteStep(PC);
5676
}
5777

58-
// https://github.com/llvm/llvm-project/issues/102513
59-
#if defined(_MSC_VER) && !defined(__clang__) && !defined(NDEBUG)
60-
#pragma optimize("", off)
61-
#endif
62-
// FIXME: We have the large switch over all opcodes here again, and in
63-
// Interpret().
64-
static bool BCP(InterpState &S, CodePtr &RealPC, int32_t Offset, PrimType PT) {
65-
[[maybe_unused]] CodePtr PCBefore = RealPC;
66-
size_t StackSizeBefore = S.Stk.size();
67-
68-
auto SpeculativeInterp = [&S, RealPC]() -> bool {
69-
const InterpFrame *StartFrame = S.Current;
70-
CodePtr PC = RealPC;
71-
72-
for (;;) {
73-
auto Op = PC.read<Opcode>();
74-
if (Op == OP_EndSpeculation)
75-
return true;
76-
CodePtr OpPC = PC;
77-
78-
switch (Op) {
79-
#define GET_INTERP
80-
#include "Opcodes.inc"
81-
#undef GET_INTERP
82-
}
83-
}
84-
llvm_unreachable("We didn't see an EndSpeculation op?");
85-
};
86-
87-
if (SpeculativeInterp()) {
88-
if (PT == PT_Ptr) {
89-
const auto &Ptr = S.Stk.pop<Pointer>();
90-
assert(S.Stk.size() == StackSizeBefore);
91-
S.Stk.push<Integral<32, true>>(
92-
Integral<32, true>::from(CheckBCPResult(S, Ptr)));
93-
} else {
94-
// Pop the result from the stack and return success.
95-
TYPE_SWITCH(PT, S.Stk.pop<T>(););
96-
assert(S.Stk.size() == StackSizeBefore);
97-
S.Stk.push<Integral<32, true>>(Integral<32, true>::from(1));
98-
}
99-
} else {
100-
if (!S.inConstantContext())
101-
return Invalid(S, RealPC);
102-
103-
S.Stk.clearTo(StackSizeBefore);
104-
S.Stk.push<Integral<32, true>>(Integral<32, true>::from(0));
105-
}
106-
107-
// RealPC should not have been modified.
108-
assert(*RealPC == *PCBefore);
109-
110-
// Jump to end label. This is a little tricker than just RealPC += Offset
111-
// because our usual jump instructions don't have any arguments, to the offset
112-
// we get is a little too much and we need to subtract the size of the
113-
// bool and PrimType arguments again.
114-
int32_t ParamSize = align(sizeof(PrimType));
115-
assert(Offset >= ParamSize);
116-
RealPC += Offset - ParamSize;
117-
118-
[[maybe_unused]] CodePtr PCCopy = RealPC;
119-
assert(PCCopy.read<Opcode>() == OP_EndSpeculation);
120-
121-
return true;
122-
}
123-
// https://github.com/llvm/llvm-project/issues/102513
124-
#if defined(_MSC_VER) && !defined(__clang__) && !defined(NDEBUG)
125-
#pragma optimize("", on)
126-
#endif
127-
12878
static void diagnoseMissingInitializer(InterpState &S, CodePtr OpPC,
12979
const ValueDecl *VD) {
13080
const SourceInfo &E = S.Current->getSource(OpPC);
@@ -258,6 +208,9 @@ static bool CheckGlobal(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
258208

259209
namespace clang {
260210
namespace interp {
211+
PRESERVE_NONE static bool BCP(InterpState &S, CodePtr &RealPC, int32_t Offset,
212+
PrimType PT);
213+
261214
static void popArg(InterpState &S, const Expr *Arg) {
262215
PrimType Ty = S.getContext().classify(Arg).value_or(PT_Ptr);
263216
TYPE_SWITCH(Ty, S.Stk.discard<T>());
@@ -2582,38 +2535,156 @@ bool CastMemberPtrDerivedPop(InterpState &S, CodePtr OpPC, int32_t Off,
25822535
return castBackMemberPointer(S, Ptr, Off, BaseDecl);
25832536
}
25842537

2585-
// https://github.com/llvm/llvm-project/issues/102513
2586-
#if defined(_MSC_VER) && !defined(__clang__) && !defined(NDEBUG)
2587-
#pragma optimize("", off)
2538+
// FIXME: Would be nice to generate this instead of hardcoding it here.
2539+
constexpr bool OpReturns(Opcode Op) {
2540+
return Op == OP_RetVoid || Op == OP_RetValue || Op == OP_NoRet ||
2541+
Op == OP_RetSint8 || Op == OP_RetUint8 || Op == OP_RetSint16 ||
2542+
Op == OP_RetUint16 || Op == OP_RetSint32 || Op == OP_RetUint32 ||
2543+
Op == OP_RetSint64 || Op == OP_RetUint64 || Op == OP_RetIntAP ||
2544+
Op == OP_RetIntAPS || Op == OP_RetBool || Op == OP_RetFixedPoint ||
2545+
Op == OP_RetPtr || Op == OP_RetMemberPtr || Op == OP_RetFloat ||
2546+
Op == OP_EndSpeculation;
2547+
}
2548+
2549+
#if USE_TAILCALLS
2550+
PRESERVE_NONE static bool InterpNext(InterpState &S, CodePtr &PC);
25882551
#endif
2552+
2553+
// The dispatcher functions read the opcode arguments from the
2554+
// bytecode and call the implementation function.
2555+
#define GET_INTERPFN_DISPATCHERS
2556+
#include "Opcodes.inc"
2557+
#undef GET_INTERPFN_DISPATCHERS
2558+
2559+
using InterpFn = bool (*)(InterpState &, CodePtr &PC) PRESERVE_NONE;
2560+
// Array of the dispatcher functions defined above.
2561+
const InterpFn InterpFunctions[] = {
2562+
#define GET_INTERPFN_LIST
2563+
#include "Opcodes.inc"
2564+
#undef GET_INTERPFN_LIST
2565+
};
2566+
2567+
#if USE_TAILCALLS
2568+
// Read the next opcode and call the dispatcher function.
2569+
PRESERVE_NONE static bool InterpNext(InterpState &S, CodePtr &PC) {
2570+
auto Op = PC.read<Opcode>();
2571+
auto Fn = InterpFunctions[Op];
2572+
MUSTTAIL return Fn(S, PC);
2573+
}
2574+
#endif
2575+
25892576
bool Interpret(InterpState &S) {
25902577
// The current stack frame when we started Interpret().
25912578
// This is being used by the ops to determine wheter
25922579
// to return from this function and thus terminate
25932580
// interpretation.
2594-
const InterpFrame *StartFrame = S.Current;
25952581
assert(!S.Current->isRoot());
25962582
CodePtr PC = S.Current->getPC();
25972583

2598-
// Empty program.
2599-
if (!PC)
2600-
return true;
2584+
#if USE_TAILCALLS
2585+
return InterpNext(S, PC);
2586+
#else
2587+
while (true) {
2588+
auto Op = PC.read<Opcode>();
2589+
auto Fn = InterpFunctions[Op];
2590+
2591+
if (!Fn(S, PC))
2592+
return false;
2593+
if (OpReturns(Op))
2594+
break;
2595+
}
2596+
return true;
2597+
#endif
2598+
}
2599+
2600+
/// This is used to implement speculative execution via __builtin_constant_p
2601+
/// when we generate bytecode.
2602+
///
2603+
/// The setup here is that we use the same tailcall mechanism for speculative
2604+
/// evaluation that we use for the regular one.
2605+
/// Since each speculative execution ends with an EndSpeculation opcode,
2606+
/// that one does NOT call InterpNext() but simply returns true.
2607+
/// This way, we return back to this function when we see an EndSpeculation,
2608+
/// OR (of course), when we encounter an error and one of the opcodes
2609+
/// returns false.
2610+
PRESERVE_NONE static bool BCP(InterpState &S, CodePtr &RealPC, int32_t Offset,
2611+
PrimType PT) {
2612+
[[maybe_unused]] CodePtr PCBefore = RealPC;
2613+
size_t StackSizeBefore = S.Stk.size();
26012614

2602-
for (;;) {
2615+
// Speculation depth must be at least 1 here, since we must have
2616+
// passed a StartSpeculation op before.
2617+
#ifndef NDEBUG
2618+
[[maybe_unused]] unsigned DepthBefore = S.SpeculationDepth;
2619+
assert(DepthBefore >= 1);
2620+
#endif
2621+
2622+
CodePtr PC = RealPC;
2623+
auto SpeculativeInterp = [&S, &PC]() -> bool {
2624+
// Ignore diagnostics during speculative execution.
2625+
PushIgnoreDiags(S, PC);
2626+
auto _ = llvm::scope_exit([&]() { PopIgnoreDiags(S, PC); });
2627+
2628+
#if USE_TAILCALLS
26032629
auto Op = PC.read<Opcode>();
2604-
CodePtr OpPC = PC;
2630+
auto Fn = InterpFunctions[Op];
2631+
return Fn(S, PC);
2632+
#else
2633+
while (true) {
2634+
auto Op = PC.read<Opcode>();
2635+
auto Fn = InterpFunctions[Op];
26052636

2606-
switch (Op) {
2607-
#define GET_INTERP
2608-
#include "Opcodes.inc"
2609-
#undef GET_INTERP
2637+
if (!Fn(S, PC))
2638+
return false;
2639+
if (OpReturns(Op))
2640+
break;
26102641
}
2642+
return true;
2643+
#endif
2644+
};
2645+
2646+
if (SpeculativeInterp()) {
2647+
// Speculation must've ended naturally via a EndSpeculation opcode.
2648+
assert(S.SpeculationDepth == DepthBefore - 1);
2649+
if (PT == PT_Ptr) {
2650+
const auto &Ptr = S.Stk.pop<Pointer>();
2651+
assert(S.Stk.size() == StackSizeBefore);
2652+
S.Stk.push<Integral<32, true>>(
2653+
Integral<32, true>::from(CheckBCPResult(S, Ptr)));
2654+
} else {
2655+
// Pop the result from the stack and return success.
2656+
TYPE_SWITCH(PT, S.Stk.discard<T>(););
2657+
assert(S.Stk.size() == StackSizeBefore);
2658+
S.Stk.push<Integral<32, true>>(Integral<32, true>::from(1));
2659+
}
2660+
} else {
2661+
// End the speculation manually since we didn't call EndSpeculation
2662+
// naturally.
2663+
EndSpeculation(S, RealPC);
2664+
2665+
if (!S.inConstantContext())
2666+
return Invalid(S, RealPC);
2667+
2668+
S.Stk.clearTo(StackSizeBefore);
2669+
S.Stk.push<Integral<32, true>>(Integral<32, true>::from(0));
26112670
}
2671+
2672+
// RealPC should not have been modified.
2673+
assert(*RealPC == *PCBefore);
2674+
2675+
// We have already evaluated this speculation's EndSpeculation opcode.
2676+
assert(S.SpeculationDepth == DepthBefore - 1);
2677+
2678+
// Jump to end label. This is a little tricker than just RealPC += Offset
2679+
// because our usual jump instructions don't have any arguments, to the offset
2680+
// we get is a little too much and we need to subtract the size of the
2681+
// bool and PrimType arguments again.
2682+
int32_t ParamSize = align(sizeof(PrimType));
2683+
assert(Offset >= ParamSize);
2684+
RealPC += Offset - ParamSize;
2685+
2686+
return true;
26122687
}
2613-
// https://github.com/llvm/llvm-project/issues/102513
2614-
#if defined(_MSC_VER) && !defined(__clang__) && !defined(NDEBUG)
2615-
#pragma optimize("", on)
2616-
#endif
26172688

26182689
} // namespace interp
26192690
} // namespace clang

0 commit comments

Comments
 (0)