Skip to content

Commit 095a18a

Browse files
authored
Fix and test infinite recursion in LLVM debug data emission (#11108)
There's a bug with the debug info in the LLVM emitter, where a struct that references itself (even through a pointer) causes infinite recursion and the compiler crashing. For example, the following would cause the crash when `-g3` is specified: ```slang struct SelfReferential { SelfReferential* self; } ``` This is kind-of a hotfix, because it is blocking me in my own project (but I'm already running my own branch with all of my PRs merged on that one, so no rush to actually merge this.) This simple approach can result in the loss of some type info in debug data when these problematic cycles are formed; there may be a better way that would retain this info, but that would likely require more thorough refactoring.
1 parent 29cd386 commit 095a18a

4 files changed

Lines changed: 87 additions & 13 deletions

File tree

source/slang-llvm/slang-llvm-builder.cpp

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,10 @@ class LLVMBuilder : public ComBaseObject, public ILLVMBuilder
365365
LLVMDebugNode* file,
366366
int line) override;
367367
SLANG_NO_THROW LLVMDebugNode* SLANG_MCALL
368+
getDebugForwardDeclareType(CharSlice name, LLVMDebugNode* file, int line) override;
369+
SLANG_NO_THROW void SLANG_MCALL
370+
replaceDebugForwardDeclareType(LLVMDebugNode* replace, LLVMDebugNode* with) override;
371+
SLANG_NO_THROW LLVMDebugNode* SLANG_MCALL
368372
getDebugFunctionType(LLVMDebugNode* returnType, Slice<LLVMDebugNode*> paramTypes) override;
369373
SLANG_NO_THROW LLVMDebugNode* SLANG_MCALL getDebugFunction(
370374
LLVMDebugNode* funcType,
@@ -719,16 +723,16 @@ void LLVMBuilder::finalize()
719723
// llvmModule->print(rso, nullptr);
720724
// printf("%s\n", out.c_str());
721725

726+
// Debug info must be finalized before verifyModule, as otherwise the
727+
// unresolved debug nodes can cause verification errors.
728+
if (options.debugLevel != SLANG_DEBUG_INFO_LEVEL_NONE)
729+
llvmDebugBuilder->finalize();
730+
722731
llvm::verifyModule(*llvmModule, &llvm::errs());
723732

724733
// O0 is separately handled inside `optimize()`; we need to call it in
725734
// any case to make sure that `ForceInline` functions get inlined.
726735
optimize();
727-
728-
if (options.debugLevel != SLANG_DEBUG_INFO_LEVEL_NONE)
729-
{
730-
llvmDebugBuilder->finalize();
731-
}
732736
}
733737

734738
void LLVMBuilder::emitGlobalLLVMIR(const std::string& textIR)
@@ -1800,6 +1804,29 @@ LLVMDebugNode* LLVMBuilder::getDebugStructType(
18001804
fieldTypes);
18011805
}
18021806

1807+
LLVMDebugNode* LLVMBuilder::getDebugForwardDeclareType(
1808+
CharSlice name,
1809+
LLVMDebugNode* file,
1810+
int line)
1811+
{
1812+
if (!file)
1813+
file = compileUnit->getFile();
1814+
llvm::DIFile* llvmFile = llvm::cast<llvm::DIFile>(file);
1815+
1816+
return llvmDebugBuilder->createReplaceableCompositeType(
1817+
llvm::dwarf::DW_TAG_structure_type,
1818+
charSliceToLLVM(name),
1819+
llvmFile,
1820+
llvmFile,
1821+
line);
1822+
}
1823+
1824+
void LLVMBuilder::replaceDebugForwardDeclareType(LLVMDebugNode* replace, LLVMDebugNode* with)
1825+
{
1826+
llvm::TempMDNode fwdDecl(replace);
1827+
llvmDebugBuilder->replaceTemporary(std::move(fwdDecl), with);
1828+
}
1829+
18031830
LLVMDebugNode* LLVMBuilder::getDebugFunctionType(
18041831
LLVMDebugNode* returnType,
18051832
Slice<LLVMDebugNode*> paramTypes)
@@ -2349,7 +2376,7 @@ SlangResult LLVMBuilder::generateJITLibrary(IArtifact** outArtifact)
23492376

23502377
} // namespace slang_llvm
23512378

