diff options
Diffstat (limited to 'source/slang')
| -rw-r--r-- | source/slang/slang-compiler-options.h | 5 | ||||
| -rw-r--r-- | source/slang/slang-compiler.cpp | 91 | ||||
| -rw-r--r-- | source/slang/slang-compiler.h | 77 | ||||
| -rw-r--r-- | source/slang/slang-emit-c-like.cpp | 2 | ||||
| -rw-r--r-- | source/slang/slang-emit-spirv-ops-debug-info-ext.h | 22 | ||||
| -rw-r--r-- | source/slang/slang-emit-spirv.cpp | 16 | ||||
| -rw-r--r-- | source/slang/slang-emit.cpp | 341 | ||||
| -rw-r--r-- | source/slang/slang-ir-inst-defs.h | 1 | ||||
| -rw-r--r-- | source/slang/slang-ir-insts.h | 9 | ||||
| -rw-r--r-- | source/slang/slang-ir-link.cpp | 5 | ||||
| -rw-r--r-- | source/slang/slang-ir-strip-debug-info.cpp | 1 | ||||
| -rw-r--r-- | source/slang/slang-ir.cpp | 7 | ||||
| -rw-r--r-- | source/slang/slang-options.cpp | 14 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 57 |
14 files changed, 628 insertions, 20 deletions
diff --git a/source/slang/slang-compiler-options.h b/source/slang/slang-compiler-options.h index dd413bee4..7205e1696 100644 --- a/source/slang/slang-compiler-options.h +++ b/source/slang/slang-compiler-options.h @@ -365,6 +365,11 @@ struct CompilerOptionSet bool shouldHaveSourceMap() { return !getBoolOption(CompilerOptionName::DisableSourceMap); } + bool shouldEmitSeparateDebugInfo() + { + return getBoolOption(CompilerOptionName::EmitSeparateDebug); + } + FloatingPointMode getFloatingPointMode() { return getEnumOption<FloatingPointMode>(CompilerOptionName::FloatingPointMode); diff --git a/source/slang/slang-compiler.cpp b/source/slang/slang-compiler.cpp index 86dcf5d7f..4e114eb78 100644 --- a/source/slang/slang-compiler.cpp +++ b/source/slang/slang-compiler.cpp @@ -1860,6 +1860,23 @@ static CodeGenTarget _getIntermediateTarget(CodeGenTarget target) } } +static IArtifact* _getSeparateDbgArtifact(IArtifact* artifact) +{ + if (!artifact) + return nullptr; + + // The first associated artifact of kind ObjectCode and SPIRV payload should be the debug + // artifact. + for (auto* associated : artifact->getAssociated()) + { + auto desc = associated->getDesc(); + if (desc.kind == ArtifactKind::ObjectCode && desc.payload == ArtifactPayload::SPIRV) + return associated; + } + + return nullptr; +} + /// Function to simplify the logic around emitting, and dissassembling SlangResult CodeGenContext::_emitEntryPoints(ComPtr<IArtifact>& outArtifact) { @@ -1890,6 +1907,32 @@ SlangResult CodeGenContext::_emitEntryPoints(ComPtr<IArtifact>& outArtifact) getSink(), disassemblyArtifact.writeRef())); + // Also disassemble the debug artifact if one exists. + auto debugArtifact = _getSeparateDbgArtifact(intermediateArtifact); + ComPtr<IArtifact> disassemblyDebugArtifact; + if (debugArtifact) + { + SLANG_RETURN_ON_FAIL(ArtifactOutputUtil::dissassembleWithDownstream( + getSession(), + debugArtifact, + getSink(), + disassemblyDebugArtifact.writeRef())); + disassemblyDebugArtifact->setName(debugArtifact->getName()); + + // The disassembly needs both the metadata for the debug build identifier + // and the debug spirv to be associated with is. + for (auto associated : intermediateArtifact->getAssociated()) + { + if (associated->getDesc().payload == ArtifactPayload::Metadata || + associated->getDesc().payload == ArtifactPayload::PostEmitMetadata) + { + disassemblyArtifact->addAssociated(associated); + break; + } + } + disassemblyArtifact->addAssociated(disassemblyDebugArtifact); + } + outArtifact.swap(disassemblyArtifact); return SLANG_OK; } @@ -2113,6 +2156,46 @@ SlangResult EndToEndCompileRequest::_maybeWriteArtifact(const String& path, IArt return SLANG_OK; } +// These helper functions are used by the -separate-debug-info command line +// arg to extract the associated artifact containing the debug SPIRV data +// and save it to a file with a .dbg.spv extension. +static String _getDebugSpvPath(const String& basePath) +{ + // Find the last occurrence of ".spv" at the end of the string. + static const char ext[] = ".spv"; + static const char dbgExt[] = ".dbg.spv"; + Index extLen = 4; + if (basePath.getLength() >= extLen && basePath.endsWith(ext)) + { + // Replace the ".spv" extension with ".dbg.spv" + String prefix = String(basePath.subString(0, basePath.getLength() - extLen)); + return prefix + dbgExt; + } + // If it doesn't end with .spv, just append .dbg.spv + return basePath + dbgExt; +} + +SlangResult EndToEndCompileRequest::_maybeWriteDebugArtifact( + TargetProgram* targetProgram, + const String& path, + IArtifact* artifact) +{ + if (targetProgram->getOptionSet().getBoolOption(CompilerOptionName::EmitSeparateDebug)) + { + const auto dbgArtifact = _getSeparateDbgArtifact(artifact); + // The artifact's name may have been set to the debug build id hash, use + // it as the filename if it exists. + String dbgPath = dbgArtifact->getName(); + if (dbgPath.getLength() == 0) + dbgPath = _getDebugSpvPath(path); + else + dbgPath.append(".dbg.spv"); + return _maybeWriteArtifact(dbgPath, dbgArtifact); + } + + return SLANG_OK; +} + IArtifact* TargetProgram::_createWholeProgramResult( DiagnosticSink* sink, EndToEndCompileRequest* endToEndReq) @@ -2612,6 +2695,10 @@ void EndToEndCompileRequest::generateOutput() const auto path = _getWholeProgramPath(targetReq); _maybeWriteArtifact(path, artifact); + + // If we are compiling separate debug info, check for the additional + // SPIRV artifact and write that if needed. + _maybeWriteDebugArtifact(targetProgram, path, artifact); } } else @@ -2624,6 +2711,10 @@ void EndToEndCompileRequest::generateOutput() const auto path = _getEntryPointPath(targetReq, ee); _maybeWriteArtifact(path, artifact); + + // If we are compiling separate debug info, check for the additional + // SPIRV artifact and write that if needed. + _maybeWriteDebugArtifact(targetProgram, path, artifact); } } } diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h index 861719aac..24ddd00cc 100644 --- a/source/slang/slang-compiler.h +++ b/source/slang/slang-compiler.h @@ -324,6 +324,16 @@ public: slang::IMetadata** outMetadata, slang::IBlob** outDiagnostics = nullptr) SLANG_OVERRIDE; + SLANG_NO_THROW SlangResult SLANG_MCALL getEntryPointCompileResult( + SlangInt entryPointIndex, + SlangInt targetIndex, + slang::ICompileResult** outCompileResult, + slang::IBlob** outDiagnostics) SLANG_OVERRIDE; + SLANG_NO_THROW SlangResult SLANG_MCALL getTargetCompileResult( + SlangInt targetIndex, + slang::ICompileResult** outCompileResult, + slang::IBlob** outDiagnostics = nullptr) SLANG_OVERRIDE; + SLANG_NO_THROW SlangResult SLANG_MCALL getResultAsFileSystem( SlangInt entryPointIndex, SlangInt targetIndex, @@ -974,6 +984,27 @@ public: return Super::getTargetMetadata(targetIndex, outMetadata, outDiagnostics); } + SLANG_NO_THROW SlangResult SLANG_MCALL getEntryPointCompileResult( + SlangInt entryPointIndex, + SlangInt targetIndex, + slang::ICompileResult** outCompileResult, + slang::IBlob** outDiagnostics) SLANG_OVERRIDE + { + return Super::getEntryPointCompileResult( + entryPointIndex, + targetIndex, + outCompileResult, + outDiagnostics); + } + + SLANG_NO_THROW SlangResult SLANG_MCALL getTargetCompileResult( + SlangInt targetIndex, + slang::ICompileResult** outCompileResult, + slang::IBlob** outDiagnostics) SLANG_OVERRIDE + { + return Super::getTargetCompileResult(targetIndex, outCompileResult, outDiagnostics); + } + SLANG_NO_THROW SlangResult SLANG_MCALL getResultAsFileSystem( SlangInt entryPointIndex, SlangInt targetIndex, @@ -1253,6 +1284,27 @@ public: return Super::getTargetMetadata(targetIndex, outMetadata, outDiagnostics); } + SLANG_NO_THROW SlangResult SLANG_MCALL getEntryPointCompileResult( + SlangInt entryPointIndex, + SlangInt targetIndex, + slang::ICompileResult** outCompileResult, + slang::IBlob** outDiagnostics) SLANG_OVERRIDE + { + return Super::getEntryPointCompileResult( + entryPointIndex, + targetIndex, + outCompileResult, + outDiagnostics); + } + + SLANG_NO_THROW SlangResult SLANG_MCALL getTargetCompileResult( + SlangInt targetIndex, + slang::ICompileResult** outCompileResult, + slang::IBlob** outDiagnostics) SLANG_OVERRIDE + { + return Super::getTargetCompileResult(targetIndex, outCompileResult, outDiagnostics); + } + SLANG_NO_THROW SlangResult SLANG_MCALL getResultAsFileSystem( SlangInt entryPointIndex, SlangInt targetIndex, @@ -1581,6 +1633,27 @@ public: return Super::getTargetMetadata(targetIndex, outMetadata, outDiagnostics); } + SLANG_NO_THROW SlangResult SLANG_MCALL getEntryPointCompileResult( + SlangInt entryPointIndex, + SlangInt targetIndex, + slang::ICompileResult** outCompileResult, + slang::IBlob** outDiagnostics) SLANG_OVERRIDE + { + return Super::getEntryPointCompileResult( + entryPointIndex, + targetIndex, + outCompileResult, + outDiagnostics); + } + + SLANG_NO_THROW SlangResult SLANG_MCALL getTargetCompileResult( + SlangInt targetIndex, + slang::ICompileResult** outCompileResult, + slang::IBlob** outDiagnostics) SLANG_OVERRIDE + { + return Super::getTargetCompileResult(targetIndex, outCompileResult, outDiagnostics); + } + /// Get a serialized representation of the checked module. virtual SLANG_NO_THROW SlangResult SLANG_MCALL serialize(ISlangBlob** outSerializedBlob) override; @@ -3356,6 +3429,10 @@ private: /// Maybe write the artifact to the path (if set), or stdout (if there is no container or path) SlangResult _maybeWriteArtifact(const String& path, IArtifact* artifact); + SlangResult _maybeWriteDebugArtifact( + TargetProgram* targetProgram, + const String& path, + IArtifact* artifact); SlangResult _writeArtifact(const String& path, IArtifact* artifact); /// Adds any extra settings to complete a targetRequest diff --git a/source/slang/slang-emit-c-like.cpp b/source/slang/slang-emit-c-like.cpp index 0534b64d4..0092d159a 100644 --- a/source/slang/slang-emit-c-like.cpp +++ b/source/slang/slang-emit-c-like.cpp @@ -3210,6 +3210,7 @@ void CLikeSourceEmitter::_emitInst(IRInst* inst) case kIROp_DebugNoScope: case kIROp_DebugInlinedVariable: case kIROp_DebugFunction: + case kIROp_DebugBuildIdentifier: break; case kIROp_Unmodified: @@ -5214,6 +5215,7 @@ void CLikeSourceEmitter::ensureGlobalInst( case kIROp_DebugSource: case kIROp_DebugValue: case kIROp_DebugInlinedVariable: + case kIROp_DebugBuildIdentifier: 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 d8826afdc..90a0d44a8 100644 --- a/source/slang/slang-emit-spirv-ops-debug-info-ext.h +++ b/source/slang/slang-emit-spirv-ops-debug-info-ext.h @@ -681,4 +681,26 @@ SpvInst* emitOpDebugForwardRefsComposite( flags); } +template<typename T> +SpvInst* emitOpDebugBuildIdentifier( + SpvInstParent* parent, + IRInst* inst, + const T& idResultType, + SpvInst* set, + IRInst* buildIdentifier, + IRInst* flags) +{ + static_assert(isSingular<T>); + return emitInst( + parent, + inst, + SpvOpExtInst, + idResultType, + kResultID, + set, + SpvWord(105), + buildIdentifier, + flags); +} + #endif // SLANG_IN_SPIRV_EMIT_CONTEXT diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp index 0a3dab78a..ec9857ecf 100644 --- a/source/slang/slang-emit-spirv.cpp +++ b/source/slang/slang-emit-spirv.cpp @@ -2284,6 +2284,18 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex } return result; } + case kIROp_DebugBuildIdentifier: + { + ensureExtensionDeclaration(UnownedStringSlice("SPV_KHR_non_semantic_info")); + auto debugBuildIdentifier = as<IRDebugBuildIdentifier>(inst); + return emitOpDebugBuildIdentifier( + getSection(SpvLogicalSectionID::GlobalVariables), + inst, + inst->getFullType(), + getNonSemanticDebugInfoExtInst(), + debugBuildIdentifier->getBuildIdentifier(), + debugBuildIdentifier->getFlags()); + } case kIROp_GetStringHash: return emitGetStringHash(inst); case kIROp_AttributedType: @@ -9184,6 +9196,10 @@ SlangResult emitSPIRVFromIR( { context.ensureInst(inst); } + if (as<IRDebugBuildIdentifier>(inst)) + { + context.ensureInst(inst); + } if (shouldPreserveParams && as<IRGlobalParam>(inst)) { context.ensureInst(inst); diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp index 88533d938..c0c1ba75a 100644 --- a/source/slang/slang-emit.cpp +++ b/source/slang/slang-emit.cpp @@ -356,6 +356,7 @@ void calcRequiredLoweringPassSet( case kIROp_DebugScope: case kIROp_DebugNoScope: case kIROp_DebugFunction: + case kIROp_DebugBuildIdentifier: result.debugInfo = true; break; case kIROp_ResultType: @@ -616,6 +617,21 @@ static void unexportNonEmbeddableIR(CodeGenTarget target, IRModule* irModule) } } +// Helper function to convert a 20 byte SHA1 to a hexadecimal string, +// needed for the build identifier instruction. +String getBuildIdentifierString(ComponentType* component) +{ + ComPtr<ISlangBlob> hashBlob; + component->getEntryPointHash(0, 0, hashBlob.writeRef()); + + const uint8_t* data = reinterpret_cast<const uint8_t*>(hashBlob->getBufferPointer()); + size_t size = hashBlob->getBufferSize(); + StringBuilder sb; + for (size_t i = 0; i < size; ++i) + sb << StringUtil::makeStringWithFormat("%02x", data[i]); + return sb.produceString(); +} + Result linkAndOptimizeIR( CodeGenContext* codeGenContext, LinkingAndOptimizationOptions const& options, @@ -644,6 +660,19 @@ Result linkAndOptimizeIR( auto irModule = outLinkedIR.module; auto irEntryPoints = outLinkedIR.entryPoints; + // For now, only emit the debug build identifier if separate debug info is enabled + // and only if there are targets. + // TODO: We will ultimately need to change this to always emit the instruction. + if (targetCompilerOptions.shouldEmitSeparateDebugInfo()) + { + // Build identifier is a hash of the source code and compile options. + String buildIdentifier = getBuildIdentifierString(codeGenContext->getProgram()); + int buildIdentifierFlags = 0; + IRBuilder builder(irModule); + builder.setInsertInto(irModule->getModuleInst()); + builder.emitDebugBuildIdentifier(buildIdentifier.getUnownedSlice(), buildIdentifierFlags); + } + #if 0 dumpIRIfEnabled(codeGenContext, irModule, "LINKED"); #endif @@ -2104,22 +2133,256 @@ SlangResult emitSPIRVFromIR( const List<IRFunc*>& irEntryPoints, List<uint8_t>& spirvOut); -SlangResult emitSPIRVForEntryPointsDirectly( - CodeGenContext* codeGenContext, - ComPtr<IArtifact>& outArtifact) +// Helper class to assist in stepping through the SPIRV instructions. +class SpirvInstructionHelper { - // Outside because we want to keep IR in scope whilst we are processing emits - LinkedIR linkedIR; - LinkingAndOptimizationOptions linkingAndOptimizationOptions; - SLANG_RETURN_ON_FAIL( - linkAndOptimizeIR(codeGenContext, linkingAndOptimizationOptions, linkedIR)); +public: + // The index of the SPIRV words in the blob. + // The first 5 words are the header as defined by the SPIRV spec. + enum SpvWordIndex + { + SPV_INDEX_MAGIC_NUMBER, + SPV_INDEX_VERSION_NUMBER, + SPV_INDEX_GENERATOR_NUMBER, + SPV_INDEX_BOUND, + SPV_INDEX_SCHEMA, + SPV_INDEX_INSTRUCTION_START, + }; + + // An instruction in the SPIRV blob. This points to the first word of the instruction. + struct SpvInstruction + { + SpvInstruction(SpvWord* word) + : word(word) + { + } - auto irModule = linkedIR.module; - auto irEntryPoints = linkedIR.entryPoints; + uint16_t getOpCode() const { return word[0] & 0xFFFF; } + uint16_t getWordCountForInst() const { return (word[0] >> 16) & 0xFFFF; } + SpvWord getOperand(uint32_t index) const { return word[index + 1]; } + + // Helper function to interpret the instruction as a string and + // extract the string value. + String getStringFromInst() const + { + String result; + for (uint32_t i = 1; i < getWordCountForInst(); i++) + { + SpvWord op = getOperand(i); + for (int b = 0; b < 4; ++b) + { + char c = (char)((op >> (b * 8)) & 0xFF); + if (c == '\0') + return result; + result.append(c); + } + } + return result; + } + + SpvWord* word = nullptr; + }; + + SpirvInstructionHelper() {} + + // Load the SPIRV instructions from the artifact into a data blob that + // we can read. + SlangResult loadBlob(ComPtr<IArtifact>& artifact) + { + ComPtr<ISlangBlob> spirvBlob; + SlangResult res = artifact->loadBlob(ArtifactKeep::Yes, spirvBlob.writeRef()); + if (SLANG_FAILED(res) || !spirvBlob) + return SLANG_FAIL; + + // Populate the full array of SPIR-V words. + m_words.clear(); + m_words.addRange( + reinterpret_cast<const SpvWord*>(spirvBlob->getBufferPointer()), + spirvBlob->getBufferSize() / sizeof(SpvWord)); + + // Populate the header words. These are the first 5 words of the SPIR-V + // blob and are treated differently from the rest of the instructions. + m_headerWords.clear(); + m_headerWords.addRange(m_words.getBuffer(), SPV_INDEX_INSTRUCTION_START); + + return SLANG_OK; + } + + // Get the header words. + List<SpvWord> getHeaderWords() const { return m_headerWords; } + + // Visit all SPIRV instructions (excluding header words), invoking the callback for each + // instruction. The callback should be a function or lambda with signature: void(const + // SpvInstruction&). + template<typename Func> + void visitInstructions(Func&& callback) + { + // Instructions start after the header (first 5 words) + constexpr size_t kHeaderWordCount = + static_cast<size_t>(SpvWordIndex::SPV_INDEX_INSTRUCTION_START); + size_t i = kHeaderWordCount; + while (i < (size_t)m_words.getCount()) + { + SpvWord* wordPtr = m_words.getBuffer() + i; + SpvInstruction inst(wordPtr); + callback(inst); + uint16_t wordCount = inst.getWordCountForInst(); + if (wordCount == 0) + break; // Prevent infinite loop on malformed input + i += wordCount; + } + } + +private: + // The full array of SPIRV words. + List<SpvWord> m_words; + + // The header words. + List<SpvWord> m_headerWords; +}; + +// Helper function that takes an artifact populated with SPIRV instructions +// after the spirv-opt step, and a previously created but empty +// strippedArtifact. The artifact is unmodified, and the strippedArtifact +// will contain all the artifact's instructions except for debug instructions. +static SlangResult stripDbgSpirvFromArtifact( + ComPtr<IArtifact>& artifact, + ComPtr<IArtifact>& strippedArtifact) +{ + // Standard debug opcodes to strip out. This mimics the behavior of + // spirv-opt. + static const uint16_t debugOpCodeVals[] = { + SpvOpSourceContinued, + SpvOpSource, + SpvOpSourceExtension, + SpvOpString, + SpvOpName, + SpvOpMemberName, + SpvOpModuleProcessed, + SpvOpLine, + SpvOpNoLine}; + // If the instruction is an extended instruction, then we also need + // to check if the instruction number is for a debug instruction as + // listed in slang-emit-spirv-ops-debug-info-ext.h + static const uint32_t debugExtInstVals[] = { + NonSemanticShaderDebugInfo100DebugCompilationUnit, + NonSemanticShaderDebugInfo100DebugTypeBasic, + NonSemanticShaderDebugInfo100DebugTypePointer, + NonSemanticShaderDebugInfo100DebugTypeQualifier, + NonSemanticShaderDebugInfo100DebugTypeArray, + NonSemanticShaderDebugInfo100DebugTypeVector, + NonSemanticShaderDebugInfo100DebugTypeFunction, + NonSemanticShaderDebugInfo100DebugTypeComposite, + NonSemanticShaderDebugInfo100DebugTypeMember, + NonSemanticShaderDebugInfo100DebugFunction, + NonSemanticShaderDebugInfo100DebugScope, + NonSemanticShaderDebugInfo100DebugNoScope, + NonSemanticShaderDebugInfo100DebugInlinedAt, + NonSemanticShaderDebugInfo100DebugLocalVariable, + NonSemanticShaderDebugInfo100DebugInlinedVariable, + NonSemanticShaderDebugInfo100DebugDeclare, + NonSemanticShaderDebugInfo100DebugValue, + NonSemanticShaderDebugInfo100DebugExpression, + NonSemanticShaderDebugInfo100DebugSource, + NonSemanticShaderDebugInfo100DebugFunctionDefinition, + NonSemanticShaderDebugInfo100DebugSourceContinued, + NonSemanticShaderDebugInfo100DebugLine, + NonSemanticShaderDebugInfo100DebugEntryPoint, + NonSemanticShaderDebugInfo100DebugTypeMatrix, + }; + + // Hash sets for easier lookup. + HashSet<uint16_t> debugOpCodes; + for (auto val : debugOpCodeVals) + debugOpCodes.add(val); + HashSet<uint32_t> debugExtInstNumbers; + for (auto val : debugExtInstVals) + debugExtInstNumbers.add(val); + + SpirvInstructionHelper spirvInstructionHelper; + SLANG_RETURN_ON_FAIL(spirvInstructionHelper.loadBlob(artifact)); + + auto headerWords = spirvInstructionHelper.getHeaderWords(); + + List<uint8_t> spirvWordsList; + spirvWordsList.addRange( + reinterpret_cast<const uint8_t*>(headerWords.getBuffer()), + headerWords.getCount() * sizeof(SpvWord)); + + // First find the DebugBuildIdentifier instruction, and keep track of which string + // it refers to, this string needs to be kept in the final output. + SpvWord debugStringId = 0; + spirvInstructionHelper.visitInstructions( + [&](const SpirvInstructionHelper::SpvInstruction& inst) + { + if (inst.getOpCode() == SpvOpExtInst) + { + if (inst.getOperand(3) == NonSemanticShaderDebugInfo100DebugBuildIdentifier) + { + debugStringId = inst.getOperand(4); + return; + } + } + }); + + // Iterate over the instructions from the artifact and add them to the list + // only if they are not debug instructions. We also get the debug build hash + // to use as the filename for the debug spirv file. + String debugBuildHash; + spirvInstructionHelper.visitInstructions( + [&](const SpirvInstructionHelper::SpvInstruction& inst) + { + if (debugOpCodes.contains(inst.getOpCode())) + { + // We can only strip strings if they are not being used by the + // DebugBuildIdentifier instruction. + bool foundDebugString = false; + if (inst.getOpCode() == SpvOpString && inst.getOperand(0) == debugStringId) + { + debugBuildHash = inst.getStringFromInst(); + foundDebugString = true; + } + if (!foundDebugString) + return; + } + // Also check if the instruction is an extended instruction containing DebugInfo. + if (inst.getOpCode() == SpvOpExtInst) + { + // Ignore this if the instruction contains DebugInfo. + if (debugExtInstNumbers.contains(inst.getOperand(3))) + return; + } + // Otherwise this is a non-debug instruction and should be included. + spirvWordsList.addRange( + reinterpret_cast<const uint8_t*>(inst.word), + inst.getWordCountForInst() * sizeof(SpvWord)); + }); + // Create the stripped artifact using the above created instruction list. + strippedArtifact->addRepresentationUnknown(ListBlob::moveCreate(spirvWordsList)); + + // Set the name of the artifact to the debug build hash so it can be used + // as the filename for the debug spirv file. + artifact->setName(debugBuildHash.getBuffer()); + + return SLANG_OK; +} + +// Helper function to create an artifact from IR used internally by +// emitSPIRVForEntryPointsDirectly. +static SlangResult createArtifactFromIR( + CodeGenContext* codeGenContext, + IRModule* irModule, + List<IRFunc*> irEntryPoints, + ComPtr<IArtifact>& artifact, + ComPtr<IArtifact>& dbgArtifact) +{ List<uint8_t> spirv, outSpirv; emitSPIRVFromIR(codeGenContext, irModule, irEntryPoints, spirv); + auto targetRequest = codeGenContext->getTargetReq(); + auto targetCompilerOptions = targetRequest->getOptionSet(); + #if 0 String optErr; if (SLANG_FAILED(optimizeSPIRV(spirv, optErr, outSpirv))) @@ -2128,8 +2391,7 @@ SlangResult emitSPIRVForEntryPointsDirectly( spirv = _Move(outSpirv); } #endif - auto artifact = - ArtifactUtil::createArtifactForCompileTarget(asExternal(codeGenContext->getTargetFormat())); + artifact->addRepresentationUnknown(ListBlob::moveCreate(spirv)); IDownstreamCompiler* compiler = codeGenContext->getSession()->getOrLoadDownstreamCompiler( @@ -2252,7 +2514,19 @@ SlangResult emitSPIRVForEntryPointsDirectly( auto downstreamStartTime = std::chrono::high_resolution_clock::now(); if (SLANG_SUCCEEDED(compiler->compile(downstreamOptions, optimizedArtifact.writeRef()))) { - artifact = _Move(optimizedArtifact); + // Check if we need to output a separate SPIRV file containing debug info. If so + // then strip all debug instructions from the artifact. The dbgArtifact will still + // contain all instructions. + if (targetCompilerOptions.shouldEmitSeparateDebugInfo()) + { + auto strippedArtifact = ArtifactUtil::createArtifactForCompileTarget(SLANG_SPIRV); + SLANG_RETURN_ON_FAIL( + stripDbgSpirvFromArtifact(optimizedArtifact, strippedArtifact)); + artifact = _Move(strippedArtifact); + dbgArtifact = _Move(optimizedArtifact); + } + else + artifact = _Move(optimizedArtifact); } auto downstreamElapsedTime = (std::chrono::high_resolution_clock::now() - downstreamStartTime).count() * 0.000000001; @@ -2262,8 +2536,49 @@ SlangResult emitSPIRVForEntryPointsDirectly( passthroughDownstreamDiagnostics(codeGenContext->getSink(), compiler, artifact)); } + return SLANG_OK; +} + +SlangResult emitSPIRVForEntryPointsDirectly( + CodeGenContext* codeGenContext, + ComPtr<IArtifact>& outArtifact) +{ + // Outside because we want to keep IR in scope whilst we are processing emits + LinkedIR linkedIR; + LinkingAndOptimizationOptions linkingAndOptimizationOptions; + SLANG_RETURN_ON_FAIL( + linkAndOptimizeIR(codeGenContext, linkingAndOptimizationOptions, linkedIR)); + + auto irModule = linkedIR.module; + auto irEntryPoints = linkedIR.entryPoints; + + dumpIRIfEnabled(codeGenContext, irModule, "POST LINK AND OPTIMIZE"); + + auto targetRequest = codeGenContext->getTargetReq(); + auto targetCompilerOptions = targetRequest->getOptionSet(); + + // Create the artifact containing the main SPIRV data, and the debug SPIRV + // data if requested by the command line arg -separate-debug-info. + Slang::ComPtr<Slang::IArtifact> dbgArtifact; + auto artifact = + ArtifactUtil::createArtifactForCompileTarget(asExternal(codeGenContext->getTargetFormat())); + SLANG_RETURN_ON_FAIL( + createArtifactFromIR(codeGenContext, irModule, irEntryPoints, artifact, dbgArtifact)); ArtifactUtil::addAssociated(artifact, linkedIR.metadata); + // Associate the debug artifact with the main artifact. + // EndToEndCompileRequest::generateOutput will read this data + // and produce a .dbg.spv file for this child artifact. + if (targetCompilerOptions.shouldEmitSeparateDebugInfo()) + { + artifact->addAssociated(dbgArtifact); + + auto artifactPostEmitMetadata = + static_cast<ArtifactPostEmitMetadata*>(linkedIR.metadata.get()); + artifactPostEmitMetadata->addRef(); + artifactPostEmitMetadata->m_debugBuildIdentifier = dbgArtifact->getName(); + } + outArtifact.swap(artifact); return SLANG_OK; diff --git a/source/slang/slang-ir-inst-defs.h b/source/slang/slang-ir-inst-defs.h index eac337deb..3711c3a31 100644 --- a/source/slang/slang-ir-inst-defs.h +++ b/source/slang/slang-ir-inst-defs.h @@ -1373,6 +1373,7 @@ INST(DebugFunction, DebugFunction, 5, 0) INST(DebugInlinedVariable, DebugInlinedVariable, 2, 0) INST(DebugScope, DebugScope, 2, 0) INST(DebugNoScope, DebugNoScope, 1, 0) +INST(DebugBuildIdentifier, DebugBuildIdentifier, 2, 0) /* Embedded Precompiled Libraries */ INST(EmbeddedDownstreamIR, EmbeddedDownstreamIR, 2, 0) diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h index a1f66d142..4f52bff5d 100644 --- a/source/slang/slang-ir-insts.h +++ b/source/slang/slang-ir-insts.h @@ -3462,6 +3462,13 @@ struct IRDebugSource : IRInst IRInst* getSource() { return getOperand(1); } }; +struct IRDebugBuildIdentifier : IRInst +{ + IR_LEAF_ISA(DebugBuildIdentifier) + IRInst* getBuildIdentifier() { return getOperand(0); } + IRInst* getFlags() { return getOperand(1); } +}; + struct IRDebugLine : IRInst { IR_LEAF_ISA(DebugLine) @@ -4031,6 +4038,8 @@ public: } IRInst* emitDebugSource(UnownedStringSlice fileName, UnownedStringSlice source); + IRInst* emitDebugBuildIdentifier(UnownedStringSlice buildIdentifier, IRIntegerValue flags); + IRInst* emitDebugBuildIdentifier(IRInst* debugBuildIdentifier); IRInst* emitDebugLine( IRInst* source, IRIntegerValue lineStart, diff --git a/source/slang/slang-ir-link.cpp b/source/slang/slang-ir-link.cpp index 29a42b73c..a7b4c4560 100644 --- a/source/slang/slang-ir-link.cpp +++ b/source/slang/slang-ir-link.cpp @@ -2174,6 +2174,11 @@ LinkedIR linkIR(CodeGenContext* codeGenContext) // regardless if the source files participate in the line table or not. cloneValue(context, inst); break; + case kIROp_DebugBuildIdentifier: + // The debug build identifier won't be referenced by anything, + // but we still need to keep it around if it is in the IR. + cloneValue(context, inst); + break; } } } diff --git a/source/slang/slang-ir-strip-debug-info.cpp b/source/slang/slang-ir-strip-debug-info.cpp index 124c41a5c..4624a2f9e 100644 --- a/source/slang/slang-ir-strip-debug-info.cpp +++ b/source/slang/slang-ir-strip-debug-info.cpp @@ -17,6 +17,7 @@ static void findDebugInfo(IRInst* inst, List<IRInst*>& debugInstructions) case kIROp_DebugScope: case kIROp_DebugNoScope: case kIROp_DebugFunction: + case kIROp_DebugBuildIdentifier: debugInstructions.add(inst); break; default: diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp index e66ad69ce..89d7a666e 100644 --- a/source/slang/slang-ir.cpp +++ b/source/slang/slang-ir.cpp @@ -3393,6 +3393,13 @@ IRInst* IRBuilder::emitDebugSource(UnownedStringSlice fileName, UnownedStringSli IRInst* args[] = {getStringValue(fileName), getStringValue(source)}; return emitIntrinsicInst(getVoidType(), kIROp_DebugSource, 2, args); } +IRInst* IRBuilder::emitDebugBuildIdentifier( + UnownedStringSlice buildIdentifier, + IRIntegerValue flags) +{ + IRInst* args[] = {getStringValue(buildIdentifier), getIntValue(getUIntType(), flags)}; + return emitIntrinsicInst(getVoidType(), kIROp_DebugBuildIdentifier, 2, args); +} IRInst* IRBuilder::emitDebugLine( IRInst* source, IRIntegerValue lineStart, diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index c269b10c4..68f538508 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -820,7 +820,11 @@ void initCommandOptions(CommandOptions& options) "-verify-debug-serial-ir", nullptr, "Verify IR in the front-end."}, - {OptionKind::DumpModule, "-dump-module", nullptr, "Disassemble and print the module IR."}}; + {OptionKind::DumpModule, "-dump-module", nullptr, "Disassemble and print the module IR."}, + {OptionKind::EmitSeparateDebug, + "-separate-debug-info", + nullptr, + "Emit debug data to a separate file, and strip it from the main output file."}}; _addOptions(makeConstArrayView(debuggingOpts), options); /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Experimental !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ @@ -3079,6 +3083,14 @@ SlangResult OptionsParser::_parse(int argc, char const* const* argv) break; } + case OptionKind::EmitSeparateDebug: + { + // This will emit a separate debug file, containing all debug info in + // a .dbg.spv file. The main output SPIRV will have all debug info stripped. + m_compileRequest->setDebugInfoLevel(SLANG_DEBUG_INFO_LEVEL_MAXIMAL); + linkage->m_optionSet.set(OptionKind::EmitSeparateDebug, true); + break; + } default: { // Hmmm, we looked up and produced a valid enum, but it wasn't handled in the diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index d8879c690..d66e86b2d 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -5499,12 +5499,16 @@ SLANG_NO_THROW void SLANG_MCALL ComponentType::getEntryPointHash( buildHash(builder); // Add the name and name override for the specified entry point to the hash. - auto entryPointName = getEntryPoint(entryPointIndex)->getName()->text; - builder.append(entryPointName); - auto entryPointMangledName = getEntryPointMangledName(entryPointIndex); - builder.append(entryPointMangledName); - auto entryPointNameOverride = getEntryPointNameOverride(entryPointIndex); - builder.append(entryPointNameOverride); + auto entryPoint = getEntryPoint(entryPointIndex); + if (entryPoint) + { + auto entryPointName = entryPoint->getName()->text; + builder.append(entryPointName); + auto entryPointMangledName = getEntryPointMangledName(entryPointIndex); + builder.append(entryPointMangledName); + auto entryPointNameOverride = getEntryPointNameOverride(entryPointIndex); + builder.append(entryPointNameOverride); + } auto hash = builder.finalize().toBlob(); *outHash = hash.detach(); @@ -5567,6 +5571,33 @@ SLANG_NO_THROW SlangResult SLANG_MCALL ComponentType::getEntryPointMetadata( return SLANG_OK; } +SLANG_NO_THROW SlangResult SLANG_MCALL ComponentType::getEntryPointCompileResult( + SlangInt entryPointIndex, + Int targetIndex, + slang::ICompileResult** outCompileResult, + slang::IBlob** outDiagnostics) +{ + auto linkage = getLinkage(); + if (targetIndex < 0 || targetIndex >= linkage->targets.getCount()) + return SLANG_E_INVALID_ARG; + auto target = linkage->targets[targetIndex]; + + auto targetProgram = getTargetProgram(target); + + DiagnosticSink sink(linkage->getSourceManager(), Lexer::sourceLocationLexer); + applySettingsToDiagnosticSink(&sink, &sink, linkage->m_optionSet); + applySettingsToDiagnosticSink(&sink, &sink, m_optionSet); + + IArtifact* artifact = targetProgram->getOrCreateEntryPointResult(entryPointIndex, &sink); + sink.getBlobIfNeeded(outDiagnostics); + if (artifact == nullptr) + return SLANG_E_NOT_AVAILABLE; + + *outCompileResult = static_cast<slang::ICompileResult*>(artifact); + (*outCompileResult)->addRef(); + return SLANG_OK; +} + RefPtr<ComponentType> ComponentType::specialize( SpecializationArg const* inSpecializationArgs, SlangInt specializationArgCount, @@ -5902,6 +5933,20 @@ SLANG_NO_THROW SlangResult SLANG_MCALL ComponentType::getTargetMetadata( return SLANG_OK; } +SLANG_NO_THROW SlangResult SLANG_MCALL ComponentType::getTargetCompileResult( + Int targetIndex, + slang::ICompileResult** outCompileResult, + slang::IBlob** outDiagnostics) +{ + IArtifact* artifact = getTargetArtifact(targetIndex, outDiagnostics); + if (artifact == nullptr) + return SLANG_E_NOT_AVAILABLE; + + *outCompileResult = static_cast<slang::ICompileResult*>(artifact); + //(*outCompileResult)->addRef(); // TODO: Needed if using a ComPtr. + return SLANG_OK; +} + // // CompositeComponentType // |
