diff options
| author | Yong He <yonghe@outlook.com> | 2023-08-15 20:55:21 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-08-15 20:55:21 -0700 |
| commit | c16f711d83df90f6826a65d61fc56cdbb932c92c (patch) | |
| tree | 3680ff05b91aa4521fd5c5e9caa9de171cbac558 /source | |
| parent | 0c366bc0a4332ee14d08f2555396a18cb64229fa (diff) | |
SPIRV: debug source and debug line. (#3109)
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/slang-emit-c-like.cpp | 5 | ||||
| -rw-r--r-- | source/slang/slang-emit-spirv-ops-debug-info-ext.h | 28 | ||||
| -rw-r--r-- | source/slang/slang-emit-spirv.cpp | 171 | ||||
| -rw-r--r-- | source/slang/slang-ir-inst-defs.h | 4 | ||||
| -rw-r--r-- | source/slang/slang-ir-insts.h | 21 | ||||
| -rw-r--r-- | source/slang/slang-ir.cpp | 17 | ||||
| -rw-r--r-- | source/slang/slang-lower-to-ir.cpp | 58 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 3 |
8 files changed, 268 insertions, 39 deletions
diff --git a/source/slang/slang-emit-c-like.cpp b/source/slang/slang-emit-c-like.cpp index 337860771..e1f631283 100644 --- a/source/slang/slang-emit-c-like.cpp +++ b/source/slang/slang-emit-c-like.cpp @@ -2507,7 +2507,6 @@ void CLikeSourceEmitter::defaultEmitInstExpr(IRInst* inst, const EmitOpInfo& inO } break; } - default: diagnoseUnhandledInst(inst); break; @@ -2557,6 +2556,10 @@ void CLikeSourceEmitter::_emitInst(IRInst* inst) m_writer->emit(";\n"); break; + case kIROp_DebugSource: + case kIROp_DebugLine: + break; + // Insts that needs to be emitted as code blocks. case kIROp_CudaKernelLaunch: emitInstStmtImpl(inst); diff --git a/source/slang/slang-emit-spirv-ops-debug-info-ext.h b/source/slang/slang-emit-spirv-ops-debug-info-ext.h new file mode 100644 index 000000000..d1ca9665a --- /dev/null +++ b/source/slang/slang-emit-spirv-ops-debug-info-ext.h @@ -0,0 +1,28 @@ +#ifdef SLANG_IN_SPIRV_EMIT_CONTEXT + +// https://github.com/KhronosGroup/SPIRV-Registry/blob/main/nonsemantic/NonSemantic.Shader.DebugInfo.100.asciidoc#DebugCompilationUnit +template<typename T> +SpvInst* emitOpDebugCompilationUnit(SpvInstParent* parent, IRInst* inst, const T& idResultType, SpvInst* set, SpvInst* version, SpvInst* dwarfVersion, SpvInst* source, SpvInst* language) +{ + static_assert(isSingular<T>); + return emitInst(parent, inst, SpvOpExtInst, idResultType, kResultID, set, SpvWord(1), version, dwarfVersion, source, language); +} + +// https://github.com/KhronosGroup/SPIRV-Registry/blob/main/nonsemantic/NonSemantic.Shader.DebugInfo.100.asciidoc#DebugSource +template<typename T> +SpvInst* emitOpDebugSource(SpvInstParent* parent, IRInst* inst, const T& idResultType, SpvInst* set, IRInst* file, IRInst* text) +{ + static_assert(isSingular<T>); + return emitInst(parent, inst, SpvOpExtInst, idResultType, kResultID, set, SpvWord(35), file, text); +} + +// https://github.com/KhronosGroup/SPIRV-Registry/blob/main/nonsemantic/NonSemantic.Shader.DebugInfo.100.asciidoc#DebugLine +template<typename T> +SpvInst* emitOpDebugLine(SpvInstParent* parent, IRInst* inst, const T& idResultType, SpvInst* set, IRInst* source, IRInst* lineStart, IRInst* lineEnd, IRInst* colStart, IRInst* colEnd) +{ + static_assert(isSingular<T>); + return emitInst(parent, inst, SpvOpExtInst, idResultType, kResultID, set, SpvWord(103), source, lineStart, lineEnd, colStart, colEnd); +} + + +#endif // SLANG_IN_SPIRV_EMIT_CONTEXT diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp index d93a50756..a3051fadb 100644 --- a/source/slang/slang-emit-spirv.cpp +++ b/source/slang/slang-emit-spirv.cpp @@ -291,6 +291,41 @@ struct SpvLiteralBits { static SpvLiteralBits from32(uint32_t value) { return SpvLiteralBits{{value}}; } static SpvLiteralBits from64(uint64_t value) { return SpvLiteralBits{{SpvWord(value), SpvWord(value >> 32)}}; } + static SpvLiteralBits fromUnownedStringSlice(UnownedStringSlice text) + { + SpvLiteralBits result; + + // [Section 2.2.1 : Instructions] + // + // > Literal String: A nul-terminated stream of characters consuming + // > an integral number of words. The character set is Unicode in the + // > UTF-8 encoding scheme. The UTF-8 octets (8-bit bytes) are packed + // > four per word, following the little-endian convention (i.e., the + // > first octet is in the lowest-order 8 bits of the word). + // > The final word contains the string’s nul-termination character (0), and + // > all contents past the end of the string in the final word are padded with 0. + + // First work out the amount of words we'll need + const Index textCount = text.getLength(); + // Calculate the minimum amount of bytes needed - which needs to include terminating 0 + const Index minByteCount = textCount + 1; + // Calculate the amount of words including padding if necessary + const Index wordCount = (minByteCount + 3) >> 2; + + // Make space on the operand stack, keeping the free space start in operandStartIndex + result.value.setCount(wordCount); + + // Set dst to the start of the operand memory + char* dst = (char*)(result.value.getBuffer()); + + // Copy the text + memcpy(dst, text.begin(), textCount); + + // Set terminating 0, and remaining buffer 0s + memset(dst + textCount, 0, wordCount * sizeof(SpvWord) - textCount); + + return result; + } List<SpvWord> value; // Words, stored low words to high (TODO, SmallArray or something here) }; @@ -482,6 +517,9 @@ struct SPIRVEmitContext // `IRInst` may not have been emitted. Dictionary<IRInst*, SpvWord> m_mapIRInstToSpvID; + // Map a Slang IR instruction to the corresponding SPIR-V debug instruction. + Dictionary<IRInst*, SpvInst*> m_mapIRInstToSpvDebugInst; + /// Register that `irInst` maps to `spvInst` void registerInst(IRInst* irInst, SpvInst* spvInst) { @@ -498,6 +536,23 @@ struct SPIRVEmitContext } } + /// Register that `irInst` has debug info represented by `spvDebugInst`. + void registerDebugInst(IRInst* irInst, SpvInst* spvDebugInst) + { + m_mapIRInstToSpvDebugInst.add(irInst, spvDebugInst); + } + + SpvInst* findDebugScope(IRInst* inst) + { + for (auto parent = inst; parent; parent = parent->getParent()) + { + SpvInst* spvInst = nullptr; + if (m_mapIRInstToSpvDebugInst.tryGetValue(parent, spvInst)) + return spvInst; + } + return nullptr; + } + /// Get or reserve a SpvID for an IR value. SpvWord getIRInstSpvID(IRInst* inst) { @@ -728,36 +783,7 @@ struct SPIRVEmitContext // Assert that `text` doesn't contain any embedded nul bytes, since they // could lead to invalid encoded results. SLANG_ASSERT(text.indexOf(0) < 0); - - // [Section 2.2.1 : Instructions] - // - // > Literal String: A nul-terminated stream of characters consuming - // > an integral number of words. The character set is Unicode in the - // > UTF-8 encoding scheme. The UTF-8 octets (8-bit bytes) are packed - // > four per word, following the little-endian convention (i.e., the - // > first octet is in the lowest-order 8 bits of the word). - // > The final word contains the string’s nul-termination character (0), and - // > all contents past the end of the string in the final word are padded with 0. - - // First work out the amount of words we'll need - const Index textCount = text.getLength(); - // Calculate the minimum amount of bytes needed - which needs to include terminating 0 - const Index minByteCount = textCount + 1; - // Calculate the amount of words including padding if necessary - const Index wordCount = (minByteCount + 3) >> 2; - - // Make space on the operand stack, keeping the free space start in operandStartIndex - const Index operandStartIndex = m_operandStack.getCount(); - m_operandStack.setCount(operandStartIndex + wordCount); - - // Set dst to the start of the operand memory - char* dst = (char*)(m_operandStack.getBuffer() + operandStartIndex); - - // Copy the text - memcpy(dst, text.begin(), textCount); - - // Set terminating 0, and remaining buffer 0s - memset(dst + textCount, 0, wordCount * sizeof(SpvWord) - textCount); + emitOperand(SpvLiteralBits::fromUnownedStringSlice(text)); } // Sometimes we will want to pass down an argument that @@ -1014,6 +1040,7 @@ struct SPIRVEmitContext # define SLANG_IN_SPIRV_EMIT_CONTEXT # include "slang-emit-spirv-ops.h" + #include "slang-emit-spirv-ops-debug-info-ext.h" # undef SLANG_IN_SPIRV_EMIT_CONTEXT /// The SPIRV OpExtInstImport inst that represents the GLSL450 @@ -1031,6 +1058,21 @@ struct SPIRVEmitContext return m_glsl450ExtInst; } + /// The SPIRV OpExtInstImport inst that represents the NonSemantic debug info + /// extended instruction set. + SpvInst* m_NonSemanticDebugInfoExtInst = nullptr; + + SpvInst* getNonSemanticDebugInfoExtInst() + { + if (m_NonSemanticDebugInfoExtInst) + return m_NonSemanticDebugInfoExtInst; + m_NonSemanticDebugInfoExtInst = emitOpExtInstImport( + getSection(SpvLogicalSectionID::ExtIntInstImports), + nullptr, + UnownedStringSlice("NonSemantic.Shader.DebugInfo.100")); + return m_NonSemanticDebugInfoExtInst; + } + // Now that we've gotten the core infrastructure out of the way, // let's start looking at emitting some instructions that make // up a SPIR-V module. @@ -1152,7 +1194,6 @@ struct SPIRVEmitContext const FloatInfo i = getFloatingTypeInfo(as<IRType>(inst)); return emitOpTypeFloat(inst, SpvLiteralInteger::from32(int32_t(i.width))); } - case kIROp_PtrType: case kIROp_RefType: case kIROp_OutType: @@ -1342,6 +1383,7 @@ struct SPIRVEmitContext case kIROp_BoolLit: case kIROp_IntLit: case kIROp_FloatLit: + case kIROp_StringLit: return emitLit(inst); case kIROp_GlobalParam: @@ -1359,6 +1401,36 @@ struct SPIRVEmitContext dumpIRToString(g); SLANG_UNEXPECTED(e.getBuffer()); } + + case kIROp_DebugSource: + { + ensureExtensionDeclaration(UnownedStringSlice("SPV_KHR_non_semantic_info")); + auto debugSource = as<IRDebugSource>(inst); + auto result = emitOpDebugSource( + getSection(SpvLogicalSectionID::ConstantsAndTypes), + inst, + inst->getFullType(), + getNonSemanticDebugInfoExtInst(), + debugSource->getFileName(), + debugSource->getSource()); + auto moduleInst = inst->getModule()->getModuleInst(); + if (!m_mapIRInstToSpvDebugInst.containsKey(moduleInst)) + { + IRBuilder builder(inst); + builder.setInsertBefore(inst); + auto translationUnit = emitOpDebugCompilationUnit( + getSection(SpvLogicalSectionID::ConstantsAndTypes), + moduleInst, + inst->getFullType(), + getNonSemanticDebugInfoExtInst(), + emitIntConstant(100, builder.getUIntType()), // ExtDebugInfo version. + emitIntConstant(5, builder.getUIntType()), // DWARF version. + result, + emitIntConstant(6, builder.getUIntType())); // Language, use HLSL's ID for now. + registerDebugInst(moduleInst, translationUnit); + } + return result; + } default: { String e = "Unhandled global inst in spirv-emit:\n" @@ -1909,6 +1981,9 @@ struct SPIRVEmitContext } case kIROp_MakeArray: return emitConstruct(parent, inst); + + case kIROp_DebugLine: + return emitDebugLine(parent, as<IRDebugLine>(inst)); } } @@ -1944,6 +2019,11 @@ struct SPIRVEmitContext ); } } + case kIROp_StringLit: + { + auto value = as<IRStringLit>(inst)->getStringSlice(); + return emitInst(getSection(SpvLogicalSectionID::DebugStringsAndSource), inst, SpvOpString, kResultID, SpvLiteralBits::fromUnownedStringSlice(value)); + } default: return nullptr; } @@ -2085,6 +2165,17 @@ struct SPIRVEmitContext name, params ); + + // Stage specific execution mode declarations. + switch (entryPointDecor->getProfile().getStage()) + { + case Stage::Fragment: + //OpExecutionMode %main OriginUpperLeft + emitInst(getSection(SpvLogicalSectionID::ExecutionModes), nullptr, SpvOpExecutionMode, dstID, SpvExecutionModeOriginUpperLeft); + break; + default: + break; + } } break; @@ -3138,6 +3229,19 @@ struct SPIRVEmitContext SLANG_UNREACHABLE("Arithmetic op with 0 or more than 2 operands"); } + SpvInst* emitDebugLine(SpvInstParent* parent, IRDebugLine* debugLine) + { + auto scope = findDebugScope(debugLine); + if (!scope) + return nullptr; + return emitOpDebugLine(parent, debugLine, debugLine->getFullType(), getNonSemanticDebugInfoExtInst(), + debugLine->getSource(), + debugLine->getLineStart(), + debugLine->getLineEnd(), + debugLine->getColStart(), + debugLine->getColEnd()); + } + OrderedHashSet<SpvCapability> m_capabilities; void requireSPIRVCapability(SpvCapability capability) @@ -3212,6 +3316,11 @@ SlangResult emitSPIRVFromIR( #endif context.emitFrontMatter(); + for (auto inst : irModule->getGlobalInsts()) + { + if (as<IRDebugSource>(inst)) + context.ensureInst(inst); + } for (auto irEntryPoint : irEntryPoints) { context.ensureInst(irEntryPoint); diff --git a/source/slang/slang-ir-inst-defs.h b/source/slang/slang-ir-inst-defs.h index 623fca32f..3007351d2 100644 --- a/source/slang/slang-ir-inst-defs.h +++ b/source/slang/slang-ir-inst-defs.h @@ -1041,6 +1041,10 @@ INST(ExistentialTypeSpecializationDictionary, ExistentialTypeSpecializationDicti /* Differentiable Type Dictionary */ INST(DifferentiableTypeDictionaryItem, DifferentiableTypeDictionaryItem, 0, 0) +/* DebugInfo */ +INST(DebugSource, DebugSource, 2, HOISTABLE) +INST(DebugLine, DebugLine, 5, 0) + #undef PARENT #undef USE_OTHER #undef INST_RANGE diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h index 4af21c50a..3503ece79 100644 --- a/source/slang/slang-ir-insts.h +++ b/source/slang/slang-ir-insts.h @@ -2855,6 +2855,24 @@ struct IRCastFloatToInt : IRInst IR_LEAF_ISA(CastFloatToInt) }; +struct IRDebugSource : IRInst +{ + IR_LEAF_ISA(DebugSource) + IRInst* getFileName() { return getOperand(0); } + IRInst* getSource() { return getOperand(1); } +}; + +struct IRDebugLine : IRInst +{ + IR_LEAF_ISA(DebugLine) + IRInst* getSource() { return getOperand(0); } + IRInst* getLineStart() { return getOperand(1); } + IRInst* getLineEnd() { return getOperand(2); } + IRInst* getColStart() { return getOperand(3); } + IRInst* getColEnd() { return getOperand(4); } +}; + + struct IRBuilderSourceLocRAII; struct IRBuilder @@ -3184,6 +3202,9 @@ public: return getAttributedType(baseType, attributes.getCount(), attributes.getBuffer()); } + IRInst* emitDebugSource(UnownedStringSlice fileName, UnownedStringSlice source); + IRInst* emitDebugLine(IRInst* source, IRIntegerValue lineStart, IRIntegerValue lineEnd, IRIntegerValue colStart, IRIntegerValue colEnd); + /// Emit an LiveRangeStart instruction indicating the referenced item is live following this instruction IRLiveRangeStart* emitLiveRangeStart(IRInst* referenced); diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp index 131c8d407..421c60d1d 100644 --- a/source/slang/slang-ir.cpp +++ b/source/slang/slang-ir.cpp @@ -3123,6 +3123,23 @@ namespace Slang { return emitIntrinsicInst((IRType*)type, kIROp_OutImplicitCast, 1, &value); } + IRInst* IRBuilder::emitDebugSource(UnownedStringSlice fileName, UnownedStringSlice source) + { + IRInst* args[] = { getStringValue(fileName), getStringValue(source) }; + return emitIntrinsicInst(getVoidType(), kIROp_DebugSource, 2, args); + } + IRInst* IRBuilder::emitDebugLine(IRInst* source, IRIntegerValue lineStart, IRIntegerValue lineEnd, IRIntegerValue colStart, IRIntegerValue colEnd) + { + IRInst* args[] = + { + source, + getIntValue(getIntType(), lineStart), + getIntValue(getIntType(), lineEnd), + getIntValue(getIntType(), colStart), + getIntValue(getIntType(), colEnd) + }; + return emitIntrinsicInst(getVoidType(), kIROp_DebugLine, 5, args); + } IRLiveRangeStart* IRBuilder::emitLiveRangeStart(IRInst* referenced) { // This instruction doesn't produce any result, diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index f89f92711..74ece5bc9 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -494,6 +494,8 @@ struct SharedIRGenContext Dictionary<Stmt*, IRBlock*> breakLabels; Dictionary<Stmt*, IRBlock*> continueLabels; + Dictionary<SourceFile*, IRInst*> mapSourceFileToDebugSourceInst; + void setGlobalValue(Decl* decl, LoweredValInfo value) { globalEnv.mapDeclToValue[decl] = value; @@ -503,17 +505,20 @@ struct SharedIRGenContext Session* session, DiagnosticSink* sink, bool obfuscateCode, - ModuleDecl* mainModuleDecl = nullptr) + ModuleDecl* mainModuleDecl, + Linkage* linkage) : m_session(session) , m_sink(sink) , m_obfuscateCode(obfuscateCode) , m_mainModuleDecl(mainModuleDecl) + , m_linkage(linkage) {} Session* m_session = nullptr; DiagnosticSink* m_sink = nullptr; bool m_obfuscateCode = false; ModuleDecl* m_mainModuleDecl = nullptr; + Linkage* m_linkage = nullptr; // List of all string literals used in user code, regardless // of how they were used (i.e., whether or not they were hashed). @@ -556,6 +561,8 @@ struct IRGenContext // The IR witness value to use for `ThisType` IRInst* thisTypeWitness = nullptr; + bool includeDebugInfo = false; + explicit IRGenContext(SharedIRGenContext* inShared, ASTBuilder* inAstBuilder) : shared(inShared) , astBuilder(inAstBuilder) @@ -588,6 +595,11 @@ struct IRGenContext return shared->m_mainModuleDecl; } + Linkage* getLinkage() + { + return shared->m_linkage; + } + LoweredValInfo* findLoweredDecl(Decl* decl) { IRGenEnv* envToFindIn = env; @@ -5712,6 +5724,24 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor> } }; +void maybeEmitDebugLine(IRGenContext* context, Stmt* stmt) +{ + if (!context->includeDebugInfo) + return; + if (as<EmptyStmt>(stmt)) + return; + auto sourceView = context->getLinkage()->getSourceManager()->findSourceView(stmt->loc); + if (!sourceView) + return; + auto source = sourceView->getSourceFile(); + IRInst* debugSourceInst = nullptr; + if (context->shared->mapSourceFileToDebugSourceInst.tryGetValue(source, debugSourceInst)) + { + auto humaneLoc = context->getLinkage()->getSourceManager()->getHumaneLoc(stmt->loc, SourceLocType::Emit); + context->irBuilder->emitDebugLine(debugSourceInst, humaneLoc.line, humaneLoc.line, humaneLoc.column, humaneLoc.column + 1); + } +} + void lowerStmt( IRGenContext* context, Stmt* stmt) @@ -5723,6 +5753,7 @@ void lowerStmt( try { + maybeEmitDebugLine(context, stmt); visitor.dispatch(stmt); } // Don't emit any context message for an explicit `AbortCompilationException` @@ -9542,7 +9573,8 @@ RefPtr<IRModule> generateIRForTranslationUnit( session, translationUnit->compileRequest->getSink(), translationUnit->compileRequest->getLinkage()->m_obfuscateCode, - translationUnit->getModuleDecl()); + translationUnit->getModuleDecl(), + translationUnit->compileRequest->getLinkage()); SharedIRGenContext* sharedContext = &sharedContextStorage; IRGenContext contextStorage(sharedContext, astBuilder); @@ -9554,10 +9586,22 @@ RefPtr<IRModule> generateIRForTranslationUnit( IRBuilder* builder = &builderStorage; context->irBuilder = builder; + context->includeDebugInfo = compileRequest->getLinkage()->debugInfoLevel != DebugInfoLevel::None; // We need to emit IR for all public/exported symbols // in the translation unit. // + // If debug info is enabled, we emit the DebugSource insts for each source file into IR. + if (context->includeDebugInfo) + { + builder->setInsertInto(module->getModuleInst()); + for (auto source : translationUnit->getSourceFiles()) + { + auto debugSource = builder->emitDebugSource(source->getPathInfo().getMostUniqueIdentity().getUnownedSlice(), source->getContent()); + context->shared->mapSourceFileToDebugSourceInst.add(source, debugSource); + } + } + // For now, we will assume that *all* global-scope declarations // represent public/exported symbols. @@ -9781,7 +9825,9 @@ struct SpecializedComponentTypeIRGenContext : ComponentTypeVisitor SharedIRGenContext sharedContextStorage( session, sink, - linkage->m_obfuscateCode + linkage->m_obfuscateCode, + nullptr, + linkage ); SharedIRGenContext* sharedContext = &sharedContextStorage; @@ -9918,7 +9964,7 @@ struct TypeConformanceIRGenContext linkage = typeConformance->getLinkage(); session = linkage->getSessionImpl(); - SharedIRGenContext sharedContextStorage(session, sink, linkage->m_obfuscateCode); + SharedIRGenContext sharedContextStorage(session, sink, linkage->m_obfuscateCode, nullptr, linkage); SharedIRGenContext* sharedContext = &sharedContextStorage; IRGenContext contextStorage(sharedContext, linkage->getASTBuilder()); @@ -10268,7 +10314,9 @@ RefPtr<IRModule> TargetProgram::createIRModuleForLayout(DiagnosticSink* sink) SharedIRGenContext sharedContextStorage( session, sink, - linkage->m_obfuscateCode); + linkage->m_obfuscateCode, + nullptr, + linkage); auto sharedContext = &sharedContextStorage; ASTBuilder* astBuilder = linkage->getASTBuilder(); diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index 71c705246..da818984b 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -170,6 +170,7 @@ void Session::init() // Built in linkage uses the built in builder m_builtinLinkage = new Linkage(this, builtinAstBuilder, nullptr); + m_builtinLinkage->debugInfoLevel = DebugInfoLevel::None; // Because the `Session` retains the builtin `Linkage`, // we need to make sure that the parent pointer inside @@ -2425,8 +2426,6 @@ void FrontEndCompileRequest::generateIR() // * it can generate diagnostics /// Generate IR for translation unit. - /// TODO(JS): Use the linkage ASTBuilder, because it seems possible that cross module constructs are possible in - /// ir lowering. RefPtr<IRModule> irModule(generateIRForTranslationUnit(getLinkage()->getASTBuilder(), translationUnit)); if (verifyDebugSerialization) |