2352-
extern "C" SLANG_DLL_EXPORT SlangResult createLLVMBuilder_V2(
2379+
extern "C" SLANG_DLL_EXPORT SlangResult createLLVMBuilder_V3(
23532380
const SlangUUID& intfGuid,
23542381
Slang::ILLVMBuilder** out,
23552382
Slang::LLVMBuilderOptions options,

source/slang-llvm/slang-llvm-builder.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,12 @@ class ILLVMBuilder : public ICastable
326326
int64_t alignment,
327327
LLVMDebugNode* file,
328328
int line) = 0;
329+
330+
virtual SLANG_NO_THROW LLVMDebugNode* SLANG_MCALL
331+
getDebugForwardDeclareType(CharSlice name, LLVMDebugNode* file, int line) = 0;
332+
virtual SLANG_NO_THROW void SLANG_MCALL
333+
replaceDebugForwardDeclareType(LLVMDebugNode* replace, LLVMDebugNode* with) = 0;
334+
329335
virtual SLANG_NO_THROW LLVMDebugNode* SLANG_MCALL
330336
getDebugFunctionType(LLVMDebugNode* returnType, Slice<LLVMDebugNode*> paramTypes) = 0;
331337
virtual SLANG_NO_THROW LLVMDebugNode* SLANG_MCALL getDebugFunction(

source/slang/slang-emit-llvm.cpp

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ class LLVMTypeTranslator
168168
Dictionary<IRTypeLayoutRules*, Dictionary<IRType*, LLVMDebugNode*>> debugTypeMap;
169169

170170
public:
171+
Dictionary<LLVMDebugNode*, LLVMDebugNode*> forwardDeclaredDebugTypes;
172+
171173
LLVMTypeTranslator(
172174
ILLVMBuilder* builder,
173175
TargetRequest* targetReq,
@@ -453,6 +455,20 @@ class LLVMTypeTranslator
453455
int line;
454456
findDebugLocation(*instToDebugLLVM, structType, file, line);
455457

458+
CharSlice linkageName, prettyName;
459+
maybeGetName(&linkageName, &prettyName, type);
460+
461+
LLVMDebugNode* forwardDeclaredType = nullptr;
462+
{
463+
// We forward-declare the type itself initially, so that
464+
// self-referential structs get some useful debug data as
465+
// well. These get replaced later with the final type.
466+
auto& types = debugTypeMap[rules];
467+
forwardDeclaredType =
468+
builder->getDebugForwardDeclareType(prettyName, file, line);
469+
types[type] = forwardDeclaredType;
470+
}
471+
456472
List<LLVMDebugNode*> types;
457473
for (auto field : structType->getFields())
458474
{
@@ -466,20 +482,18 @@ class LLVMTypeTranslator
466482
IRIntegerValue offset = getOffset(field, rules);
467483

468484
IRStructKey* key = field->getKey();
469-
CharSlice linkageName, prettyName;
470-
maybeGetName(&linkageName, &prettyName, key);
485+
CharSlice fieldLinkageName, fieldPrettyName;
486+
maybeGetName(&fieldLinkageName, &fieldPrettyName, key);
471487

472488
types.add(builder->getDebugStructField(
473489
debugType,
474-
prettyName,
490+
fieldPrettyName,
475491
offset,
476492
fieldSizeAndAlignment.size,
477493
fieldSizeAndAlignment.alignment,
478494
file,
479495
line));
480496
}
481-
CharSlice linkageName, prettyName;
482-
maybeGetName(&linkageName, &prettyName, type);
483497
sizeAndAlignment.size = align(sizeAndAlignment.size, sizeAndAlignment.alignment);
484498

485499
llvmType = builder->getDebugStructType(
@@ -489,6 +503,7 @@ class LLVMTypeTranslator
489503
sizeAndAlignment.alignment,
490504
file,
491505
line);
506+
forwardDeclaredDebugTypes[forwardDeclaredType] = llvmType;
492507
}
493508
break;
494509

@@ -688,13 +703,13 @@ struct LLVMEmitter
688703
return SLANG_FAIL;
689704
}
690705

691-
using BuilderFuncV2 = SlangResult (*)(
706+
using BuilderFuncV3 = SlangResult (*)(
692707
const SlangUUID& intfGuid,
693708
Slang::ILLVMBuilder** out,
694709
Slang::LLVMBuilderOptions options,
695710
Slang::IArtifact** outErrorArtifact);
696711

697-
auto builderFunc = (BuilderFuncV2)library->findFuncByName("createLLVMBuilder_V2");
712+
auto builderFunc = (BuilderFuncV3)library->findFuncByName("createLLVMBuilder_V3");
698713
if (!builderFunc)
699714
return SLANG_FAIL;
700715

@@ -2862,6 +2877,12 @@ struct LLVMEmitter
28622877
emitGlobalDeclarations(irModule);
28632878
emitGlobalFunctions(irModule);
28642879
emitGlobalInstructionCtor();
2880+
2881+
// Some debug types may have been left as forward declared if they
2882+
// were self-referential (e.g., struct containing a pointer to itself).
2883+
// We have to resolve these last.
2884+
for (auto [fwdDecl, concrete] : types->forwardDeclaredDebugTypes)
2885+
builder->replaceDebugForwardDeclareType(fwdDecl, concrete);
28652886
}
28662887
};
28672888

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// This test just needs to run without crashing.
2+
//TEST(compute):COMPARE_COMPUTE_EX(filecheck-buffer=CHECK): -llvm -compute -Xslang -O0 -Xslang -g3
3+
//TEST_INPUT:set outputBuffer = out ubuffer(data=[0 0 0 0], stride=4)
4+
RWStructuredBuffer<int> outputBuffer;
5+
6+
struct SelfReferential
7+
{
8+
// There used to be a bug in the debug data emission here, where this would
9+
// cause infinite recursion inside the LLVM emitter.
10+
SelfReferential* self;
11+
}
12+
13+
[numthreads(1,1,1)]
14+
void computeMain(uint2 tid : SV_DispatchThreadID)
15+
{
16+
SelfReferential self;
17+
self.self = &self;
18+
// CHECK: 0
19+
outputBuffer[0] = 0;
20+
}

0 commit comments

Comments
 (0)