[ctxprof] Handle instrumenting functions with musttail
calls (#135121)
Functions with `musttail` calls can't be roots because we can't instrument their `ret` to release the context. This patch tags their `CtxRoot` field in their `FunctionData`. In compiler-rt we then know not to allow such functions become roots, and also not confuse `CtxRoot == 0x1` with there being a context root. Currently we also lose the context tree under such cases. We can, in a subsequent patch, have the root detector search past these functions.
This commit is contained in:
parent
e071233fa5
commit
e7aed23d32
@ -125,10 +125,11 @@ public:
|
|||||||
/// VOLATILE_PTRDECL is the same as above, but for volatile pointers;
|
/// VOLATILE_PTRDECL is the same as above, but for volatile pointers;
|
||||||
///
|
///
|
||||||
/// MUTEXDECL takes one parameter, the name of a field that is a mutex.
|
/// MUTEXDECL takes one parameter, the name of a field that is a mutex.
|
||||||
#define CTXPROF_FUNCTION_DATA(PTRDECL, VOLATILE_PTRDECL, MUTEXDECL) \
|
#define CTXPROF_FUNCTION_DATA(PTRDECL, CONTEXT_PTR, VOLATILE_PTRDECL, \
|
||||||
|
MUTEXDECL) \
|
||||||
PTRDECL(FunctionData, Next) \
|
PTRDECL(FunctionData, Next) \
|
||||||
VOLATILE_PTRDECL(void, EntryAddress) \
|
VOLATILE_PTRDECL(void, EntryAddress) \
|
||||||
VOLATILE_PTRDECL(ContextRoot, CtxRoot) \
|
CONTEXT_PTR \
|
||||||
VOLATILE_PTRDECL(ContextNode, FlatCtx) \
|
VOLATILE_PTRDECL(ContextNode, FlatCtx) \
|
||||||
MUTEXDECL(Mutex)
|
MUTEXDECL(Mutex)
|
||||||
|
|
||||||
|
@ -272,6 +272,8 @@ void setupContext(ContextRoot *Root, GUID Guid, uint32_t NumCounters,
|
|||||||
|
|
||||||
ContextRoot *FunctionData::getOrAllocateContextRoot() {
|
ContextRoot *FunctionData::getOrAllocateContextRoot() {
|
||||||
auto *Root = CtxRoot;
|
auto *Root = CtxRoot;
|
||||||
|
if (!canBeRoot(Root))
|
||||||
|
return Root;
|
||||||
if (Root)
|
if (Root)
|
||||||
return Root;
|
return Root;
|
||||||
__sanitizer::GenericScopedLock<__sanitizer::StaticSpinMutex> L(&Mutex);
|
__sanitizer::GenericScopedLock<__sanitizer::StaticSpinMutex> L(&Mutex);
|
||||||
@ -328,8 +330,10 @@ ContextNode *getUnhandledContext(FunctionData &Data, void *Callee, GUID Guid,
|
|||||||
if (!CtxRoot) {
|
if (!CtxRoot) {
|
||||||
if (auto *RAD = getRootDetector())
|
if (auto *RAD = getRootDetector())
|
||||||
RAD->sample();
|
RAD->sample();
|
||||||
else if (auto *CR = Data.CtxRoot)
|
else if (auto *CR = Data.CtxRoot) {
|
||||||
return tryStartContextGivenRoot(CR, Guid, NumCounters, NumCallsites);
|
if (canBeRoot(CR))
|
||||||
|
return tryStartContextGivenRoot(CR, Guid, NumCounters, NumCallsites);
|
||||||
|
}
|
||||||
if (IsUnderContext || !__sanitizer::atomic_load_relaxed(&ProfilingStarted))
|
if (IsUnderContext || !__sanitizer::atomic_load_relaxed(&ProfilingStarted))
|
||||||
return TheScratchContext;
|
return TheScratchContext;
|
||||||
else
|
else
|
||||||
@ -404,20 +408,21 @@ ContextNode *__llvm_ctx_profile_get_context(FunctionData *Data, void *Callee,
|
|||||||
ContextNode *__llvm_ctx_profile_start_context(FunctionData *FData, GUID Guid,
|
ContextNode *__llvm_ctx_profile_start_context(FunctionData *FData, GUID Guid,
|
||||||
uint32_t Counters,
|
uint32_t Counters,
|
||||||
uint32_t Callsites) {
|
uint32_t Callsites) {
|
||||||
|
auto *Root = FData->getOrAllocateContextRoot();
|
||||||
return tryStartContextGivenRoot(FData->getOrAllocateContextRoot(), Guid,
|
assert(canBeRoot(Root));
|
||||||
Counters, Callsites);
|
return tryStartContextGivenRoot(Root, Guid, Counters, Callsites);
|
||||||
}
|
}
|
||||||
|
|
||||||
void __llvm_ctx_profile_release_context(FunctionData *FData)
|
void __llvm_ctx_profile_release_context(FunctionData *FData)
|
||||||
SANITIZER_NO_THREAD_SAFETY_ANALYSIS {
|
SANITIZER_NO_THREAD_SAFETY_ANALYSIS {
|
||||||
const auto *CurrentRoot = __llvm_ctx_profile_current_context_root;
|
const auto *CurrentRoot = __llvm_ctx_profile_current_context_root;
|
||||||
if (!CurrentRoot || FData->CtxRoot != CurrentRoot)
|
auto *CR = FData->CtxRoot;
|
||||||
|
if (!CurrentRoot || CR != CurrentRoot)
|
||||||
return;
|
return;
|
||||||
IsUnderContext = false;
|
IsUnderContext = false;
|
||||||
assert(FData->CtxRoot);
|
assert(CR && canBeRoot(CR));
|
||||||
__llvm_ctx_profile_current_context_root = nullptr;
|
__llvm_ctx_profile_current_context_root = nullptr;
|
||||||
FData->CtxRoot->Taken.Unlock();
|
CR->Taken.Unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
void __llvm_ctx_profile_start_collection(unsigned AutodetectDuration) {
|
void __llvm_ctx_profile_start_collection(unsigned AutodetectDuration) {
|
||||||
@ -481,10 +486,13 @@ bool __llvm_ctx_profile_fetch(ProfileWriter &Writer) {
|
|||||||
// traversing it.
|
// traversing it.
|
||||||
const auto *Pos = reinterpret_cast<const FunctionData *>(
|
const auto *Pos = reinterpret_cast<const FunctionData *>(
|
||||||
__sanitizer::atomic_load_relaxed(&AllFunctionsData));
|
__sanitizer::atomic_load_relaxed(&AllFunctionsData));
|
||||||
for (; Pos; Pos = Pos->Next)
|
for (; Pos; Pos = Pos->Next) {
|
||||||
if (!Pos->CtxRoot)
|
const auto *CR = Pos->CtxRoot;
|
||||||
Writer.writeFlat(Pos->FlatCtx->guid(), Pos->FlatCtx->counters(),
|
if (!CR && canBeRoot(CR)) {
|
||||||
Pos->FlatCtx->counters_size());
|
const auto *FP = Pos->FlatCtx;
|
||||||
|
Writer.writeFlat(FP->guid(), FP->counters(), FP->counters_size());
|
||||||
|
}
|
||||||
|
}
|
||||||
Writer.endFlatSection();
|
Writer.endFlatSection();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,9 @@ struct FunctionData {
|
|||||||
#define _PTRDECL(T, N) T *N = nullptr;
|
#define _PTRDECL(T, N) T *N = nullptr;
|
||||||
#define _VOLATILE_PTRDECL(T, N) T *volatile N = nullptr;
|
#define _VOLATILE_PTRDECL(T, N) T *volatile N = nullptr;
|
||||||
#define _MUTEXDECL(N) ::__sanitizer::SpinMutex N;
|
#define _MUTEXDECL(N) ::__sanitizer::SpinMutex N;
|
||||||
CTXPROF_FUNCTION_DATA(_PTRDECL, _VOLATILE_PTRDECL, _MUTEXDECL)
|
#define _CONTEXT_PTR ContextRoot *CtxRoot = nullptr;
|
||||||
|
CTXPROF_FUNCTION_DATA(_PTRDECL, _CONTEXT_PTR, _VOLATILE_PTRDECL, _MUTEXDECL)
|
||||||
|
#undef _CONTEXT_PTR
|
||||||
#undef _PTRDECL
|
#undef _PTRDECL
|
||||||
#undef _VOLATILE_PTRDECL
|
#undef _VOLATILE_PTRDECL
|
||||||
#undef _MUTEXDECL
|
#undef _MUTEXDECL
|
||||||
@ -167,6 +169,11 @@ inline bool isScratch(const void *Ctx) {
|
|||||||
return (reinterpret_cast<uint64_t>(Ctx) & 1);
|
return (reinterpret_cast<uint64_t>(Ctx) & 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// True if Ctx is either nullptr or not the 0x1 value.
|
||||||
|
inline bool canBeRoot(const ContextRoot *Ctx) {
|
||||||
|
return reinterpret_cast<uintptr_t>(Ctx) != 1U;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace __ctx_profile
|
} // namespace __ctx_profile
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -66,7 +66,16 @@ void RootAutoDetector::start() {
|
|||||||
atomic_load_relaxed(&RAD->FunctionDataListHead));
|
atomic_load_relaxed(&RAD->FunctionDataListHead));
|
||||||
FD; FD = FD->Next) {
|
FD; FD = FD->Next) {
|
||||||
if (AllRoots.contains(reinterpret_cast<uptr>(FD->EntryAddress))) {
|
if (AllRoots.contains(reinterpret_cast<uptr>(FD->EntryAddress))) {
|
||||||
FD->getOrAllocateContextRoot();
|
if (canBeRoot(FD->CtxRoot)) {
|
||||||
|
FD->getOrAllocateContextRoot();
|
||||||
|
} else {
|
||||||
|
// FIXME: address this by informing the root detection algorithm
|
||||||
|
// to skip over such functions and pick the next down in the
|
||||||
|
// stack. At that point, this becomes an assert.
|
||||||
|
Printf("[ctxprof] Root auto-detector selected a musttail "
|
||||||
|
"function for root (%p). Ignoring\n",
|
||||||
|
FD->EntryAddress);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
atomic_store_relaxed(&RAD->Self, 0);
|
atomic_store_relaxed(&RAD->Self, 0);
|
||||||
|
@ -301,3 +301,13 @@ TEST_F(ContextTest, Dump) {
|
|||||||
EXPECT_EQ(W2.FlatsWritten, 1);
|
EXPECT_EQ(W2.FlatsWritten, 1);
|
||||||
EXPECT_EQ(W2.ExitedFlatCount, 1);
|
EXPECT_EQ(W2.ExitedFlatCount, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(ContextTest, MustNotBeRoot) {
|
||||||
|
FunctionData FData;
|
||||||
|
FData.CtxRoot = reinterpret_cast<ContextRoot *>(1U);
|
||||||
|
int FakeCalleeAddress = 0;
|
||||||
|
__llvm_ctx_profile_start_collection();
|
||||||
|
auto *Subctx =
|
||||||
|
__llvm_ctx_profile_get_context(&FData, &FakeCalleeAddress, 2, 3, 1);
|
||||||
|
EXPECT_TRUE(isScratch(Subctx));
|
||||||
|
}
|
||||||
|
@ -125,10 +125,11 @@ public:
|
|||||||
/// VOLATILE_PTRDECL is the same as above, but for volatile pointers;
|
/// VOLATILE_PTRDECL is the same as above, but for volatile pointers;
|
||||||
///
|
///
|
||||||
/// MUTEXDECL takes one parameter, the name of a field that is a mutex.
|
/// MUTEXDECL takes one parameter, the name of a field that is a mutex.
|
||||||
#define CTXPROF_FUNCTION_DATA(PTRDECL, VOLATILE_PTRDECL, MUTEXDECL) \
|
#define CTXPROF_FUNCTION_DATA(PTRDECL, CONTEXT_PTR, VOLATILE_PTRDECL, \
|
||||||
|
MUTEXDECL) \
|
||||||
PTRDECL(FunctionData, Next) \
|
PTRDECL(FunctionData, Next) \
|
||||||
VOLATILE_PTRDECL(void, EntryAddress) \
|
VOLATILE_PTRDECL(void, EntryAddress) \
|
||||||
VOLATILE_PTRDECL(ContextRoot, CtxRoot) \
|
CONTEXT_PTR \
|
||||||
VOLATILE_PTRDECL(ContextNode, FlatCtx) \
|
VOLATILE_PTRDECL(ContextNode, FlatCtx) \
|
||||||
MUTEXDECL(Mutex)
|
MUTEXDECL(Mutex)
|
||||||
|
|
||||||
|
@ -12,9 +12,11 @@
|
|||||||
#include "llvm/Analysis/CtxProfAnalysis.h"
|
#include "llvm/Analysis/CtxProfAnalysis.h"
|
||||||
#include "llvm/Analysis/OptimizationRemarkEmitter.h"
|
#include "llvm/Analysis/OptimizationRemarkEmitter.h"
|
||||||
#include "llvm/IR/Analysis.h"
|
#include "llvm/IR/Analysis.h"
|
||||||
|
#include "llvm/IR/Constants.h"
|
||||||
#include "llvm/IR/DiagnosticInfo.h"
|
#include "llvm/IR/DiagnosticInfo.h"
|
||||||
#include "llvm/IR/GlobalValue.h"
|
#include "llvm/IR/GlobalValue.h"
|
||||||
#include "llvm/IR/IRBuilder.h"
|
#include "llvm/IR/IRBuilder.h"
|
||||||
|
#include "llvm/IR/InstrTypes.h"
|
||||||
#include "llvm/IR/Instructions.h"
|
#include "llvm/IR/Instructions.h"
|
||||||
#include "llvm/IR/IntrinsicInst.h"
|
#include "llvm/IR/IntrinsicInst.h"
|
||||||
#include "llvm/IR/Module.h"
|
#include "llvm/IR/Module.h"
|
||||||
@ -55,7 +57,7 @@ class CtxInstrumentationLowerer final {
|
|||||||
Module &M;
|
Module &M;
|
||||||
ModuleAnalysisManager &MAM;
|
ModuleAnalysisManager &MAM;
|
||||||
Type *ContextNodeTy = nullptr;
|
Type *ContextNodeTy = nullptr;
|
||||||
Type *FunctionDataTy = nullptr;
|
StructType *FunctionDataTy = nullptr;
|
||||||
|
|
||||||
DenseSet<const Function *> ContextRootSet;
|
DenseSet<const Function *> ContextRootSet;
|
||||||
Function *StartCtx = nullptr;
|
Function *StartCtx = nullptr;
|
||||||
@ -63,6 +65,7 @@ class CtxInstrumentationLowerer final {
|
|||||||
Function *ReleaseCtx = nullptr;
|
Function *ReleaseCtx = nullptr;
|
||||||
GlobalVariable *ExpectedCalleeTLS = nullptr;
|
GlobalVariable *ExpectedCalleeTLS = nullptr;
|
||||||
GlobalVariable *CallsiteInfoTLS = nullptr;
|
GlobalVariable *CallsiteInfoTLS = nullptr;
|
||||||
|
Constant *CannotBeRootInitializer = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CtxInstrumentationLowerer(Module &M, ModuleAnalysisManager &MAM);
|
CtxInstrumentationLowerer(Module &M, ModuleAnalysisManager &MAM);
|
||||||
@ -117,12 +120,29 @@ CtxInstrumentationLowerer::CtxInstrumentationLowerer(Module &M,
|
|||||||
|
|
||||||
#define _PTRDECL(_, __) PointerTy,
|
#define _PTRDECL(_, __) PointerTy,
|
||||||
#define _VOLATILE_PTRDECL(_, __) PointerTy,
|
#define _VOLATILE_PTRDECL(_, __) PointerTy,
|
||||||
|
#define _CONTEXT_ROOT PointerTy,
|
||||||
#define _MUTEXDECL(_) SanitizerMutexType,
|
#define _MUTEXDECL(_) SanitizerMutexType,
|
||||||
|
|
||||||
FunctionDataTy = StructType::get(
|
FunctionDataTy = StructType::get(
|
||||||
M.getContext(),
|
M.getContext(), {CTXPROF_FUNCTION_DATA(_PTRDECL, _CONTEXT_ROOT,
|
||||||
{CTXPROF_FUNCTION_DATA(_PTRDECL, _VOLATILE_PTRDECL, _MUTEXDECL)});
|
_VOLATILE_PTRDECL, _MUTEXDECL)});
|
||||||
#undef _PTRDECL
|
#undef _PTRDECL
|
||||||
|
#undef _CONTEXT_ROOT
|
||||||
|
#undef _VOLATILE_PTRDECL
|
||||||
|
#undef _MUTEXDECL
|
||||||
|
|
||||||
|
#define _PTRDECL(_, __) Constant::getNullValue(PointerTy),
|
||||||
|
#define _VOLATILE_PTRDECL(_, __) _PTRDECL(_, __)
|
||||||
|
#define _MUTEXDECL(_) Constant::getNullValue(SanitizerMutexType),
|
||||||
|
#define _CONTEXT_ROOT \
|
||||||
|
Constant::getIntegerValue( \
|
||||||
|
PointerTy, \
|
||||||
|
APInt(M.getDataLayout().getPointerTypeSizeInBits(PointerTy), 1U)),
|
||||||
|
CannotBeRootInitializer = ConstantStruct::get(
|
||||||
|
FunctionDataTy, {CTXPROF_FUNCTION_DATA(_PTRDECL, _CONTEXT_ROOT,
|
||||||
|
_VOLATILE_PTRDECL, _MUTEXDECL)});
|
||||||
|
#undef _PTRDECL
|
||||||
|
#undef _CONTEXT_ROOT
|
||||||
#undef _VOLATILE_PTRDECL
|
#undef _VOLATILE_PTRDECL
|
||||||
#undef _MUTEXDECL
|
#undef _MUTEXDECL
|
||||||
|
|
||||||
@ -134,8 +154,8 @@ CtxInstrumentationLowerer::CtxInstrumentationLowerer(Module &M,
|
|||||||
I32Ty, /*NumCallsites*/
|
I32Ty, /*NumCallsites*/
|
||||||
});
|
});
|
||||||
|
|
||||||
// Define a global for each entrypoint. We'll reuse the entrypoint's name as
|
// Define a global for each entrypoint. We'll reuse the entrypoint's name
|
||||||
// prefix. We assume the entrypoint names to be unique.
|
// as prefix. We assume the entrypoint names to be unique.
|
||||||
for (const auto &Fname : ContextRoots) {
|
for (const auto &Fname : ContextRoots) {
|
||||||
if (const auto *F = M.getFunction(Fname)) {
|
if (const auto *F = M.getFunction(Fname)) {
|
||||||
if (F->isDeclaration())
|
if (F->isDeclaration())
|
||||||
@ -145,10 +165,10 @@ CtxInstrumentationLowerer::CtxInstrumentationLowerer(Module &M,
|
|||||||
for (const auto &I : BB)
|
for (const auto &I : BB)
|
||||||
if (const auto *CB = dyn_cast<CallBase>(&I))
|
if (const auto *CB = dyn_cast<CallBase>(&I))
|
||||||
if (CB->isMustTailCall()) {
|
if (CB->isMustTailCall()) {
|
||||||
M.getContext().emitError(
|
M.getContext().emitError("The function " + Fname +
|
||||||
"The function " + Fname +
|
" was indicated as a context root, "
|
||||||
" was indicated as a context root, but it features musttail "
|
"but it features musttail "
|
||||||
"calls, which is not supported.");
|
"calls, which is not supported.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -240,6 +260,14 @@ bool CtxInstrumentationLowerer::lowerFunction(Function &F) {
|
|||||||
return false;
|
return false;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
|
if (HasMusttail && ContextRootSet.contains(&F)) {
|
||||||
|
F.getContext().emitError(
|
||||||
|
"[ctx_prof] A function with musttail calls was explicitly requested as "
|
||||||
|
"root. That is not supported because we cannot instrument a return "
|
||||||
|
"instruction to release the context: " +
|
||||||
|
F.getName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
auto &Head = F.getEntryBlock();
|
auto &Head = F.getEntryBlock();
|
||||||
for (auto &I : Head) {
|
for (auto &I : Head) {
|
||||||
// Find the increment intrinsic in the entry basic block.
|
// Find the increment intrinsic in the entry basic block.
|
||||||
@ -263,9 +291,14 @@ bool CtxInstrumentationLowerer::lowerFunction(Function &F) {
|
|||||||
// regular function)
|
// regular function)
|
||||||
// Don't set a name, they end up taking a lot of space and we don't need
|
// Don't set a name, they end up taking a lot of space and we don't need
|
||||||
// them.
|
// them.
|
||||||
|
|
||||||
|
// Zero-initialize the FunctionData, except for functions that have
|
||||||
|
// musttail calls. There, we set the CtxRoot field to 1, which will be
|
||||||
|
// treated as a "can't be set as root".
|
||||||
TheRootFuctionData = new GlobalVariable(
|
TheRootFuctionData = new GlobalVariable(
|
||||||
M, FunctionDataTy, false, GlobalVariable::InternalLinkage,
|
M, FunctionDataTy, false, GlobalVariable::InternalLinkage,
|
||||||
Constant::getNullValue(FunctionDataTy));
|
HasMusttail ? CannotBeRootInitializer
|
||||||
|
: Constant::getNullValue(FunctionDataTy));
|
||||||
|
|
||||||
if (ContextRootSet.contains(&F)) {
|
if (ContextRootSet.contains(&F)) {
|
||||||
Context = Builder.CreateCall(
|
Context = Builder.CreateCall(
|
||||||
@ -366,10 +399,6 @@ bool CtxInstrumentationLowerer::lowerFunction(Function &F) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// FIXME: This would happen if the entrypoint tailcalls. A way to fix would be
|
|
||||||
// to disallow this, (so this then stays as an error), another is to detect
|
|
||||||
// that and then do a wrapper or disallow the tail call. This only affects
|
|
||||||
// instrumentation, when we want to detect the call graph.
|
|
||||||
if (!HasMusttail && !ContextWasReleased)
|
if (!HasMusttail && !ContextWasReleased)
|
||||||
F.getContext().emitError(
|
F.getContext().emitError(
|
||||||
"[ctx_prof] A function that doesn't have musttail calls was "
|
"[ctx_prof] A function that doesn't have musttail calls was "
|
||||||
|
@ -18,7 +18,7 @@ declare void @bar()
|
|||||||
; LOWERING: @[[GLOB4:[0-9]+]] = internal global { ptr, ptr, ptr, ptr, i8 } zeroinitializer
|
; LOWERING: @[[GLOB4:[0-9]+]] = internal global { ptr, ptr, ptr, ptr, i8 } zeroinitializer
|
||||||
; LOWERING: @[[GLOB5:[0-9]+]] = internal global { ptr, ptr, ptr, ptr, i8 } zeroinitializer
|
; LOWERING: @[[GLOB5:[0-9]+]] = internal global { ptr, ptr, ptr, ptr, i8 } zeroinitializer
|
||||||
; LOWERING: @[[GLOB6:[0-9]+]] = internal global { ptr, ptr, ptr, ptr, i8 } zeroinitializer
|
; LOWERING: @[[GLOB6:[0-9]+]] = internal global { ptr, ptr, ptr, ptr, i8 } zeroinitializer
|
||||||
; LOWERING: @[[GLOB7:[0-9]+]] = internal global { ptr, ptr, ptr, ptr, i8 } zeroinitializer
|
; LOWERING: @[[GLOB7:[0-9]+]] = internal global { ptr, ptr, ptr, ptr, i8 } { ptr null, ptr null, ptr inttoptr (i64 1 to ptr), ptr null, i8 0 }
|
||||||
;.
|
;.
|
||||||
define void @foo(i32 %a, ptr %fct) {
|
define void @foo(i32 %a, ptr %fct) {
|
||||||
; INSTRUMENT-LABEL: define void @foo(
|
; INSTRUMENT-LABEL: define void @foo(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user