summaryrefslogtreecommitdiffstats
path: root/source/slang
diff options
context:
space:
mode:
authorMukund Keshava <mkeshava@nvidia.com>2025-05-11 02:29:37 +0530
committerGitHub <noreply@github.com>2025-05-10 20:59:37 +0000
commit083eecee3f56b90c7011895f53aaafa9db15856e (patch)
treea0a18f0905f2884374ee351713fe77af0843f679 /source/slang
parent48203ea02250ba517f749a222092f091d9bef15e (diff)
Add debug information for slang inling (#6621)
Diffstat (limited to 'source/slang')
-rw-r--r--source/slang/slang-emit-c-like.cpp15
-rw-r--r--source/slang/slang-emit-spirv-ops-debug-info-ext.h86
-rw-r--r--source/slang/slang-emit-spirv.cpp212
-rw-r--r--source/slang/slang-emit.cpp4
-rw-r--r--source/slang/slang-ir-autodiff-fwd.cpp5
-rw-r--r--source/slang/slang-ir-autodiff-rev.cpp5
-rw-r--r--source/slang/slang-ir-autodiff.cpp4
-rw-r--r--source/slang/slang-ir-inline.cpp258
-rw-r--r--source/slang/slang-ir-inst-defs.h8
-rw-r--r--source/slang/slang-ir-inst-pass-base.h2
-rw-r--r--source/slang/slang-ir-insts.h74
-rw-r--r--source/slang/slang-ir-strip-debug-info.cpp4
-rw-r--r--source/slang/slang-ir-validate.cpp1
-rw-r--r--source/slang/slang-ir.cpp52
-rw-r--r--source/slang/slang-lower-to-ir.cpp25
15 files changed, 704 insertions, 51 deletions
diff --git a/source/slang/slang-emit-c-like.cpp b/source/slang/slang-emit-c-like.cpp
index 18dbcf1cd..56668d092 100644
--- a/source/slang/slang-emit-c-like.cpp
+++ b/source/slang/slang-emit-c-like.cpp
@@ -3168,6 +3168,11 @@ void CLikeSourceEmitter::_emitInst(IRInst* inst)
case kIROp_DebugLine:
case kIROp_DebugVar:
case kIROp_DebugValue:
+ case kIROp_DebugInlinedAt:
+ case kIROp_DebugScope:
+ case kIROp_DebugNoScope:
+ case kIROp_DebugInlinedVariable:
+ case kIROp_DebugFunction:
break;
case kIROp_Unmodified:
@@ -5151,6 +5156,16 @@ void CLikeSourceEmitter::ensureGlobalInst(
return;
case kIROp_ThisType:
return;
+ case kIROp_DebugInlinedAt:
+ case kIROp_DebugScope:
+ case kIROp_DebugNoScope:
+ case kIROp_DebugFunction:
+ case kIROp_DebugVar:
+ case kIROp_DebugLine:
+ case kIROp_DebugSource:
+ case kIROp_DebugValue:
+ case kIROp_DebugInlinedVariable:
+ return;
default:
break;
}
diff --git a/source/slang/slang-emit-spirv-ops-debug-info-ext.h b/source/slang/slang-emit-spirv-ops-debug-info-ext.h
index 41c6e1faa..d8826afdc 100644
--- a/source/slang/slang-emit-spirv-ops-debug-info-ext.h
+++ b/source/slang/slang-emit-spirv-ops-debug-info-ext.h
@@ -428,13 +428,75 @@ SpvInst* emitOpDebugScope(
IRInst* inst,
const T& idResultType,
SpvInst* set,
- SpvInst* scope)
+ SpvInst* scope,
+ SpvInst* inlinedAt = nullptr)
{
static_assert(isSingular<T>);
+ if (inlinedAt)
+ {
+ return emitInst(
+ parent,
+ inst,
+ SpvOpExtInst,
+ idResultType,
+ kResultID,
+ set,
+ SpvWord(23),
+ scope,
+ inlinedAt);
+ }
return emitInst(parent, inst, SpvOpExtInst, idResultType, kResultID, set, SpvWord(23), scope);
}
template<typename T>
+SpvInst* emitOpDebugNoScope(
+ SpvInstParent* parent,
+ IRInst* inst,
+ const T& idResultType,
+ SpvInst* set)
+{
+ static_assert(isSingular<T>);
+ return emitInst(parent, inst, SpvOpExtInst, idResultType, kResultID, set, SpvWord(24));
+}
+
+template<typename T>
+SpvInst* emitOpDebugInlinedAt(
+ SpvInstParent* parent,
+ IRInst* inst,
+ const T& idResultType,
+ SpvInst* set,
+ IRInst* line,
+ SpvInst* scope,
+ SpvInst* inlinedAt = nullptr)
+{
+ static_assert(isSingular<T>);
+ if (inlinedAt)
+ {
+ return emitInst(
+ parent,
+ inst,
+ SpvOpExtInst,
+ idResultType,
+ kResultID,
+ set,
+ SpvWord(25),
+ line,
+ scope,
+ inlinedAt);
+ }
+ return emitInst(
+ parent,
+ inst,
+ SpvOpExtInst,
+ idResultType,
+ kResultID,
+ set,
+ SpvWord(25),
+ line,
+ scope);
+}
+
+template<typename T>
SpvInst* emitOpDebugLocalVariable(
SpvInstParent* parent,
IRInst* inst,
@@ -484,6 +546,28 @@ SpvInst* emitOpDebugLocalVariable(
flags);
}
+template<typename T>
+SpvInst* emitOpDebugInlinedVariable(
+ SpvInstParent* parent,
+ IRInst* inst,
+ const T& idResultType,
+ SpvInst* set,
+ IRInst* variable,
+ IRInst* inlined)
+{
+ static_assert(isSingular<T>);
+ return emitInst(
+ parent,
+ inst,
+ SpvOpExtInst,
+ idResultType,
+ kResultID,
+ set,
+ SpvWord(27),
+ variable,
+ inlined);
+}
+
template<typename T, typename Ts>
SpvInst* emitOpDebugValue(
SpvInstParent* parent,
diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp
index ad1f1bdf8..bd9b23b2d 100644
--- a/source/slang/slang-emit-spirv.cpp
+++ b/source/slang/slang-emit-spirv.cpp
@@ -1952,6 +1952,17 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex
case kIROp_IndicesType:
case kIROp_PrimitivesType:
return nullptr;
+ case kIROp_DebugFunction:
+ return emitDebugFunction(
+ getSection(SpvLogicalSectionID::ConstantsAndTypes),
+ nullptr,
+ nullptr,
+ as<IRDebugFunction>(inst),
+ nullptr);
+ case kIROp_DebugInlinedAt:
+ return emitDebugInlinedAt(
+ getSection(SpvLogicalSectionID::ConstantsAndTypes),
+ as<IRDebugInlinedAt>(inst));
default:
{
if (as<IRSPIRVAsmOperand>(inst))
@@ -3026,7 +3037,17 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex
}
}
// DebugInfo.
- funcDebugScope = emitDebugFunction(spvBlock, spvFunc, irFunc);
+ IRDebugFunction* irDebugFunc = nullptr;
+ if (auto debugFuncDecor = irFunc->findDecoration<IRDebugFuncDecoration>())
+ {
+ irDebugFunc = as<IRDebugFunction>(debugFuncDecor->getDebugFunc());
+ }
+ funcDebugScope = emitDebugFunction(
+ getSection(SpvLogicalSectionID::ConstantsAndTypes),
+ spvBlock,
+ spvFunc,
+ irDebugFunc,
+ irFunc->getDataType());
}
if (funcDebugScope)
{
@@ -4083,6 +4104,25 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex
operands.getArrayView());
}
break;
+ case kIROp_DebugFunction:
+ return emitDebugFunction(
+ getSection(SpvLogicalSectionID::ConstantsAndTypes),
+ nullptr,
+ nullptr,
+ as<IRDebugFunction>(inst),
+ nullptr);
+ case kIROp_DebugInlinedAt:
+ return emitDebugInlinedAt(
+ getSection(SpvLogicalSectionID::ConstantsAndTypes),
+ as<IRDebugInlinedAt>(inst));
+ case kIROp_DebugScope:
+ return emitDebugScope(parent, as<IRDebugScope>(inst));
+ case kIROp_DebugNoScope:
+ return emitDebugNoScope(parent);
+ case kIROp_DebugInlinedVariable:
+ return emitDebugInlinedVariable(
+ getSection(SpvLogicalSectionID::ConstantsAndTypes),
+ as<IRDebugInlinedVariable>(inst));
}
if (result)
emitDecorations(inst, getID(result));
@@ -7395,6 +7435,136 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex
return m_nullDwarfExpr;
}
+ SpvInst* emitDebugScope(SpvInstParent* parent, IRDebugScope* debugScope)
+ {
+ auto inlinedAt = ensureInst(debugScope->getInlinedAt());
+ if (!inlinedAt)
+ return nullptr;
+
+ SpvInst* scope = ensureInst(debugScope->getScope());
+ if (!scope)
+ return nullptr;
+
+ return emitOpDebugScope(
+ parent,
+ nullptr,
+ m_voidType,
+ getNonSemanticDebugInfoExtInst(),
+ scope,
+ inlinedAt);
+ }
+
+
+ SpvInst* emitDebugFunction(
+ SpvInstParent* parent,
+ SpvInst* firstBlock,
+ SpvInst* spvFunc,
+ IRDebugFunction* debugFunc,
+ IRFuncType* debugType)
+ {
+ SpvInst* debugFuncInfo = nullptr;
+ if (debugFunc && m_mapIRInstToSpvInst.tryGetValue(debugFunc, debugFuncInfo))
+ {
+ return debugFuncInfo;
+ }
+
+ auto scope = findDebugScope(debugFunc);
+ if (!scope)
+ return nullptr;
+
+ SpvInst* neededDebugType = nullptr;
+ if (debugType)
+ {
+ neededDebugType = emitDebugType(debugType);
+ }
+ else
+ {
+ neededDebugType = emitDebugType(as<IRFuncType>(debugFunc->getDebugType()));
+ }
+
+ IRBuilder builder(debugFunc);
+ debugFuncInfo = emitOpDebugFunction(
+ parent,
+ debugFunc,
+ m_voidType,
+ getNonSemanticDebugInfoExtInst(),
+ debugFunc->getName(),
+ neededDebugType,
+ debugFunc->getFile(),
+ debugFunc->getLine(),
+ debugFunc->getCol(),
+ scope,
+ debugFunc->getName(),
+ builder.getIntValue(builder.getUIntType(), 0),
+ debugFunc->getLine());
+
+ if (firstBlock && spvFunc)
+ {
+ emitOpDebugFunctionDefinition(
+ firstBlock,
+ nullptr,
+ m_voidType,
+ getNonSemanticDebugInfoExtInst(),
+ debugFuncInfo,
+ spvFunc);
+ }
+ return debugFuncInfo;
+ }
+
+ SpvInst* emitDebugNoScope(SpvInstParent* parent)
+ {
+ return emitOpDebugNoScope(parent, nullptr, m_voidType, getNonSemanticDebugInfoExtInst());
+ }
+
+ SpvInst* emitDebugInlinedAt(SpvInstParent* parent, IRDebugInlinedAt* debugInlinedAt)
+ {
+ IRInst* lineInst = debugInlinedAt->getLine();
+
+ SpvInst* scope = nullptr;
+ if (as<IRDebugFunction>(debugInlinedAt->getDebugFunc()))
+ {
+ scope = ensureInst(debugInlinedAt->getDebugFunc());
+ }
+ if (scope == nullptr)
+ {
+ scope = findDebugScope(debugInlinedAt);
+ }
+
+ // If it's not chained to another IRDebugInlinedAt, we don't use this.
+ SpvInst* inlined = nullptr;
+ if (as<IRDebugInlinedAt>(debugInlinedAt)->isOuterInlinedPresent())
+ {
+ inlined = ensureInst(debugInlinedAt->getOuterInlinedAt());
+ }
+
+ return emitOpDebugInlinedAt(
+ parent,
+ debugInlinedAt,
+ m_voidType,
+ getNonSemanticDebugInfoExtInst(),
+ lineInst,
+ scope,
+ inlined);
+ }
+
+ SpvInst* emitDebugInlinedVariable(
+ SpvInstParent* parent,
+ IRDebugInlinedVariable* debugInlinedVar)
+ {
+ // Get the operands from the IRDebugInlinedVariable instruction
+ IRInst* variable = debugInlinedVar->getVariable();
+ IRInst* inlinedAt = debugInlinedVar->getInlinedAt();
+
+ // Emit the OpDebugInlinedVariable instruction
+ return emitOpDebugInlinedVariable(
+ parent,
+ debugInlinedVar,
+ m_voidType,
+ getNonSemanticDebugInfoExtInst(),
+ variable,
+ inlinedAt);
+ }
+
bool translateIRAccessChain(
IRBuilder& builder,
IRInst* baseType,
@@ -7860,46 +8030,6 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex
return result;
}
- SpvInst* emitDebugFunction(SpvInst* firstBlock, SpvInst* spvFunc, IRFunc* function)
- {
- auto scope = findDebugScope(function);
- if (!scope)
- return nullptr;
- auto name = getName(function);
- if (!name)
- return nullptr;
- auto debugLoc = function->findDecoration<IRDebugLocationDecoration>();
- if (!debugLoc)
- return nullptr;
- auto debugType = emitDebugType(function->getDataType());
- IRBuilder builder(function);
- auto debugFunc = emitOpDebugFunction(
- getSection(SpvLogicalSectionID::ConstantsAndTypes),
- nullptr,
- m_voidType,
- getNonSemanticDebugInfoExtInst(),
- name,
- debugType,
- debugLoc->getSource(),
- debugLoc->getLine(),
- debugLoc->getCol(),
- scope,
- name,
- builder.getIntValue(builder.getUIntType(), 0),
- debugLoc->getLine());
- registerDebugInst(function, debugFunc);
-
- emitOpDebugFunctionDefinition(
- firstBlock,
- nullptr,
- m_voidType,
- getNonSemanticDebugInfoExtInst(),
- debugFunc,
- spvFunc);
-
- return debugFunc;
- }
-
SpvInst* emitSPIRVAsm(SpvInstParent* parent, IRSPIRVAsm* inst)
{
SpvInst* last = nullptr;
diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp
index ac6336e9c..9410a8f45 100644
--- a/source/slang/slang-emit.cpp
+++ b/source/slang/slang-emit.cpp
@@ -350,6 +350,10 @@ void calcRequiredLoweringPassSet(
case kIROp_DebugLine:
case kIROp_DebugLocationDecoration:
case kIROp_DebugSource:
+ case kIROp_DebugInlinedAt:
+ case kIROp_DebugScope:
+ case kIROp_DebugNoScope:
+ case kIROp_DebugFunction:
result.debugInfo = true;
break;
case kIROp_ResultType:
diff --git a/source/slang/slang-ir-autodiff-fwd.cpp b/source/slang/slang-ir-autodiff-fwd.cpp
index febd30d7e..df690a4e2 100644
--- a/source/slang/slang-ir-autodiff-fwd.cpp
+++ b/source/slang/slang-ir-autodiff-fwd.cpp
@@ -2184,6 +2184,11 @@ InstPair ForwardDiffTranscriber::transcribeInstImpl(IRBuilder* builder, IRInst*
case kIROp_DebugLine:
case kIROp_DebugVar:
case kIROp_DebugValue:
+ case kIROp_DebugInlinedAt:
+ case kIROp_DebugScope:
+ case kIROp_DebugNoScope:
+ case kIROp_DebugInlinedVariable:
+ case kIROp_DebugFunction:
case kIROp_GetArrayLength:
case kIROp_SizeOf:
case kIROp_AlignOf:
diff --git a/source/slang/slang-ir-autodiff-rev.cpp b/source/slang/slang-ir-autodiff-rev.cpp
index 519f796b4..773a7741f 100644
--- a/source/slang/slang-ir-autodiff-rev.cpp
+++ b/source/slang/slang-ir-autodiff-rev.cpp
@@ -275,6 +275,11 @@ InstPair BackwardDiffTranscriberBase::transcribeInstImpl(IRBuilder* builder, IRI
case kIROp_WrapExistential:
case kIROp_MakeExistential:
case kIROp_MakeExistentialWithRTTI:
+ case kIROp_DebugInlinedAt:
+ case kIROp_DebugScope:
+ case kIROp_DebugNoScope:
+ case kIROp_DebugInlinedVariable:
+ case kIROp_DebugFunction:
return transcribeNonDiffInst(builder, origInst);
case kIROp_StructKey:
diff --git a/source/slang/slang-ir-autodiff.cpp b/source/slang/slang-ir-autodiff.cpp
index 6ad8c5dbb..c8dc3b480 100644
--- a/source/slang/slang-ir-autodiff.cpp
+++ b/source/slang/slang-ir-autodiff.cpp
@@ -965,7 +965,9 @@ IRInst* DifferentialPairTypeBuilder::lowerDiffPairType(IRBuilder* builder, IRTyp
if (as<IRThisType>(primalType) || as<IRAssociatedType>(primalType))
{
List<IRInterfaceType*> constraintTypes;
- constraintTypes.add(this->commonDiffPairInterface);
+ auto diffPairInterfaceType =
+ cast<IRInterfaceType>(getOrCreateCommonDiffPairInterface(builder));
+ constraintTypes.add(diffPairInterfaceType);
return builder->getAssociatedType(constraintTypes.getArrayView());
}
diff --git a/source/slang/slang-ir-inline.cpp b/source/slang/slang-ir-inline.cpp
index 73cf6eb39..8575330f8 100644
--- a/source/slang/slang-ir-inline.cpp
+++ b/source/slang/slang-ir-inline.cpp
@@ -3,6 +3,8 @@
#include "../core/slang-performance-profiler.h"
#include "slang-ir-ssa-simplification.h"
+#include "slang-ir-util.h"
+
// This file provides general facilities for inlining function calls.
//
@@ -290,6 +292,124 @@ struct InliningPassBase
return true;
}
+ // Find an existing debug function for the given function
+ IRInst* findExistingDebugFunc(IRFunc* func)
+ {
+ if (auto debugFuncDecor = func->findDecoration<IRDebugFuncDecoration>())
+ {
+ return debugFuncDecor->getDebugFunc();
+ }
+ return nullptr;
+ }
+
+ struct DebugInlineInfo
+ {
+ IRInst* newDebugInlinedAt = nullptr;
+ IRInst* calleeDebugFunc = nullptr;
+ };
+
+ // Sets up the initial debug information structures required *before* inlining a call site.
+ //
+ // This function performs the following steps:
+ // 1. Checks if the callee function has associated debug location information.
+ // 2. Obtain the call inst's existing debug information.
+ // 3. Finds the last `IRDebugLine` preceding the `call` instruction to determine the source
+ // location (line, col, file) of the call site.
+ // 4. Emits an `IRDebugInlinedAt` instruction with outer set to callDebugInlinedAt.
+ // 5. Inserts the newly created `IRDebugInlinedAt` instruction immediately *before* the `call`
+ // instruction.
+ DebugInlineInfo emitCalleeDebugInlinedAt(IRCall* call, IRFunc* callee, IRBuilder& builder)
+ {
+ IRDebugLine* lastDebugLine = nullptr;
+ IRInst* newDebugInlinedAt = nullptr;
+ IRInst* callerDebugFunc = nullptr;
+ IRInst* calleeDebugFunc = nullptr;
+
+ if (!callee->findDecoration<IRDebugLocationDecoration>())
+ {
+ return DebugInlineInfo();
+ }
+ else
+ {
+ // Check if the call inst is part of an existing scope. If yes, then we restore
+ // that scope after the inlining of callee. This case can occur when we have out of
+ // order inlining. See forceinline-basic-block-inline-order.slang test for that use
+ // case. If we are travesing back the call inst and if we find a DebugNoScope, it means
+ // that there's another function that was inlined. We don't want that scope. If the call
+ // inst truly belongs to another DebugScope, then we should hit a DebugScope inst
+ // *before* we see a DebugNoScope
+ IRDebugScope* callDebugScope = nullptr;
+ builder.setInsertAfter(call);
+ for (IRInst* inst = call->getPrevInst(); inst; inst = inst->getPrevInst())
+ {
+ if (as<IRDebugNoScope>(inst))
+ {
+ break;
+ }
+ if (as<IRDebugScope>(inst))
+ {
+ callDebugScope = as<IRDebugScope>(inst);
+ builder.emitDebugScope(
+ callDebugScope->getScope(),
+ callDebugScope->getInlinedAt());
+ break;
+ }
+ }
+ if (!callDebugScope)
+ {
+ builder.emitDebugNoScope();
+ }
+
+ IRDebugInlinedAt* callDebugInlinedAt = nullptr;
+ for (IRInst* inst = call->getPrevInst(); inst; inst = inst->getPrevInst())
+ {
+ if (as<IRDebugNoScope>(inst))
+ {
+ break;
+ }
+ if (as<IRDebugInlinedAt>(inst))
+ {
+ callDebugInlinedAt = as<IRDebugInlinedAt>(inst);
+ break;
+ }
+ }
+
+ // Find the last IRDebugLine to extract debug info.
+ for (IRInst* inst = call->getPrevInst(); inst; inst = inst->getPrevInst())
+ {
+ if (auto debugLine = as<IRDebugLine>(inst))
+ {
+ lastDebugLine = debugLine;
+ break;
+ }
+ }
+
+ if (!lastDebugLine)
+ return DebugInlineInfo();
+
+ calleeDebugFunc = findExistingDebugFunc(callee);
+
+ // The caller func is the right lexical scope needed for nsight to show where
+ // the function is getting inlined inside.
+ auto callerFunc = getParentFunc(call);
+ callerDebugFunc = findExistingDebugFunc(callerFunc);
+
+ builder.setInsertBefore(call);
+ newDebugInlinedAt = builder.emitDebugInlinedAt(
+ lastDebugLine->getLineStart(),
+ lastDebugLine->getColStart(),
+ lastDebugLine->getSource(),
+ callerDebugFunc,
+ callDebugInlinedAt);
+
+ if (newDebugInlinedAt && calleeDebugFunc)
+ {
+ return DebugInlineInfo{newDebugInlinedAt, calleeDebugFunc};
+ }
+ }
+ return DebugInlineInfo();
+ }
+
/// Inline the given `callSite`, which is assumed to have been validated
void inlineCallSite(CallSiteInfo const& callSite)
{
@@ -481,7 +601,9 @@ struct InliningPassBase
void inlineSingleBlockFuncBody(
CallSiteInfo const& callSite,
IRCloneEnv* env,
- IRBuilder* builder)
+ IRBuilder* builder,
+ IRInst* newDebugInlinedAt,
+ IRInst* calleeDebugFunc)
{
auto call = callSite.call;
auto callee = callSite.callee;
@@ -495,18 +617,22 @@ struct InliningPassBase
// them into the same basic block as the `call`.
//
builder->setInsertBefore(call);
+ IRInst* calleeDebugScope = nullptr;
+ if (calleeDebugFunc && newDebugInlinedAt)
+ {
+ calleeDebugScope = builder->emitDebugScope(calleeDebugFunc, newDebugInlinedAt);
+ }
// Along the way, we will detect any `return` instruction,
// and remember the (clone of the) returned value.
//
IRInst* returnVal = nullptr;
-
+ List<IRDebugInlinedAt*> debugInlinedInsts;
for (auto inst : firstBlock->getChildren())
{
switch (inst->getOp())
{
default:
- // In the common case we just clone the instruction as-is
_cloneInstWithSourceLoc(callSite, env, builder, inst);
break;
@@ -524,9 +650,40 @@ struct InliningPassBase
SLANG_ASSERT(!returnVal);
returnVal = findCloneForOperand(env, inst->getOperand(0));
break;
+
+ case kIROp_DebugNoScope:
+ {
+ if (calleeDebugScope)
+ _cloneInstWithSourceLoc(callSite, env, builder, calleeDebugScope);
+ break;
+ }
+
+ case kIROp_DebugInlinedAt:
+ {
+ auto clonedInst = _cloneInstWithSourceLoc(callSite, env, builder, inst);
+ if (!as<IRDebugInlinedAt>(clonedInst)->isOuterInlinedPresent())
+ debugInlinedInsts.add(as<IRDebugInlinedAt>(clonedInst));
+ break;
+ }
+ }
+ }
+ // For any debugInlinedAt without an outerinlinedAt, emit a new debugInlinedAt with the
+ // outer set, and delete the older debugInlinedAt
+ for (auto inst : debugInlinedInsts)
+ {
+ if (newDebugInlinedAt && !inst->isOuterInlinedPresent())
+ {
+ builder->setInsertAfter(inst);
+ auto newInlinedAt = builder->emitDebugInlinedAt(
+ inst->getLine(),
+ inst->getCol(),
+ inst->getFile(),
+ inst->getDebugFunc(),
+ newDebugInlinedAt);
+ inst->replaceUsesWith(newInlinedAt);
+ inst->removeAndDeallocate();
}
}
-
// We are going to remove the original `call` now that the callee
// has been inlined, but before we do that we need to replace
// all uses of the `call` with whatever value was produced by the
@@ -547,10 +704,28 @@ struct InliningPassBase
}
/// Inline the body of the callee for `callSite`.
+ // Here is the algorithm for inserting debug information for slang inlined functions:
+ // 1. Check if the call inst belongs to an existing debug scope and find corresponding
+ // debugInlinedAt. [callDebugScope, callDebugInlinedAt]
+ // 1a. If callDebugScope exists, emit this debug Scope* after* the call inst.
+ // 2. Emit a new DebugInlinedAt inst, with debugFunc of the callee, and outer debugInlinedAt is
+ // callDebugInlinedAt. [newDebugInlinedAt]
+ // 2a. If calleDebugScope does not exist, emit debugNoScope after the call inst.
+ // 3. Clone the callee body.
+ // 4. For each cloned block, do this:
+ // 4a.Emit a new DebugScope inst setting the current scope to newDebugInlinedAt.
+ // [calleeDebugScope] 4b.Emit a DebugNoScope at the end of each block. 4c.If callDebugScope
+ // exists, do not emit a DebugNoScope for the last block.
+ // 5. For each cloned debugInlinedAt inst, if its outer inlined at operand is null, set it to
+ // the new DebugInlinedAt inst inserted at the top of the block.
+ // 6. For each cloned debugNoScope inst, replace it with calleeDebugScope. (This is because all
+ // cloned insts are in callee's scope).
void inlineFuncBody(CallSiteInfo const& callSite, IRCloneEnv* env, IRBuilder* builder)
{
- auto call = callSite.call;
auto callee = callSite.callee;
+ auto call = callSite.call;
+
+ auto debugInlineInfo = emitCalleeDebugInlinedAt(call, callee, *builder);
// If the callee consists of a single basic block *and* that block
// ends with a `return` instruction, then we can apply a simple approach
@@ -561,10 +736,35 @@ struct InliningPassBase
SLANG_ASSERT(firstBlock);
if (!firstBlock->getNextBlock() && as<IRReturn>(firstBlock->getTerminator()))
{
- inlineSingleBlockFuncBody(callSite, env, builder);
+ inlineSingleBlockFuncBody(
+ callSite,
+ env,
+ builder,
+ debugInlineInfo.newDebugInlinedAt,
+ debugInlineInfo.calleeDebugFunc);
return;
}
+ // If the callee has multiple blocks, use the more complex inlining approach
+ inlineMultipleBlockFuncBody(
+ callSite,
+ env,
+ builder,
+ debugInlineInfo.newDebugInlinedAt,
+ debugInlineInfo.calleeDebugFunc);
+ }
+
+ // Inline the body of the callee for `callSite`, for a callee that has multiple basic blocks.
+ void inlineMultipleBlockFuncBody(
+ CallSiteInfo const& callSite,
+ IRCloneEnv* env,
+ IRBuilder* builder,
+ IRInst* newDebugInlinedAt,
+ IRInst* calleeDebugFunc)
+ {
+ auto call = callSite.call;
+ auto callee = callSite.callee;
+
// If the callee has any non-trivial control flow (multiple basic blocks
// and terminators other than `return`), we will need to split the control
// flow of the caller at the block that contains `call`.
@@ -680,7 +880,53 @@ struct InliningPassBase
}
isFirstBlock = false;
}
+ // For each existing debugNoScope inst, replace it with new debug scope we emit.
+ // For any debugInlinedAt without an outerinlinedAt, emit a new debugInlinedAt with the
+ // outer set, and delete the older debugInlinedAt
+ if (newDebugInlinedAt && callee->findDecoration<IRDebugLocationDecoration>())
+ {
+ for (auto calleeBlock : callee->getBlocks())
+ {
+ IRBlock* clonedBlock = as<IRBlock>(env->mapOldValToNew.getValue(calleeBlock));
+ setInsertBeforeOrdinaryInst(builder, clonedBlock->getFirstOrdinaryInst());
+ builder->emitDebugScope(calleeDebugFunc, newDebugInlinedAt);
+ List<IRInst*> debugNoScopeToRemove;
+ List<IRDebugInlinedAt*> debugInlinedAtToProcess;
+ for (auto inst : clonedBlock->getChildren())
+ {
+ if (as<IRDebugNoScope>(inst))
+ {
+ debugNoScopeToRemove.add(inst);
+ }
+ if (auto inlinedAt = as<IRDebugInlinedAt>(inst))
+ {
+ if (!inlinedAt->isOuterInlinedPresent())
+ {
+ debugInlinedAtToProcess.add(inlinedAt);
+ }
+ }
+ }
+ for (auto inst : debugNoScopeToRemove)
+ {
+ builder->setInsertAfter(inst);
+ builder->emitDebugScope(calleeDebugFunc, newDebugInlinedAt);
+ inst->removeAndDeallocate();
+ }
+ for (auto inlinedAt : debugInlinedAtToProcess)
+ {
+ builder->setInsertAfter(inlinedAt);
+ auto newInlinedAt = builder->emitDebugInlinedAt(
+ inlinedAt->getLine(),
+ inlinedAt->getCol(),
+ inlinedAt->getFile(),
+ inlinedAt->getDebugFunc(),
+ newDebugInlinedAt);
+ inlinedAt->replaceUsesWith(newInlinedAt);
+ inlinedAt->removeAndDeallocate();
+ }
+ }
+ }
// If there was a `returnVal` instruction that established
// the return value of the inlined function, then that value
// should be used to replace any uses of the original call.
diff --git a/source/slang/slang-ir-inst-defs.h b/source/slang/slang-ir-inst-defs.h
index 66505dbd0..5a62c8063 100644
--- a/source/slang/slang-ir-inst-defs.h
+++ b/source/slang/slang-ir-inst-defs.h
@@ -1165,6 +1165,9 @@ INST_RANGE(BindingQuery, GetRegisterIndex, GetRegisterSpace)
/// Decorates an inst with a debug source location (IRDebugSource, IRIntLit(line), IRIntLit(col)).
INST(DebugLocationDecoration, DebugLocation, 3, 0)
+ /// Decorates a function with a link to its debug function representation
+ INST(DebugFunctionDecoration, DebugFunction, 1, 0)
+
/// Recognized by SPIRV-emit pass so we can emit a SPIRV `Block` decoration.
INST(SPIRVBlockDecoration, spvBlock, 0, 0)
@@ -1356,6 +1359,11 @@ INST(DebugSource, DebugSource, 2, HOISTABLE)
INST(DebugLine, DebugLine, 5, 0)
INST(DebugVar, DebugVar, 4, 0)
INST(DebugValue, DebugValue, 2, 0)
+INST(DebugInlinedAt, DebugInlinedAt, 5, 0)
+INST(DebugFunction, DebugFunction, 5, 0)
+INST(DebugInlinedVariable, DebugInlinedVariable, 2, 0)
+INST(DebugScope, DebugScope, 2, 0)
+INST(DebugNoScope, DebugNoScope, 1, 0)
/* Embedded Precompiled Libraries */
INST(EmbeddedDownstreamIR, EmbeddedDownstreamIR, 2, 0)
diff --git a/source/slang/slang-ir-inst-pass-base.h b/source/slang/slang-ir-inst-pass-base.h
index 5ce73f617..8b7685652 100644
--- a/source/slang/slang-ir-inst-pass-base.h
+++ b/source/slang/slang-ir-inst-pass-base.h
@@ -137,6 +137,8 @@ public:
case kIROp_GenericSpecializationDictionary:
case kIROp_ExistentialFuncSpecializationDictionary:
case kIROp_ExistentialTypeSpecializationDictionary:
+ case kIROp_DebugInlinedAt:
+ case kIROp_DebugFunction:
continue;
default:
break;
diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h
index 5b196cf24..4bef81f47 100644
--- a/source/slang/slang-ir-insts.h
+++ b/source/slang/slang-ir-insts.h
@@ -3442,6 +3442,60 @@ struct IRDebugValue : IRInst
IRInst* getValue() { return getOperand(1); }
};
+struct IRDebugInlinedAt : IRInst
+{
+ IR_LEAF_ISA(DebugInlinedAt)
+ IRInst* getLine() { return getOperand(0); }
+ IRInst* getCol() { return getOperand(1); }
+ IRInst* getFile() { return getOperand(2); }
+ IRInst* getDebugFunc() { return getOperand(3); }
+ IRInst* getOuterInlinedAt()
+ {
+ if (operandCount == 5)
+ return getOperand(4);
+ return nullptr;
+ }
+ void setDebugFunc(IRInst* func) { setOperand(3, func); }
+ bool isOuterInlinedPresent() { return operandCount == 5; }
+};
+
+struct IRDebugScope : IRInst
+{
+ IR_LEAF_ISA(DebugScope)
+ IRInst* getScope() { return getOperand(0); }
+ IRInst* getInlinedAt() { return getOperand(1); }
+ void setInlinedAt(IRInst* inlinedAt) { setOperand(1, inlinedAt); }
+};
+
+struct IRDebugNoScope : IRInst
+{
+ IR_LEAF_ISA(DebugNoScope)
+ IRInst* getScope() { return getOperand(0); }
+};
+
+struct IRDebugInlinedVariable : IRInst
+{
+ IR_LEAF_ISA(DebugInlinedVariable)
+ IRInst* getVariable() { return getOperand(0); }
+ IRInst* getInlinedAt() { return getOperand(1); }
+};
+
+struct IRDebugFunction : IRInst
+{
+ IR_LEAF_ISA(DebugFunction)
+ IRInst* getName() { return getOperand(0); }
+ IRInst* getLine() { return getOperand(1); }
+ IRInst* getCol() { return getOperand(2); }
+ IRInst* getFile() { return getOperand(3); }
+ IRInst* getDebugType() { return getOperand(4); }
+};
+
+struct IRDebugFuncDecoration : IRInst
+{
+ IR_LEAF_ISA(DebugFunctionDecoration)
+ IRInst* getDebugFunc() { return getOperand(0); }
+};
+
struct IRDebugLocationDecoration : IRDecoration
{
IRInst* getSource() { return getOperand(0); }
@@ -3943,6 +3997,21 @@ public:
IRInst* col,
IRInst* argIndex = nullptr);
IRInst* emitDebugValue(IRInst* debugVar, IRInst* debugValue);
+ IRInst* emitDebugInlinedAt(
+ IRInst* line,
+ IRInst* col,
+ IRInst* file,
+ IRInst* debugFunc,
+ IRInst* outerInlinedAt);
+ IRInst* emitDebugInlinedVariable(IRInst* variable, IRInst* inlinedAt);
+ IRInst* emitDebugScope(IRInst* scope, IRInst* inlinedAt);
+ IRInst* emitDebugNoScope();
+ IRInst* emitDebugFunction(
+ IRInst* name,
+ IRInst* line,
+ IRInst* col,
+ IRInst* file,
+ IRInst* debugType);
/// Emit an LiveRangeStart instruction indicating the referenced item is live following this
/// instruction
@@ -5012,6 +5081,11 @@ public:
getIntValue(getUIntType(), col));
}
+ void addDebugFunctionDecoration(IRInst* value, IRInst* debugFunction)
+ {
+ addDecoration(value, kIROp_DebugFunctionDecoration, debugFunction);
+ }
+
void addUnsafeForceInlineDecoration(IRInst* value)
{
addDecoration(value, kIROp_UnsafeForceInlineEarlyDecoration);
diff --git a/source/slang/slang-ir-strip-debug-info.cpp b/source/slang/slang-ir-strip-debug-info.cpp
index 8b2a07663..124c41a5c 100644
--- a/source/slang/slang-ir-strip-debug-info.cpp
+++ b/source/slang/slang-ir-strip-debug-info.cpp
@@ -13,6 +13,10 @@ static void findDebugInfo(IRInst* inst, List<IRInst*>& debugInstructions)
case kIROp_DebugLine:
case kIROp_DebugLocationDecoration:
case kIROp_DebugSource:
+ case kIROp_DebugInlinedAt:
+ case kIROp_DebugScope:
+ case kIROp_DebugNoScope:
+ case kIROp_DebugFunction:
debugInstructions.add(inst);
break;
default:
diff --git a/source/slang/slang-ir-validate.cpp b/source/slang/slang-ir-validate.cpp
index 11587d600..19de64618 100644
--- a/source/slang/slang-ir-validate.cpp
+++ b/source/slang/slang-ir-validate.cpp
@@ -244,6 +244,7 @@ void validateIRInstOperand(IRValidateContext* context, IRInst* inst, IRUse* oper
switch (inst->getOp())
{
case kIROp_DifferentiableTypeDictionaryItem:
+ case kIROp_DebugScope:
return;
}
//
diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp
index 98c0fa471..fb7d752d5 100644
--- a/source/slang/slang-ir.cpp
+++ b/source/slang/slang-ir.cpp
@@ -3411,6 +3411,53 @@ IRInst* IRBuilder::emitDebugValue(IRInst* debugVar, IRInst* debugValue)
args.getBuffer());
}
+IRInst* IRBuilder::emitDebugInlinedAt(
+ IRInst* line,
+ IRInst* col,
+ IRInst* file,
+ IRInst* debugFunc,
+ IRInst* outerInlinedAt)
+{
+ if (outerInlinedAt)
+ {
+ IRInst* args[] = {line, col, file, debugFunc, outerInlinedAt};
+ return emitIntrinsicInst(getVoidType(), kIROp_DebugInlinedAt, 5, args);
+ }
+ else
+ {
+ IRInst* args[] = {line, col, file, debugFunc};
+ return emitIntrinsicInst(getVoidType(), kIROp_DebugInlinedAt, 4, args);
+ }
+}
+
+IRInst* IRBuilder::emitDebugFunction(
+ IRInst* name,
+ IRInst* line,
+ IRInst* col,
+ IRInst* file,
+ IRInst* debugType)
+{
+ IRInst* args[] = {name, line, col, file, debugType};
+ return emitIntrinsicInst(getVoidType(), kIROp_DebugFunction, 5, args);
+}
+
+IRInst* IRBuilder::emitDebugInlinedVariable(IRInst* variable, IRInst* inlinedAt)
+{
+ IRInst* args[] = {variable, inlinedAt};
+ return emitIntrinsicInst(getVoidType(), kIROp_DebugInlinedVariable, 2, args);
+}
+
+IRInst* IRBuilder::emitDebugScope(IRInst* scope, IRInst* inlinedAt)
+{
+ IRInst* args[] = {scope, inlinedAt};
+ return emitIntrinsicInst(getVoidType(), kIROp_DebugScope, 2, args);
+}
+
+IRInst* IRBuilder::emitDebugNoScope()
+{
+ return emitIntrinsicInst(getVoidType(), kIROp_DebugNoScope, 0, nullptr);
+}
+
IRLiveRangeStart* IRBuilder::emitLiveRangeStart(IRInst* referenced)
{
// This instruction doesn't produce any result,
@@ -7941,8 +7988,8 @@ IRInstList<IRDecoration> IRInst::getDecorations()
IRInst* IRInst::getFirstChild()
{
// The children come after any decorations,
- // so if there are any decorations, then the
- // first child is right after the last decoration.
+ // so if there are any decorations, then
+ // the first child is right after the last decoration.
//
if (auto lastDecoration = getLastDecoration())
return lastDecoration->getNextInst();
@@ -8462,6 +8509,7 @@ bool IRInst::mightHaveSideEffects(SideEffectAnalysisOptions options)
case kIROp_RTTIObject:
case kIROp_RTTIType:
case kIROp_Func:
+ case kIROp_DebugFunction:
case kIROp_Generic:
case kIROp_Var:
case kIROp_Param:
diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp
index 2271e750e..c58eed1c1 100644
--- a/source/slang/slang-lower-to-ir.cpp
+++ b/source/slang/slang-lower-to-ir.cpp
@@ -11011,6 +11011,31 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
}
}
+ // Add debugfunction decoration and emit debug function. This
+ // is needed for emitting debug information
+ auto nameHint = irFunc->findDecoration<IRNameHintDecoration>();
+ IRStringLit* nameOperand = nameHint ? as<IRStringLit>(nameHint->getNameOperand()) : nullptr;
+ if (nameOperand)
+ {
+ getBuilder()->setInsertInto(getBuilder()->getModule()->getModuleInst());
+
+ auto locationDecor = irFunc->findDecoration<IRDebugLocationDecoration>();
+ IRInst* debugType = irFunc->getDataType();
+
+ if (locationDecor && debugType)
+ {
+ auto debugFuncCallee = getBuilder()->emitDebugFunction(
+ nameOperand,
+ locationDecor->getLine(),
+ locationDecor->getCol(),
+ locationDecor->getSource(),
+ debugType);
+
+ // Add a decoration to link the function to its debug function
+ getBuilder()->addDecoration(irFunc, kIROp_DebugFunctionDecoration, debugFuncCallee);
+ }
+ }
+
// For convenience, ensure that any additional global
// values that were emitted while outputting the function
// body appear before the function itself in the list