Files
TheRock/rocm-systems/projects/rocprof-trace-decoder/markers/SQTTMarkers.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

479 lines
20 KiB
C++

// MIT License
//
// Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "SQTTPass.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/InlineAsm.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Metadata.h"
#include "llvm/Support/AtomicOrdering.h"
#include "llvm/Support/ErrorHandling.h"
using namespace llvm;
static constexpr uint32_t GFX12_SHADER_CYCLES_LO = 29;
// Only sched_barrier and IR fences constrain ttracedata scheduling. AMDGPU
// barriers are IntrNoMem, so they still need an explicit sched_barrier(0).
static bool isHardSchedBoundary(Instruction* I)
{
if (!I) return false;
if (isa<FenceInst>(I)) return true;
auto* CI = dyn_cast<CallInst>(I);
Function* F = CI ? CI->getCalledFunction() : nullptr;
return F && F->getIntrinsicID() == Intrinsic::amdgcn_sched_barrier;
}
static bool isCanonicalSchedBarrier(Instruction* I)
{
auto* CI = dyn_cast_or_null<CallInst>(I);
Function* F = CI ? CI->getCalledFunction() : nullptr;
auto* Arg = CI && CI->arg_size() == 1 ? dyn_cast<ConstantInt>(CI->getArgOperand(0)) : nullptr;
return F && F->getIntrinsicID() == Intrinsic::amdgcn_sched_barrier && Arg && Arg->isZero();
}
static bool isMarkerSentinel(Instruction* I)
{
auto* CI = dyn_cast_or_null<CallInst>(I);
Function* F = CI ? CI->getCalledFunction() : nullptr;
return F && F->getName().starts_with("__sqtt_named_marker_");
}
static Instruction* nextRealInstruction(Instruction* I)
{
while (I && (I->isDebugOrPseudoInst() || I->isLifetimeStartOrEnd() || isMarkerSentinel(I))) I = I->getNextNode();
return I;
}
static bool isPayloadSequence(CallInst* header, CallInst* payload, MDNode* group)
{
for (Instruction* I = header->getNextNode(); I != payload; I = I ? I->getNextNode() : nullptr)
if (!I || (I->getMetadata(SQTT_PAYLOAD_GROUP_METADATA) != group && !I->isDebugOrPseudoInst() &&
!I->isLifetimeStartOrEnd()))
return false;
return true;
}
static void branchToScopedTrace(BasicBlock* source, BasicBlock* trace, BasicBlock* skip, Value* ok, bool pinSkipHead)
{
source->getTerminator()->eraseFromParent();
IRBuilder<>(source).CreateCondBr(ok, trace, skip);
if (!pinSkipHead) return;
Module* M = source->getParent()->getParent();
Type* I32 = Type::getInt32Ty(M->getContext());
Function* SchedBarrier = Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_sched_barrier);
IRBuilder<>(&*skip->getFirstInsertionPt()).CreateCall(SchedBarrier, {ConstantInt::get(I32, 0)});
}
bool SQTTInstrumentPass::isSyncInstruction(Instruction* I)
{
return isa<FenceInst>(I) || classifyBarrier(dyn_cast<CallInst>(I)) != BarrierKind::None;
}
// Emit the configured compiler ordering barrier; the fence is limited to LDS.
static void emitMemBarrier(IRBuilder<>& B, MemBarrierMode mode)
{
if (mode == MemBarrierMode::None) return;
LLVMContext& Ctx = B.getContext();
if (mode == MemBarrierMode::AsmClobber)
{
InlineAsm* MF =
InlineAsm::get(FunctionType::get(Type::getVoidTy(Ctx), false), "", "~{memory}", /*hasSideEffects=*/true);
B.CreateCall(MF);
return;
}
// MemBarrierMode::Fence
SyncScope::ID WG = Ctx.getOrInsertSyncScopeID("workgroup");
FenceInst* F = B.CreateFence(AtomicOrdering::AcquireRelease, WG);
// Keep compiler ordering without global cache invalidation.
Metadata* LocalSyncAS[] = {MDString::get(Ctx, "amdgpu-synchronize-as"), MDString::get(Ctx, "local")};
F->setMetadata(LLVMContext::MD_mmra, MDNode::get(Ctx, LocalSyncAS));
}
void SQTTInstrumentPass::emitTraceBoundary(IRBuilder<>& B, bool after, bool schedBarrier)
{
Module* M = B.GetInsertBlock()->getParent()->getParent();
Type* I32 = Type::getInt32Ty(B.getContext());
Function* SchedBarrier =
schedBarrier ? Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_sched_barrier) : nullptr;
if (!after && SchedBarrier) B.CreateCall(SchedBarrier, {ConstantInt::get(I32, 0)});
emitMemBarrier(B, Config.MemBarrier);
if (after && SchedBarrier) B.CreateCall(SchedBarrier, {ConstantInt::get(I32, 0)});
}
void SQTTInstrumentPass::emitTraceBoundaries(IRBuilder<>& B, Instruction* first, Instruction* last, bool schedBarrier)
{
Instruction* next = last->getNextNode();
IRBuilder<> Before(first);
emitTraceBoundary(Before, /*after=*/false, schedBarrier && !isHardSchedBoundary(first->getPrevNode()));
// Newly-built scoped traces end after `last`; existing traces have a tail.
if (next) B.SetInsertPoint(next);
emitTraceBoundary(B, /*after=*/true, schedBarrier && !isHardSchedBoundary(next));
}
void SQTTInstrumentPass::emitScopedTrace(
IRBuilder<>& B,
Function& F,
GfxGen gen,
const char* traceBlockName,
const char* skipBlockName,
bool pinSkipHead,
function_ref<void(IRBuilder<>&)> emit
)
{
if (!Config.needsScopeCheck())
{
emit(B);
return;
}
Value* Ok = getOrCreateScopeCheck(F, gen);
Instruction* SplitPt = &*B.GetInsertPoint();
BasicBlock* OrigBB = SplitPt->getParent();
BasicBlock* TailBB = OrigBB->splitBasicBlock(SplitPt, skipBlockName);
BasicBlock* TraceBB = BasicBlock::Create(F.getContext(), traceBlockName, &F, TailBB);
branchToScopedTrace(OrigBB, TraceBB, TailBB, Ok, pinSkipHead);
IRBuilder<> Trace(TraceBB);
emit(Trace);
Trace.CreateBr(TailBB);
B.SetInsertPoint(&*TailBB->begin());
}
void SQTTInstrumentPass::insertTraceMarker(IRBuilder<>& B, uint32_t markerID, Function& F, GfxGen gen, Value* payload)
{
Module* M = F.getParent();
bool needsBarriers = !Config.needsScopeCheck();
bool pinSkipHead = Config.needsScopeCheck() && isSyncInstruction(nextRealInstruction(&*B.GetInsertPoint()));
auto emitMarker = [&](IRBuilder<>& Builder)
{
CallInst* first = emitBareTrace(Builder, markerID, M, gen);
Instruction* last = payload ? emitRawTracePayload(Builder, payload, M, first) : first;
emitTraceBoundaries(Builder, first, last, needsBarriers);
};
emitScopedTrace(B, F, gen, "sqtt.trace", "sqtt.skip", pinSkipHead, emitMarker);
}
Value* SQTTInstrumentPass::buildScopeCheck(IRBuilder<>& B, GfxGen gen)
{
Module* M = B.GetInsertBlock()->getParent()->getParent();
LLVMContext& Ctx = M->getContext();
Type* I32 = Type::getInt32Ty(Ctx);
HwRegEncodings hw = getHwRegEncodings(gen);
Function* SGetReg = Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_s_getreg);
Value* Ok = ConstantInt::getTrue(Ctx);
auto addCheck = [&](uint32_t mask, uint32_t fullMask, uint32_t hwReg)
{
if ((mask & fullMask) != fullMask)
{
Value* ID = B.CreateCall(SGetReg, {ConstantInt::get(I32, hwReg)});
Value* Bit = B.CreateAnd(B.CreateLShr(ConstantInt::get(I32, mask), ID), ConstantInt::get(I32, 1));
Ok = B.CreateAnd(Ok, B.CreateICmpNE(Bit, ConstantInt::get(I32, 0)));
}
};
addCheck(Config.WaveMask, FULL_WAVE_MASK, hw.wave);
addCheck(Config.SimdMask, FULL_SIMD_MASK, hw.simd);
addCheck(Config.CuMask, FULL_CU_MASK, hw.cu);
addCheck(Config.WgMask, FULL_WG_MASK, hw.wg);
return Ok;
}
Value* SQTTInstrumentPass::getOrCreateScopeCheck(Function& F, GfxGen gen)
{
if (CurScopeCheck) return CurScopeCheck;
IRBuilder<> B(&*F.getEntryBlock().getFirstInsertionPt());
CurScopeCheck = buildScopeCheck(B, gen);
return CurScopeCheck;
}
// Only marker/hint/debug/lifetime instructions may move into a conditional trace block.
static bool isIgnorableBetweenMarkers(Instruction* I)
{
auto* CI = dyn_cast<CallInst>(I);
Function* F = CI ? CI->getCalledFunction() : nullptr;
if (!F) return false;
auto ID = F->getIntrinsicID();
return ID == Intrinsic::amdgcn_s_ttracedata || ID == Intrinsic::amdgcn_s_ttracedata_imm ||
isCanonicalSchedBarrier(I) || ID == Intrinsic::dbg_declare || ID == Intrinsic::dbg_value ||
ID == Intrinsic::dbg_label || ID == Intrinsic::lifetime_start || ID == Intrinsic::lifetime_end;
}
bool SQTTInstrumentPass::finalizeExistingMarkers(Function& F, GfxGen gen)
{
struct TraceRange
{
CallInst *First, *Last;
};
SmallVector<SmallVector<TraceRange, 4>, 8> ByBlock;
for (auto& BB : F)
{
SmallVector<TraceRange, 4> InBB;
for (auto& I : BB)
{
auto* CI = dyn_cast<CallInst>(&I);
if (!isTraceDataCall(CI)) continue;
MDNode* group = CI->getMetadata(SQTT_PAYLOAD_GROUP_METADATA);
if (CI->getMetadata(SQTT_RAW_PAYLOAD_METADATA) && group && !InBB.empty() &&
InBB.back().First == InBB.back().Last && InBB.back().First->getMetadata(SQTT_MARKER_HEADER_METADATA) &&
InBB.back().First->getMetadata(SQTT_PAYLOAD_GROUP_METADATA) == group &&
isPayloadSequence(InBB.back().First, CI, group))
InBB.back().Last = CI;
else
InBB.push_back({CI, CI});
}
if (!InBB.empty()) ByBlock.push_back(std::move(InBB));
}
if (ByBlock.empty()) return false;
const bool scopeCheck = Config.needsScopeCheck();
auto addBoundaries = [&](const TraceRange& range)
{
IRBuilder<> B(range.First);
emitTraceBoundaries(B, range.First, range.Last, !scopeCheck);
};
for (auto& Ranges : ByBlock)
{
if (!scopeCheck)
{
for (const TraceRange& range : Ranges) addBoundaries(range);
continue;
}
for (const TraceRange& range : Ranges)
{
while (isCanonicalSchedBarrier(range.First->getPrevNode())) range.First->getPrevNode()->eraseFromParent();
while (isCanonicalSchedBarrier(range.Last->getNextNode())) range.Last->getNextNode()->eraseFromParent();
}
// Coalesce adjacent marker runs: OptimizerLastEP has no later
// SimplifyCFG. Wrap each run before adding its boundaries.
for (size_t first = 0; first < Ranges.size();)
{
size_t last = first;
while (last + 1 < Ranges.size())
{
bool canExtend = true;
for (Instruction* I = Ranges[last].Last->getNextNode(); I && I != Ranges[last + 1].First;
I = I->getNextNode())
{
if (!isIgnorableBetweenMarkers(I))
{
canExtend = false;
break;
}
}
if (!canExtend) break;
++last;
}
bool pinSkipHead = isSyncInstruction(nextRealInstruction(Ranges[last].Last->getNextNode()));
wrapRangeWithScopeCheck(Ranges[first].First, Ranges[last].Last, F, gen, pinSkipHead);
for (size_t i = first; i <= last; ++i) addBoundaries(Ranges[i]);
first = last + 1;
}
}
return true;
}
void SQTTInstrumentPass::wrapRangeWithScopeCheck(
CallInst* First, CallInst* Last, Function& F, GfxGen gen, bool pinSkipHead
)
{
Value* Ok = getOrCreateScopeCheck(F, gen);
BasicBlock* OrigBB = First->getParent();
BasicBlock* TraceBB = OrigBB->splitBasicBlock(First->getIterator(), "sqtt.trace");
// Split after the run so the tail begins with the first non-marker.
BasicBlock* TailBB = TraceBB->splitBasicBlock(Last->getNextNode()->getIterator(), "sqtt.skip");
branchToScopedTrace(OrigBB, TraceBB, TailBB, Ok, pinSkipHead);
}
CallInst* SQTTInstrumentPass::emitBareTrace(IRBuilder<>& B, uint32_t encoded, Module* M, GfxGen gen)
{
LLVMContext& Ctx = M->getContext();
bool useImm = canUseImm(encoded) && supportsImmTrace(gen);
Function* TTD = Intrinsic::getOrInsertDeclaration(
M, useImm ? Intrinsic::amdgcn_s_ttracedata_imm : Intrinsic::amdgcn_s_ttracedata
);
CallInst* CI =
B.CreateCall(TTD, {ConstantInt::get(useImm ? Type::getInt16Ty(Ctx) : Type::getInt32Ty(Ctx), encoded)});
CI->setMetadata(SQTT_MARKER_HEADER_METADATA, MDNode::get(Ctx, {}));
return CI;
}
CallInst* SQTTInstrumentPass::emitBareTraceValue(IRBuilder<>& B, Value* val, Module* M)
{
Function* TTD = Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_s_ttracedata);
CallInst* trace = B.CreateCall(TTD, {val});
trace->setMetadata(SQTT_RAW_PAYLOAD_METADATA, MDNode::get(B.getContext(), {}));
return trace;
}
bool SQTTInstrumentPass::isTraceDataCall(const CallInst* CI)
{
if (!CI) return false;
const Function* Callee = CI->getCalledFunction();
if (!Callee) return false;
auto ID = Callee->getIntrinsicID();
return ID == Intrinsic::amdgcn_s_ttracedata || ID == Intrinsic::amdgcn_s_ttracedata_imm;
}
CallInst* SQTTInstrumentPass::emitRawTracePayload(IRBuilder<>& B, Value* val, Module* M, CallInst* header)
{
// Full s_ttracedata lowers through M0, so its input must be scalar. The
// intrinsic lowering normally inserts this readfirstlane for a divergent
// named data value; retain that behavior before the explicit asm lowering.
Type* I32 = Type::getInt32Ty(M->getContext());
MDNode* group = MDNode::getDistinct(B.getContext(), {});
header->setMetadata(SQTT_PAYLOAD_GROUP_METADATA, group);
if (val->getType() != I32)
{
val = B.CreateZExtOrTrunc(val, I32);
if (auto* I = dyn_cast<Instruction>(val)) I->setMetadata(SQTT_PAYLOAD_GROUP_METADATA, group);
}
if (!isa<ConstantInt>(val))
{
Function* ReadFirstLane = Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_readfirstlane, {I32});
CallInst* prep = B.CreateCall(ReadFirstLane, {val});
prep->setMetadata(SQTT_PAYLOAD_GROUP_METADATA, group);
val = prep;
}
CallInst* trace = emitBareTraceValue(B, val, M);
trace->setMetadata(SQTT_PAYLOAD_GROUP_METADATA, group);
return trace;
}
bool SQTTInstrumentPass::finalizeFullTraces(Function& F, GfxGen gen)
{
unsigned clockBits = Config.ShaderClockBits;
const bool packClock = gen == GfxGen::GFX12 && clockBits != 0;
if (packClock && clockBits > 29) report_fatal_error("SQTT_SHADER_CLOCK_BITS must leave at least one marker ID bit");
if (packClock && (Config.ShaderClockShift >= 32 || Config.ShaderClockShift + clockBits > 32))
report_fatal_error("SQTT shader clock window must fit in shader_cycles_lo bits [31:0]");
const unsigned idBits = packClock ? 30 - clockBits : 0;
const uint32_t maxID = packClock ? (uint32_t(1) << idBits) - 1u : 0;
const uint32_t markerAndFlagMask = packClock ? (uint32_t(1) << (idBits + 2)) - 1u : 0;
Module* M = F.getParent();
LLVMContext& Ctx = M->getContext();
Type* I32 = Type::getInt32Ty(Ctx);
const bool useShaderCyclesU64 = packClock && hasShaderCyclesU64(F);
Function* SGetReg = nullptr;
InlineAsm* GetShaderCycles = nullptr;
uint32_t hwreg = packClock ? GETREG_IMMED(clockBits - 1, Config.ShaderClockShift, GFX12_SHADER_CYCLES_LO) : 0;
uint32_t clockDestShift = packClock ? 32 - clockBits : 0;
// Model M0 as a fixed output instead of a clobber. M0 is reserved on
// AMDGPU and LLVM diagnoses `~{m0}` as undefined behavior.
FunctionType* TraceTy = FunctionType::get(I32, {I32}, false);
static constexpr const char TraceAsmText[] = "s_mov_b32 m0, $1\n"
"s_nop 0\n"
"s_ttracedata";
InlineAsm* ImmediateTrace = InlineAsm::get(TraceTy, TraceAsmText, "={m0},i", /*hasSideEffects=*/true);
InlineAsm* ScalarTrace = InlineAsm::get(TraceTy, TraceAsmText, "={m0},s", /*hasSideEffects=*/true);
bool changed = false;
for (auto& BB : F)
{
for (auto It = BB.begin(), End = BB.end(); It != End;)
{
auto* CI = dyn_cast<CallInst>(&*It++);
if (!isTraceDataCall(CI)) continue;
bool packThisTrace = false;
if (packClock && !CI->getMetadata(SQTT_RAW_PAYLOAD_METADATA))
{
Value* encoded = CI->getArgOperand(0);
if (auto* Arg = dyn_cast<ConstantInt>(encoded))
{
// A bare exit has no ID and cannot be distinguished from
// the numeric API in trace data, so keep all of them
// packed.
if (CI->getMetadata(SQTT_MARKER_HEADER_METADATA) || Arg->getZExtValue() == FLAG_EXIT_PREV)
{
uint32_t markerID = Arg->getZExtValue() >> 2;
if (markerID > maxID)
report_fatal_error(
Twine("SQTT marker ID ") + Twine(markerID) +
" does not fit with SQTT_SHADER_CLOCK_BITS=" + Twine(clockBits)
);
packThisTrace = true;
}
}
else
packThisTrace = CI->getMetadata(SQTT_MARKER_HEADER_METADATA);
}
if (!packThisTrace && CI->getCalledFunction()->getIntrinsicID() != Intrinsic::amdgcn_s_ttracedata) continue;
IRBuilder<> B(CI);
Value* value = CI->getArgOperand(0);
if (packThisTrace)
{
if (value->getType() != I32) value = B.CreateZExtOrTrunc(value, I32);
Value* markerAndFlags = B.CreateAnd(value, ConstantInt::get(I32, markerAndFlagMask));
Value* clock;
if (useShaderCyclesU64)
{
if (!GetShaderCycles)
GetShaderCycles = InlineAsm::get(
FunctionType::get(Type::getInt64Ty(Ctx), false),
"s_get_shader_cycles_u64 $0",
"=s",
/*hasSideEffects=*/true
);
Value* cyclesLo = B.CreateTrunc(B.CreateCall(GetShaderCycles), I32);
// The final shift below discards all but clockBits,
// matching the right-aligned field returned by s_getreg
// on gfx1200 and gfx1201.
clock = B.CreateLShr(cyclesLo, ConstantInt::get(I32, Config.ShaderClockShift));
}
else
{
if (!SGetReg) SGetReg = Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_s_getreg);
clock = B.CreateCall(SGetReg, {ConstantInt::get(I32, hwreg)});
}
value = B.CreateOr(B.CreateShl(clock, ConstantInt::get(I32, clockDestShift)), markerAndFlags);
ShaderClockBitsUsed = clockBits;
}
InlineAsm* TraceAsm = isa<ConstantInt>(value) ? ImmediateTrace : ScalarTrace;
CallInst* replacement = B.CreateCall(TraceAsm, {value});
replacement->setDebugLoc(CI->getDebugLoc());
replacement->copyMetadata(*CI);
CI->eraseFromParent();
changed = true;
}
}
return changed;
}