diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2020-08-06 17:15:55 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-08-06 17:15:55 -0400 |
| commit | 20af567033dedea15abb22fb7d344d116d7b99c5 (patch) | |
| tree | fe176cd9d8366cbbd78889f8c8b51acf8a0d1653 /source | |
| parent | 3231048b328551edff9a923dec3f7bd46ed5aff8 (diff) | |
Emit spir-v using MemoryArena to stop memory leak (#1479)
* Use m_style for OSFindFilesResult
* Refactor of FindFilesResult.
* Fixes on linux for FindFiles.
* Simplify FindFilesState, and linux support for pattern matching.
* Small fixes to linux FindFilesState
* Fix typo on linux FindFiles
* Fix typo in linux FindFiles.
* Renamed some variables, and improved comments on FindFiles.
* Improve comments on FildFiles
* Small improvements around FindFiles.
* Refactor FindFiles again.. into a visitor and function in Path.
* Fix some problems on linux.
* Fix linux typo.
* Renamed os -> find-file-util
* find-file-utl -> directory-util
* Make delete of PathInfo explicit.
* Initialize alwaysCreateCollectedParam .
* WIP spir-v emit using MemoryArena
* Fix bug in spirv emit.
* Fix bug with handling null termination on strings in spirv emit.
* Small improvements in comments around emit spirv
* Remove the 'dst' from emitOperand - we can only emit to the current inst.
* Improve SpirV emit comments.
* Don't store the created instruction in the InstConstructScope - as it's always the m_currentInst.
Don't return the instruction after _beginInst.
Slight comment improvements.
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/slang-emit-spirv.cpp | 280 | ||||
| -rw-r--r-- | source/slang/slang-file-system.cpp | 6 | ||||
| -rw-r--r-- | source/slang/slang-ir-entry-point-uniforms.h | 4 |
3 files changed, 181 insertions, 109 deletions
diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp index 915d963c0..3d3eae43a 100644 --- a/source/slang/slang-emit-spirv.cpp +++ b/source/slang/slang-emit-spirv.cpp @@ -7,6 +7,8 @@ #include "spirv/unified1/spirv.h" +#include "../core/slang-memory-arena.h" + namespace Slang { @@ -190,7 +192,9 @@ struct SpvInst : SpvInstParent // need to store a more refined representation here. /// The additional words of the instruction after the opcode - List<SpvWord> operandWords; + SpvWord* operandWords = nullptr; + /// The amount of operand words + uint32_t operandWordsCount = 0; // We will store the instructions in a given `SpvInstParent` // using an intrusive linked list. @@ -210,7 +214,7 @@ struct SpvInst : SpvInstParent // > including the word holding the word count and opcode, and any optional // > operands. An instruction’s word count is the total space taken by the instruction. // - SpvWord wordCount = 1 + SpvWord(operandWords.getCount()); + SpvWord wordCount = 1 + SpvWord(operandWordsCount); // [2.3: Physical Layout of a SPIR-V Module and Instruction] // @@ -221,11 +225,8 @@ struct SpvInst : SpvInstParent // The operand words simply follow the opcode word. // - for( auto word : operandWords ) - { - ioWords.add(word); - } - + ioWords.addRange(operandWords, operandWordsCount); + // In our representation choice, the children of a // parent instruction will always follow the encoded // words of a parent: @@ -418,39 +419,115 @@ struct SPIRVEmitContext // Emitting an instruction starts with picking the opcode // and allocating the `SpvInst`. + // Holds a stack of instructions operands *BEFORE* they added to the instruction. + List<SpvWord> m_operandStack; + // The current instruction being constructed. Cannot add operands unless it is set. + SpvInst* m_currentInst = nullptr; + + // Operands can only be added when inside of a InstConstructScope + struct InstConstructScope + { + SLANG_FORCE_INLINE operator SpvInst*() const { return m_context->m_currentInst; } + + InstConstructScope(SPIRVEmitContext* context, SpvOp opcode, IRInst* irInst = nullptr): + m_context(context), + m_operandsStartIndex(context->m_operandStack.getCount()), + m_previousInst(context->m_currentInst) + { + m_context->_beginInst(opcode, irInst); + } + ~InstConstructScope() + { + m_context->_endInst(m_previousInst, m_operandsStartIndex); + } + + SPIRVEmitContext* m_context; ///< The context + SpvInst* m_previousInst; ///< The previously live inst + Index m_operandsStartIndex; ///< The start index for operands of m_inst + }; + + /// Holds memory for instructions and operands. + MemoryArena m_memoryArena; + /// Begin emitting an instruction with the given SPIR-V `opcode`. /// /// If `irInst` is non-null, then the resulting SPIR-V instruction /// will be registered as corresponding to `irInst`. /// - SpvInst* beginInst(SpvOp opcode, IRInst* irInst = nullptr) + /// The created instruction is stored in m_currentInst. + /// + /// Should not typically be called directly use InstConstructScope to scope construction + void _beginInst(SpvOp opcode, IRInst* irInst = nullptr) { - // TODO: We are currently just leaking the `SpvInst`s we allocate. - // We should set up a pool allocator that this pass can use for - // both the `SpvInst`s and for their constituent words. - // - auto spvInst = new SpvInst(); + // Allocate the instruction + + auto spvInst = new (m_memoryArena.allocate(sizeof(SpvInst))) SpvInst(); spvInst->opcode = opcode; if(irInst) { registerInst(irInst, spvInst); } + m_currentInst = spvInst; + } + /// End emitting an instruction + /// Should not typically be called directly use InstConstructScope to scope construction + void _endInst(SpvInst* previousInst, Index operandsStartIndex) + { + SLANG_ASSERT(m_currentInst); + // Work out how many operands were added + const Index operandsCount = m_operandStack.getCount() - operandsStartIndex; + + if (operandsCount) + { + // Allocate the operands + m_currentInst->operandWords = m_memoryArena.allocateAndCopyArray(m_operandStack.getBuffer() + operandsStartIndex, operandsCount); + // Set the count + m_currentInst->operandWordsCount = uint32_t(operandsCount); + } + + // Make the previous inst active + m_currentInst = previousInst; + // Reset the operand stack + m_operandStack.setCount(operandsStartIndex); + } + + /// Ensure that an instruction has been emitted + SpvInst* ensureInst(IRInst* irInst) + { + SpvInst* spvInst = nullptr; + if (!m_mapIRInstToSpvInst.TryGetValue(irInst, spvInst)) + { + // If the `irInst` hasn't already been emitted, + // then we will assume that is is a global instruction + // (a constant, type, function, etc.) and we should make + // sure it gets emitted now. + // + // Note: this step means that emitting an instruction + // can be re-entrant/recursive. Because we emit the SPIR-V + // words for an instruction into an intermediate structure + // we don't have to worry about the re-entrancy causing + // the ordering of instruction words to be interleaved. + // + spvInst = emitGlobalInst(irInst); + } return spvInst; } - // Once an instruction has been created, we append the operand + // Whilst an instruction has been created, we append the operand // words to it with `emitOperand`. There are a few different // case of operands that we handle. // // The simplest case is when an instruction takes an operand // that is just a literal SPIR-V word. - /// Emit a literal `word` as an operand to `dst`. - void emitOperand(SpvInst* dst, SpvWord word) + /// Emit a literal `word` as an operand to the current instruction + void emitOperand(SpvWord word) { - dst->operandWords.add(word); + // Can only add operands if we are constructing an instruction (ie in _beginInst/_endInst) + SLANG_ASSERT(m_currentInst); + m_operandStack.add(word); } // The most common case of operand is an <id> that represents @@ -459,10 +536,10 @@ struct SPIRVEmitContext // the same. If we have a `SpvInst` we can look up or // generate an <id> for it. - /// Emit an operand to the `dst` instruction, which references `src` by its <id> - void emitOperand(SpvInst* dst, SpvInst* src) + /// Emit an operand to the current instruction, which references `src` by its <id> + void emitOperand(SpvInst* src) { - emitOperand(dst, getID(src)); + emitOperand(getID(src)); } // Commonly, we will have an operand in the form of an `IRInst` @@ -471,36 +548,14 @@ struct SPIRVEmitContext // or which we have yet to emit (because it is a global-scope // instruction that has not been referenced before). - /// Emit an operand to the `dst` instruction, which references `src` by its <id> - void emitOperand(SpvInst* dst, IRInst* src) + /// Emit an operand to the current instruction, which references `src` by its <id> + void emitOperand(IRInst* src) { // We first ensure that the `src` instruction has been emitted, // and then handle it as for any other <id> operand. // SpvInst* spvSrc = ensureInst(src); - emitOperand(dst, getID(spvSrc)); - } - - /// Ensure that an instruction has been emitted - SpvInst* ensureInst(IRInst* irInst) - { - SpvInst* spvInst = nullptr; - if( !m_mapIRInstToSpvInst.TryGetValue(irInst, spvInst) ) - { - // If the `irInst` hasn't already been emitted, - // then we will assume that is is a global instruction - // (a constant, type, function, etc.) and we should make - // sure it gets emitted now. - // - // Note: this step means that emitting an instruction - // can be re-entrant/recursive. Because we emit the SPIR-V - // words for an instruction into an intermediate structure - // we don't have to worry about the re-entrancy causing - // the ordering of instruction words to be interleaved. - // - spvInst = emitGlobalInst(irInst); - } - return spvInst; + emitOperand(getID(spvSrc)); } // Some instructions take a string as a literal operand, @@ -508,8 +563,16 @@ struct SPIRVEmitContext // encode the string into multiple operand words. /// Emit an operand that is encoded as a literal string - void emitOperand(SpvInst* dst, UnownedStringSlice const& text) + void emitOperand(UnownedStringSlice const& text) { + // Can only emitOperands if we are in an instruction + SLANG_ASSERT(m_currentInst); + SLANG_COMPILE_TIME_ASSERT(sizeof(SpvWord) == 4); + + // 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 @@ -517,36 +580,28 @@ struct SPIRVEmitContext // > 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). - // - // We start by emitting the contents of `text` in - // 4-byte chunks. - // - char const* cursor = text.begin(); - char const* end = text.end(); - while( (end - cursor) >= 4 ) - { - SpvWord word; - memcpy(&word, cursor, 4); - emitOperand(dst, word); - cursor += 4; - } - // // > 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. - // - // For the last word, the low-order bytes will - // come from the remainder of the string (if - // there is anything left), and the rest will - // be left as zeros. - // - // TODO: This code should probably assert that `text` - // doesn't contain any embedded nul bytes, since they - // could lead to invalid encoded results. - // - SLANG_ASSERT((end - cursor) <= 3); - SpvWord lastWord = 0; - memcpy(&lastWord, cursor, (end - cursor)); - emitOperand(dst, lastWord); + + // 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); } // Sometimes we will want to pass down an argument that @@ -556,10 +611,12 @@ struct SPIRVEmitContext enum ResultIDToken { kResultID }; - void emitOperand(SpvInst* dst, ResultIDToken) + void emitOperand(ResultIDToken) { - // A result <id> operand uses the <id> of the instruction itself. - emitOperand(dst, getID(dst)); + SLANG_ASSERT(m_currentInst); + + // A result <id> operand uses the <id> of the instruction itself (which is m_currentInst) + emitOperand(getID(m_currentInst)); } // As another convenience, there are often cases where @@ -569,7 +626,7 @@ struct SPIRVEmitContext // Slang IR and SPIR-V instructions agree on the // number, order, and meaning of their operands. - /// Helper type for emitting all the operands of some IR instruction + /// Helper type for emitting all the operands of the current IR instruction struct OperandsOf { OperandsOf(IRInst* irInst) @@ -579,14 +636,14 @@ struct SPIRVEmitContext IRInst* irInst = nullptr; }; - /// Emit operand words for all the operands of a given IR instruction - void emitOperand(SpvInst* dst, OperandsOf const& other) + /// Emit operand words for all the operands of the current IR instruction + void emitOperand(OperandsOf const& other) { auto irInst = other.irInst; auto operandCount = irInst->getOperandCount(); for( UInt ii = 0; ii < operandCount; ++ii ) { - emitOperand(dst, irInst->getOperand(ii)); + emitOperand(irInst->getOperand(ii)); } } @@ -609,7 +666,8 @@ struct SPIRVEmitContext SpvInst* emitInst(SpvInstParent* parent, IRInst* irInst, SpvOp opcode) { - auto spvInst = beginInst(opcode, irInst); + InstConstructScope scopeInst(this, opcode, irInst); + SpvInst* spvInst = scopeInst; parent->addInst(spvInst); return spvInst; } @@ -617,8 +675,9 @@ struct SPIRVEmitContext template<typename A> SpvInst* emitInst(SpvInstParent* parent, IRInst* irInst, SpvOp opcode, A const& a) { - auto spvInst = beginInst(opcode, irInst); - emitOperand(spvInst, a); + InstConstructScope scopeInst(this, opcode, irInst); + SpvInst* spvInst = scopeInst; + emitOperand(a); parent->addInst(spvInst); return spvInst; } @@ -626,9 +685,10 @@ struct SPIRVEmitContext template<typename A, typename B> SpvInst* emitInst(SpvInstParent* parent, IRInst* irInst, SpvOp opcode, A const& a, B const& b) { - auto spvInst = beginInst(opcode, irInst); - emitOperand(spvInst, a); - emitOperand(spvInst, b); + InstConstructScope scopeInst(this, opcode, irInst); + SpvInst* spvInst = scopeInst; + emitOperand(a); + emitOperand(b); parent->addInst(spvInst); return spvInst; } @@ -636,10 +696,11 @@ struct SPIRVEmitContext template<typename A, typename B, typename C> SpvInst* emitInst(SpvInstParent* parent, IRInst* irInst, SpvOp opcode, A const& a, B const& b, C const& c) { - auto spvInst = beginInst(opcode, irInst); - emitOperand(spvInst, a); - emitOperand(spvInst, b); - emitOperand(spvInst, c); + InstConstructScope scopeInst(this, opcode, irInst); + SpvInst* spvInst = scopeInst; + emitOperand(a); + emitOperand(b); + emitOperand(c); parent->addInst(spvInst); return spvInst; } @@ -647,11 +708,12 @@ struct SPIRVEmitContext template<typename A, typename B, typename C, typename D> SpvInst* emitInst(SpvInstParent* parent, IRInst* irInst, SpvOp opcode, A const& a, B const& b, C const& c, D const& d) { - auto spvInst = beginInst(opcode, irInst); - emitOperand(spvInst, a); - emitOperand(spvInst, b); - emitOperand(spvInst, c); - emitOperand(spvInst, d); + InstConstructScope scopeInst(this, opcode, irInst); + SpvInst* spvInst = scopeInst; + emitOperand(a); + emitOperand(b); + emitOperand(c); + emitOperand(d); parent->addInst(spvInst); return spvInst; } @@ -659,12 +721,13 @@ struct SPIRVEmitContext template<typename A, typename B, typename C, typename D, typename E> SpvInst* emitInst(SpvInstParent* parent, IRInst* irInst, SpvOp opcode, A const& a, B const& b, C const& c, D const& d, E const& e) { - auto spvInst = beginInst(opcode, irInst); - emitOperand(spvInst, a); - emitOperand(spvInst, b); - emitOperand(spvInst, c); - emitOperand(spvInst, d); - emitOperand(spvInst, e); + InstConstructScope scopeInst(this, opcode, irInst); + SpvInst* spvInst = scopeInst; + emitOperand(a); + emitOperand(b); + emitOperand(c); + emitOperand(d); + emitOperand(e); parent->addInst(spvInst); return spvInst; } @@ -1110,6 +1173,12 @@ struct SPIRVEmitContext #undef CASE } } + + SPIRVEmitContext(IRModule* module) : + m_irModule(module), + m_memoryArena(2048) + { + } }; SlangResult emitSPIRVFromIR( @@ -1119,12 +1188,11 @@ SlangResult emitSPIRVFromIR( List<uint8_t>& spirvOut) { SLANG_UNUSED(compileRequest); - SLANG_UNUSED(irModule); - + spirvOut.clear(); - SPIRVEmitContext context; - context.m_irModule = irModule; + SPIRVEmitContext context(irModule); + context.emitFrontMatter(); for (auto irEntryPoint : irEntryPoints) { diff --git a/source/slang/slang-file-system.cpp b/source/slang/slang-file-system.cpp index ebe1eeb61..d3d1fd9f5 100644 --- a/source/slang/slang-file-system.cpp +++ b/source/slang/slang-file-system.cpp @@ -210,7 +210,8 @@ CacheFileSystem::~CacheFileSystem() { for (const auto& pair : m_uniqueIdentityMap) { - delete pair.Value; + PathInfo* pathInfo = pair.Value; + delete pathInfo; } } @@ -261,7 +262,8 @@ void CacheFileSystem::clearCache() { for (const auto& pair : m_uniqueIdentityMap) { - delete pair.Value; + PathInfo* pathInfo = pair.Value; + delete pathInfo; } m_uniqueIdentityMap.Clear(); diff --git a/source/slang/slang-ir-entry-point-uniforms.h b/source/slang/slang-ir-entry-point-uniforms.h index c2c131d61..64de86527 100644 --- a/source/slang/slang-ir-entry-point-uniforms.h +++ b/source/slang/slang-ir-entry-point-uniforms.h @@ -9,7 +9,9 @@ struct IRModule; struct CollectEntryPointUniformParamsOptions { - bool alwaysCreateCollectedParam; + // TODO(JS): Not sure if it makes sense to initialize to true or false. Go with false as + // seems to fit usage. + bool alwaysCreateCollectedParam = false; }; /// Collect entry point uniform parameters into a wrapper `struct` and/or buffer |
