Currently have made a spirv parser/assembler library. The api is not versatile to make sure of minimal allocation.
There's a WordBuffer class containing functions like AddOpVariable and AddOpTypeInteger with parameters. Each parameters is then put into a stack allocated array and pushed to the buffer.
SPIR-V are composed of 32bit integer words (not exactly byte code). Types have to be declared and are either 2 or 3 words depending the type declared. There's an implicit index given to these instruction which can be used by other instruction.
Example below
; Magic: 0x07230203 (SPIR-V)
; Version: 0x00010000 (Version: 1.0.0)
; Generator: 0x00080001 (Khronos Glslang Reference Front End; 1)
; Bound: 63
; Schema: 0
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main" %31 %33 %42 %57
OpExecutionMode %4 OriginLowerLeft
. . .
; All types, variables, and constants
%2 = OpTypeVoid
%3 = OpTypeFunction %2 ; void ()
%6 = OpTypeFloat 32 ; 32-bit float
%7 = OpTypeVector %6 4 ; vec4
%8 = OpTypePointer Function %7 ; function-local vec4*
%10 = OpConstant %6 1
%11 = OpConstant %6 2
. . .
Where %6 = OpTypeFloat 32 is the 6th instruction declared in the buffer that implicitely has an Id. It is used right after with %7 = OpTypeVector %6 4.
OpTypeFloat is a 2 word instruction, one word for OpTypeFloat and the other for the value 32 declaring a 32bit float type.
OpTypeVector is a 3 word instruction, one word for OpTypeVector, one for the index of the type %6 and the last one for the number of components 4 which results in a float4/vec4 type.
Mixins would be defined as a SPIR-V byte code containing partial informations.
Let's define MixinA containing the OpTypeFloat instruction and MixinB containing the OpTypeVector instruction referencing the OpTypeFloat instruction.
In MixinA, OpTypeFloat would be the first instruction with an implicit Id, it would be disassembled as %1 = OpTypeFloat 32.
In MixinB, the same would happen with OpTypeVector, it would also reference the OpTypeFloat present in MixinA and everything would be disassembled as %1 = OpTypeVector %1.
The index clash is the core issue we're facing with mixing spir-v raw byte code.
Each mixin object will have an additional parameter giving an offset to the current mixin.