/ src / Spv.Generator / Module.cs
Module.cs
  1  using System.Collections.Generic;
  2  using System.Diagnostics;
  3  using System.IO;
  4  using static Spv.Specification;
  5  
  6  namespace Spv.Generator
  7  {
  8      public partial class Module
  9      {
 10          // TODO: Register on SPIR-V registry.
 11          private const int GeneratorId = 0;
 12  
 13          private readonly uint _version;
 14  
 15          private uint _bound;
 16  
 17          // Follow spec order here while keeping it as simple as possible.
 18          private readonly List<Capability> _capabilities;
 19          private readonly List<string> _extensions;
 20          private readonly Dictionary<DeterministicStringKey, Instruction> _extInstImports;
 21          private AddressingModel _addressingModel;
 22          private MemoryModel _memoryModel;
 23  
 24          private readonly List<Instruction> _entrypoints;
 25          private readonly List<Instruction> _executionModes;
 26          private readonly List<Instruction> _debug;
 27          private readonly List<Instruction> _annotations;
 28  
 29          // In the declaration block.
 30          private readonly Dictionary<TypeDeclarationKey, Instruction> _typeDeclarations;
 31          private readonly List<Instruction> _typeDeclarationsList;
 32          // In the declaration block.
 33          private readonly List<Instruction> _globals;
 34          // In the declaration block.
 35          private readonly Dictionary<ConstantKey, Instruction> _constants;
 36          // In the declaration block, for function that aren't defined in the module.
 37          private readonly List<Instruction> _functionsDeclarations;
 38  
 39          private readonly List<Instruction> _functionsDefinitions;
 40  
 41          private readonly GeneratorPool<Instruction> _instPool;
 42          private readonly GeneratorPool<LiteralInteger> _integerPool;
 43  
 44          public Module(uint version, GeneratorPool<Instruction> instPool = null, GeneratorPool<LiteralInteger> integerPool = null)
 45          {
 46              _version = version;
 47              _bound = 1;
 48              _capabilities = new List<Capability>();
 49              _extensions = new List<string>();
 50              _extInstImports = new Dictionary<DeterministicStringKey, Instruction>();
 51              _addressingModel = AddressingModel.Logical;
 52              _memoryModel = MemoryModel.Simple;
 53              _entrypoints = new List<Instruction>();
 54              _executionModes = new List<Instruction>();
 55              _debug = new List<Instruction>();
 56              _annotations = new List<Instruction>();
 57              _typeDeclarations = new Dictionary<TypeDeclarationKey, Instruction>();
 58              _typeDeclarationsList = new List<Instruction>();
 59              _constants = new Dictionary<ConstantKey, Instruction>();
 60              _globals = new List<Instruction>();
 61              _functionsDeclarations = new List<Instruction>();
 62              _functionsDefinitions = new List<Instruction>();
 63  
 64              _instPool = instPool ?? new GeneratorPool<Instruction>();
 65              _integerPool = integerPool ?? new GeneratorPool<LiteralInteger>();
 66  
 67              LiteralInteger.RegisterPool(_integerPool);
 68          }
 69  
 70          private uint GetNewId()
 71          {
 72              return _bound++;
 73          }
 74  
 75          public void AddCapability(Capability capability)
 76          {
 77              _capabilities.Add(capability);
 78          }
 79  
 80          public void AddExtension(string extension)
 81          {
 82              _extensions.Add(extension);
 83          }
 84  
 85          public Instruction NewInstruction(Op opcode, uint id = Instruction.InvalidId, Instruction resultType = null)
 86          {
 87              var result = _instPool.Allocate();
 88              result.Set(opcode, id, resultType);
 89  
 90              return result;
 91          }
 92  
 93          public Instruction AddExtInstImport(string import)
 94          {
 95              var key = new DeterministicStringKey(import);
 96  
 97              if (_extInstImports.TryGetValue(key, out Instruction extInstImport))
 98              {
 99                  // Update the duplicate instance to use the good id so it ends up being encoded correctly.
100                  return extInstImport;
101              }
102  
103              Instruction instruction = NewInstruction(Op.OpExtInstImport);
104              instruction.AddOperand(import);
105  
106              instruction.SetId(GetNewId());
107  
108              _extInstImports.Add(key, instruction);
109  
110              return instruction;
111          }
112  
113          private void AddTypeDeclaration(Instruction instruction, bool forceIdAllocation)
114          {
115              var key = new TypeDeclarationKey(instruction);
116  
117              if (!forceIdAllocation)
118              {
119                  if (_typeDeclarations.TryGetValue(key, out Instruction typeDeclaration))
120                  {
121                      // Update the duplicate instance to use the good id so it ends up being encoded correctly.
122  
123                      instruction.SetId(typeDeclaration.Id);
124  
125                      return;
126                  }
127              }
128  
129              instruction.SetId(GetNewId());
130  
131              _typeDeclarations[key] = instruction;
132              _typeDeclarationsList.Add(instruction);
133          }
134  
135          public void AddEntryPoint(ExecutionModel executionModel, Instruction function, string name, params Instruction[] interfaces)
136          {
137              Debug.Assert(function.Opcode == Op.OpFunction);
138  
139              Instruction entryPoint = NewInstruction(Op.OpEntryPoint);
140  
141              entryPoint.AddOperand(executionModel);
142              entryPoint.AddOperand(function);
143              entryPoint.AddOperand(name);
144              entryPoint.AddOperand(interfaces);
145  
146              _entrypoints.Add(entryPoint);
147          }
148  
149          public void AddExecutionMode(Instruction function, ExecutionMode mode, params IOperand[] parameters)
150          {
151              Debug.Assert(function.Opcode == Op.OpFunction);
152  
153              Instruction executionModeInstruction = NewInstruction(Op.OpExecutionMode);
154  
155              executionModeInstruction.AddOperand(function);
156              executionModeInstruction.AddOperand(mode);
157              executionModeInstruction.AddOperand(parameters);
158  
159              _executionModes.Add(executionModeInstruction);
160          }
161  
162          private void AddToFunctionDefinitions(Instruction instruction)
163          {
164              Debug.Assert(instruction.Opcode != Op.OpTypeInt);
165              _functionsDefinitions.Add(instruction);
166          }
167  
168          private void AddAnnotation(Instruction annotation)
169          {
170              _annotations.Add(annotation);
171          }
172  
173          private void AddDebug(Instruction debug)
174          {
175              _debug.Add(debug);
176          }
177  
178          public void AddLabel(Instruction label)
179          {
180              Debug.Assert(label.Opcode == Op.OpLabel);
181  
182              label.SetId(GetNewId());
183  
184              AddToFunctionDefinitions(label);
185          }
186  
187          public void AddLocalVariable(Instruction variable)
188          {
189              // TODO: Ensure it has the local modifier.
190              Debug.Assert(variable.Opcode == Op.OpVariable);
191  
192              variable.SetId(GetNewId());
193  
194              AddToFunctionDefinitions(variable);
195          }
196  
197          public void AddGlobalVariable(Instruction variable)
198          {
199              // TODO: Ensure it has the global modifier.
200              // TODO: All constants opcodes (OpSpecXXX and the rest of the OpConstantXXX).
201              Debug.Assert(variable.Opcode == Op.OpVariable);
202  
203              variable.SetId(GetNewId());
204  
205              _globals.Add(variable);
206          }
207  
208          private void AddConstant(Instruction constant)
209          {
210              Debug.Assert(constant.Opcode == Op.OpConstant ||
211                           constant.Opcode == Op.OpConstantFalse ||
212                           constant.Opcode == Op.OpConstantTrue ||
213                           constant.Opcode == Op.OpConstantNull ||
214                           constant.Opcode == Op.OpConstantComposite);
215  
216              var key = new ConstantKey(constant);
217  
218              if (_constants.TryGetValue(key, out Instruction global))
219              {
220                  // Update the duplicate instance to use the good id so it ends up being encoded correctly.
221                  constant.SetId(global.Id);
222  
223                  return;
224              }
225  
226              constant.SetId(GetNewId());
227  
228              _constants.Add(key, constant);
229          }
230  
231          public Instruction ExtInst(Instruction resultType, Instruction set, LiteralInteger instruction, params IOperand[] parameters)
232          {
233              Instruction result = NewInstruction(Op.OpExtInst, GetNewId(), resultType);
234  
235              result.AddOperand(set);
236              result.AddOperand(instruction);
237              result.AddOperand(parameters);
238              AddToFunctionDefinitions(result);
239  
240              return result;
241          }
242  
243          public void SetMemoryModel(AddressingModel addressingModel, MemoryModel memoryModel)
244          {
245              _addressingModel = addressingModel;
246              _memoryModel = memoryModel;
247          }
248  
249          // TODO: Find a way to make the auto generate one used.
250          public Instruction OpenClPrintf(Instruction resultType, Instruction format, params Instruction[] additionalarguments)
251          {
252              Instruction result = NewInstruction(Op.OpExtInst, GetNewId(), resultType);
253  
254              result.AddOperand(AddExtInstImport("OpenCL.std"));
255              result.AddOperand((LiteralInteger)184);
256              result.AddOperand(format);
257              result.AddOperand(additionalarguments);
258              AddToFunctionDefinitions(result);
259  
260              return result;
261          }
262  
263          public byte[] Generate()
264          {
265              // Estimate the size needed for the generated code, to avoid expanding the MemoryStream.
266              int sizeEstimate = 1024 + _functionsDefinitions.Count * 32;
267  
268              using MemoryStream stream = new(sizeEstimate);
269  
270              BinaryWriter writer = new(stream, System.Text.Encoding.ASCII);
271  
272              // Header
273              writer.Write(MagicNumber);
274              writer.Write(_version);
275              writer.Write(GeneratorId);
276              writer.Write(_bound);
277              writer.Write(0u);
278  
279              // 1.
280              foreach (Capability capability in _capabilities)
281              {
282                  Instruction capabilityInstruction = NewInstruction(Op.OpCapability);
283  
284                  capabilityInstruction.AddOperand(capability);
285                  capabilityInstruction.Write(writer);
286              }
287  
288              // 2.
289              foreach (string extension in _extensions)
290              {
291                  Instruction extensionInstruction = NewInstruction(Op.OpExtension);
292  
293                  extensionInstruction.AddOperand(extension);
294                  extensionInstruction.Write(writer);
295              }
296  
297              // 3.
298              foreach (Instruction extInstImport in _extInstImports.Values)
299              {
300                  extInstImport.Write(writer);
301              }
302  
303              // 4.
304              Instruction memoryModelInstruction = NewInstruction(Op.OpMemoryModel);
305              memoryModelInstruction.AddOperand(_addressingModel);
306              memoryModelInstruction.AddOperand(_memoryModel);
307              memoryModelInstruction.Write(writer);
308  
309              // 5.
310              foreach (Instruction entrypoint in _entrypoints)
311              {
312                  entrypoint.Write(writer);
313              }
314  
315              // 6.
316              foreach (Instruction executionMode in _executionModes)
317              {
318                  executionMode.Write(writer);
319              }
320  
321              // 7.
322              // TODO: Order debug information correctly.
323              foreach (Instruction debug in _debug)
324              {
325                  debug.Write(writer);
326              }
327  
328              // 8.
329              foreach (Instruction annotation in _annotations)
330              {
331                  annotation.Write(writer);
332              }
333  
334              // Ensure that everything is in the right order in the declarations section.
335              List<Instruction> declarations = new();
336              declarations.AddRange(_typeDeclarationsList);
337              declarations.AddRange(_globals);
338              declarations.AddRange(_constants.Values);
339              declarations.Sort((Instruction x, Instruction y) => x.Id.CompareTo(y.Id));
340  
341              // 9.
342              foreach (Instruction declaration in declarations)
343              {
344                  declaration.Write(writer);
345              }
346  
347              // 10.
348              foreach (Instruction functionDeclaration in _functionsDeclarations)
349              {
350                  functionDeclaration.Write(writer);
351              }
352  
353              // 11.
354              foreach (Instruction functionDefinition in _functionsDefinitions)
355              {
356                  functionDefinition.Write(writer);
357              }
358  
359              _instPool.Clear();
360              _integerPool.Clear();
361  
362              LiteralInteger.UnregisterPool();
363  
364              return stream.ToArray();
365          }
366      }
367  }