authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-03-11 23:39:23+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-03-18 19:13:49+01:00
loge566158acf034105a43690501664c45b8a065f6a
treef79acb4350a77fddcc9fff44a9cbd3c5f8384c3b
parent9b058117f0f4595d43fbe08a3e659ac865e8b459
signaturebadge-check Signed by SSH key SHA256:ZS52FNyUv2WUXvO4njmVaFVO46RHojFuOrxRc4LuKzg

spirv: make IdResult an enum


6 files changed, 618 insertions(+), 19 deletions(-)

src/codegen/spirv.zig+3-3
......@@ -5158,7 +5158,7 @@ const DeclGen = struct {
51585158 const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];
51595159 extra_index = case.end + case.data.items_len + case_body.len;
51605160
5161 const label = IdRef{ .id = @intCast(first_case_label.id + case_i) };
5161 const label: IdRef = @enumFromInt(@intFromEnum(first_case_label) + case_i);
51625162
51635163 for (items) |item| {
51645164 const value = (try self.air.value(item, mod)) orelse unreachable;
......@@ -5172,7 +5172,7 @@ const DeclGen = struct {
51725172 else => unreachable,
51735173 };
51745174 const int_lit: spec.LiteralContextDependentNumber = switch (cond_words) {
5175 1 => .{ .uint32 = @as(u32, @intCast(int_val)) },
5175 1 => .{ .uint32 = @intCast(int_val) },
51765176 2 => .{ .uint64 = int_val },
51775177 else => unreachable,
51785178 };
......@@ -5197,7 +5197,7 @@ const DeclGen = struct {
51975197 const case_body: []const Air.Inst.Index = @ptrCast(self.air.extra[case.end + items.len ..][0..case.data.body_len]);
51985198 extra_index = case.end + case.data.items_len + case_body.len;
51995199
5200 const label = IdResult{ .id = @intCast(first_case_label.id + case_i) };
5200 const label: IdResult = @enumFromInt(@intFromEnum(first_case_label) + case_i);
52015201
52025202 try self.beginSpvBlock(label);
52035203
src/codegen/spirv/Module.zig+2-2
......@@ -215,12 +215,12 @@ pub fn deinit(self: *Module) void {
215215
216216pub fn allocId(self: *Module) spec.IdResult {
217217 defer self.next_result_id += 1;
218 return .{ .id = self.next_result_id };
218 return @enumFromInt(self.next_result_id);
219219}
220220
221221pub fn allocIds(self: *Module, n: u32) spec.IdResult {
222222 defer self.next_result_id += n;
223 return .{ .id = self.next_result_id };
223 return @enumFromInt(self.next_result_id);
224224}
225225
226226pub fn idBound(self: Module) Word {
src/codegen/spirv/Section.zig+10-10
......@@ -123,7 +123,7 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands)
123123
124124pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void {
125125 switch (Operand) {
126 spec.IdResult => section.writeWord(operand.id),
126 spec.IdResult => section.writeWord(@intFromEnum(operand)),
127127
128128 spec.LiteralInteger => section.writeWord(operand),
129129
......@@ -138,9 +138,9 @@ pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand)
138138 // instruction in which it is used.
139139 spec.LiteralSpecConstantOpInteger => section.writeWord(@intFromEnum(operand.opcode)),
140140
141 spec.PairLiteralIntegerIdRef => section.writeWords(&.{ operand.value, operand.label.id }),
142 spec.PairIdRefLiteralInteger => section.writeWords(&.{ operand.target.id, operand.member }),
143 spec.PairIdRefIdRef => section.writeWords(&.{ operand[0].id, operand[1].id }),
141 spec.PairLiteralIntegerIdRef => section.writeWords(&.{ operand.value, @enumFromInt(operand.label) }),
142 spec.PairIdRefLiteralInteger => section.writeWords(&.{ @intFromEnum(operand.target), operand.member }),
143 spec.PairIdRefIdRef => section.writeWords(&.{ @intFromEnum(operand[0]), @intFromEnum(operand[1]) }),
144144
145145 else => switch (@typeInfo(Operand)) {
146146 .Enum => section.writeWord(@intFromEnum(operand)),
......@@ -338,8 +338,8 @@ test "SPIR-V Section emit() - simple" {
338338 defer section.deinit(std.testing.allocator);
339339
340340 try section.emit(std.testing.allocator, .OpUndef, .{
341 .id_result_type = .{ .id = 0 },
342 .id_result = .{ .id = 1 },
341 .id_result_type = @enumFromInt(0),
342 .id_result = @enumFromInt(1),
343343 });
344344
345345 try testing.expectEqualSlices(Word, &.{
......@@ -356,7 +356,7 @@ test "SPIR-V Section emit() - string" {
356356 try section.emit(std.testing.allocator, .OpSource, .{
357357 .source_language = .Unknown,
358358 .version = 123,
359 .file = .{ .id = 456 },
359 .file = @enumFromInt(256),
360360 .source = "pub fn main() void {}",
361361 });
362362
......@@ -381,8 +381,8 @@ test "SPIR-V Section emit() - extended mask" {
381381 defer section.deinit(std.testing.allocator);
382382
383383 try section.emit(std.testing.allocator, .OpLoopMerge, .{
384 .merge_block = .{ .id = 10 },
385 .continue_target = .{ .id = 20 },
384 .merge_block = @enumFromInt(10),
385 .continue_target = @enumFromInt(20),
386386 .loop_control = .{
387387 .Unroll = true,
388388 .DependencyLength = .{
......@@ -405,7 +405,7 @@ test "SPIR-V Section emit() - extended union" {
405405 defer section.deinit(std.testing.allocator);
406406
407407 try section.emit(std.testing.allocator, .OpExecutionMode, .{
408 .entry_point = .{ .id = 888 },
408 .entry_point = @enumFromInt(888),
409409 .mode = .{
410410 .LocalSize = .{ .x_size = 4, .y_size = 8, .z_size = 16 },
411411 },
src/codegen/spirv/spec.zig+3-2
......@@ -12,8 +12,9 @@ pub const Version = packed struct(Word) {
1212};
1313
1414pub const Word = u32;
15pub const IdResult = struct {
16 id: Word,
15pub const IdResult = enum(Word) {
16 none,
17 _,
1718};
1819pub const IdResultType = IdResult;
1920pub const IdRef = IdResult;
src/link/SpirV/BinaryModule.zig created+597
......@@ -0,0 +1,597 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const Allocator = std.mem.Allocator;
4const log = std.log.scoped(.spirv_parse);
5
6const spec = @import("../../codegen/spirv/spec.zig");
7const Opcode = spec.Opcode;
8const Word = spec.Word;
9const InstructionSet = spec.InstructionSet;
10const ResultId = spec.IdResult;
11
12const BinaryModule = @This();
13
14pub const header_words = 5;
15
16/// The module SPIR-V version.
17version: spec.Version,
18
19/// The generator magic number.
20generator_magic: u32,
21
22/// The result-id bound of this SPIR-V module.
23id_bound: u32,
24
25/// The instructions of this module. This does not contain the header.
26instructions: []const Word,
27
28/// Maps OpExtInstImport result-ids to their InstructionSet.
29ext_inst_map: std.AutoHashMapUnmanaged(ResultId, InstructionSet),
30
31/// This map contains the width of arithmetic types (OpTypeInt and
32/// OpTypeFloat). We need this information to correctly parse the operands
33/// of Op(Spec)Constant and OpSwitch.
34arith_type_width: std.AutoHashMapUnmanaged(ResultId, u16),
35
36pub fn deinit(self: *BinaryModule, a: Allocator) void {
37 self.ext_inst_map.deinit(a);
38 self.arith_type_width.deinit(a);
39 self.* = undefined;
40}
41
42pub fn iterateInstructions(self: BinaryModule) Instruction.Iterator {
43 return Instruction.Iterator.init(self.instructions);
44}
45
46/// Errors that can be raised when the module is not correct.
47/// Note that the parser doesn't validate SPIR-V modules by a
48/// long shot. It only yields errors that critically prevent
49/// further analysis of the module.
50pub const ParseError = error{
51 /// Raised when the module doesn't start with the SPIR-V magic.
52 /// This usually means that the module isn't actually SPIR-V.
53 InvalidMagic,
54 /// Raised when the module has an invalid "physical" format:
55 /// For example when the header is incomplete, or an instruction
56 /// has an illegal format.
57 InvalidPhysicalFormat,
58 /// OpExtInstImport was used with an unknown extension string.
59 InvalidExtInstImport,
60 /// The module had an instruction with an invalid (unknown) opcode.
61 InvalidOpcode,
62 /// An instruction's operands did not conform to the SPIR-V specification
63 /// for that instruction.
64 InvalidOperands,
65 /// A result-id was declared more than once.
66 DuplicateId,
67 /// Some ID did not resolve.
68 InvalidId,
69 /// Parser ran out of memory.
70 OutOfMemory,
71};
72
73pub const Instruction = struct {
74 pub const Iterator = struct {
75 words: []const Word,
76 index: usize = 0,
77 offset: usize = 0,
78
79 pub fn init(words: []const Word) Iterator {
80 return .{ .words = words };
81 }
82
83 pub fn next(self: *Iterator) ?Instruction {
84 if (self.offset >= self.words.len) return null;
85
86 const instruction_len = self.words[self.offset] >> 16;
87 defer self.offset += instruction_len;
88 defer self.index += 1;
89 assert(instruction_len != 0 and self.offset < self.words.len); // Verified in BinaryModule.parse.
90
91 return Instruction{
92 .opcode = @enumFromInt(self.words[self.offset] & 0xFFFF),
93 .index = self.index,
94 .offset = self.offset,
95 .operands = self.words[self.offset..][1..instruction_len],
96 };
97 }
98 };
99
100 /// The opcode for this instruction.
101 opcode: Opcode,
102 /// The instruction's index.
103 index: usize,
104 /// The instruction's word offset in the module.
105 offset: usize,
106 /// The raw (unparsed) operands for this instruction.
107 operands: []const Word,
108};
109
110/// This struct is used to return information about
111/// a module's functions - entry points, functions,
112/// list of callees.
113pub const FunctionInfo = struct {
114 /// Information that is gathered about a particular function.
115 pub const Fn = struct {
116 /// The word-offset of the first word (of the OpFunction instruction)
117 /// of this instruction.
118 begin_offset: usize,
119 /// The past-end offset of the end (including operands) of the last
120 /// instruction of the function.
121 end_offset: usize,
122 /// The index of the first callee in `callee_store`.
123 first_callee: usize,
124 /// The module offset of the OpTypeFunction instruction corresponding
125 /// to this function.
126 /// We use an offset so that we don't need to keep a separate map.
127 type_offset: usize,
128 };
129
130 /// Maps function result-id -> Function information structure.
131 functions: std.AutoArrayHashMapUnmanaged(ResultId, Fn),
132 /// List of entry points in this module. Contains OpFunction result-ids.
133 entry_points: []const ResultId,
134 /// For each function, a list of function result-ids that it calls.
135 callee_store: []const ResultId,
136
137 pub fn deinit(self: *FunctionInfo, a: Allocator) void {
138 self.functions.deinit(a);
139 a.free(self.entry_points);
140 a.free(self.callee_store);
141 self.* = undefined;
142 }
143
144 /// Fetch the list of callees per function. Guaranteed to contain only unique IDs.
145 pub fn callees(self: FunctionInfo, fn_id: ResultId) []const ResultId {
146 const fn_index = self.functions.getIndex(fn_id).?;
147 const values = self.functions.values();
148 const first_callee = values[fn_index].first_callee;
149 if (fn_index == values.len - 1) {
150 return self.callee_store[first_callee..];
151 } else {
152 const next_first_callee = values[fn_index + 1].first_callee;
153 return self.callee_store[first_callee..next_first_callee];
154 }
155 }
156
157 /// Returns a topological ordering of the functions: For each item
158 /// in the returned list of OpFunction result-ids, it is guaranteed that
159 /// the callees have a lower index. Note that SPIR-V does not support
160 /// any recursion, so this always works.
161 pub fn topologicalSort(self: FunctionInfo, a: Allocator) ![]const ResultId {
162 var sort = std.ArrayList(ResultId).init(a);
163 defer sort.deinit();
164
165 var seen = try std.DynamicBitSetUnmanaged.initEmpty(a, self.functions.count());
166 defer seen.deinit(a);
167
168 var stack = std.ArrayList(ResultId).init(a);
169 defer stack.deinit();
170
171 for (self.functions.keys()) |id| {
172 try self.topologicalSortStep(id, &sort, &seen);
173 }
174
175 return try sort.toOwnedSlice();
176 }
177
178 fn topologicalSortStep(
179 self: FunctionInfo,
180 id: ResultId,
181 sort: *std.ArrayList(ResultId),
182 seen: *std.DynamicBitSetUnmanaged,
183 ) !void {
184 const fn_index = self.functions.getIndex(id) orelse {
185 log.err("function calls invalid callee-id {}", .{@intFromEnum(id)});
186 return error.InvalidId;
187 };
188 if (seen.isSet(fn_index)) {
189 return;
190 }
191
192 seen.set(fn_index);
193 for (self.callees(id)) |callee| {
194 try self.topologicalSortStep(callee, sort, seen);
195 }
196
197 try sort.append(id);
198 }
199};
200
201/// This parser contains information (acceleration tables)
202/// that can be persisted across different modules. This is
203/// used to initialize the module, and is also used when
204/// further analyzing it.
205pub const Parser = struct {
206 /// The allocator used to allocate this parser's structures,
207 /// and also the structures of any parsed module.
208 a: Allocator,
209
210 /// Maps (instruction set, opcode) => instruction index (for instruction set)
211 opcode_table: std.AutoHashMapUnmanaged(u32, u16) = .{},
212
213 pub fn init(a: Allocator) !Parser {
214 var self = Parser{
215 .a = a,
216 };
217 errdefer self.deinit();
218
219 inline for (std.meta.tags(InstructionSet)) |set| {
220 const instructions = set.instructions();
221 try self.opcode_table.ensureUnusedCapacity(a, @intCast(instructions.len));
222 for (instructions, 0..) |inst, i| {
223 // Note: Some instructions may alias another. In this case we don't really care
224 // which one is first: they all (should) have the same operands anyway. Just pick
225 // the first, which is usually the core, KHR or EXT variant.
226 const entry = self.opcode_table.getOrPutAssumeCapacity(mapSetAndOpcode(set, @intCast(inst.opcode)));
227 if (!entry.found_existing) {
228 entry.value_ptr.* = @intCast(i);
229 }
230 }
231 }
232
233 return self;
234 }
235
236 pub fn deinit(self: *Parser) void {
237 self.opcode_table.deinit(self.a);
238 }
239
240 fn mapSetAndOpcode(set: InstructionSet, opcode: u16) u32 {
241 return (@as(u32, @intFromEnum(set)) << 16) | opcode;
242 }
243
244 pub fn parse(self: *Parser, module: []const u32) ParseError!BinaryModule {
245 if (module[0] != spec.magic_number) {
246 return error.InvalidMagic;
247 } else if (module.len < header_words) {
248 log.err("module only has {}/{} header words", .{ module.len, header_words });
249 return error.InvalidPhysicalFormat;
250 }
251
252 var binary = BinaryModule{
253 .version = @bitCast(module[1]),
254 .generator_magic = module[2],
255 .id_bound = module[3],
256 .instructions = module[header_words..],
257 .ext_inst_map = .{},
258 .arith_type_width = .{},
259 };
260
261 // First pass through the module to verify basic structure and
262 // to gather some initial stuff for more detailed analysis.
263 // We want to check some stuff that Instruction.Iterator is no good for,
264 // so just iterate manually.
265 var offset: usize = 0;
266 while (offset < binary.instructions.len) {
267 const len = binary.instructions[offset] >> 16;
268 if (len == 0 or len + offset > binary.instructions.len) {
269 log.err("invalid instruction format: len={}, end={}, module len={}", .{ len, len + offset, binary.instructions.len });
270 return error.InvalidPhysicalFormat;
271 }
272 defer offset += len;
273
274 // We can't really efficiently use non-exhaustive enums here, because we would
275 // need to manually write out all valid cases. Since we have this map anyway, just
276 // use that.
277 const opcode_num: u16 = @truncate(binary.instructions[offset]);
278 const index = self.opcode_table.get(mapSetAndOpcode(.core, opcode_num)) orelse {
279 log.err("invalid opcode for core set: {}", .{opcode_num});
280 return error.InvalidOpcode;
281 };
282
283 const opcode: Opcode = @enumFromInt(opcode_num);
284 const operands = binary.instructions[offset..][1..len];
285 switch (opcode) {
286 .OpExtInstImport => {
287 const set_name = std.mem.sliceTo(std.mem.sliceAsBytes(operands[1..]), 0);
288 const set = std.meta.stringToEnum(InstructionSet, set_name) orelse {
289 log.err("invalid instruction set '{s}'", .{set_name});
290 return error.InvalidExtInstImport;
291 };
292 if (set == .core) return error.InvalidExtInstImport;
293 try binary.ext_inst_map.put(self.a, @enumFromInt(operands[0]), set);
294 },
295 .OpTypeInt, .OpTypeFloat => {
296 const entry = try binary.arith_type_width.getOrPut(self.a, @enumFromInt(operands[0]));
297 if (entry.found_existing) return error.DuplicateId;
298 entry.value_ptr.* = std.math.cast(u16, operands[1]) orelse return error.InvalidOperands;
299 },
300 else => {},
301 }
302
303 // OpSwitch takes a value as argument, not an OpType... hence we need to populate arith_type_width
304 // with ALL operations that return an int or float.
305 const proper_operands = InstructionSet.core.instructions()[index].operands;
306
307 if (proper_operands.len >= 2 and
308 proper_operands[0].kind == .IdResultType and
309 proper_operands[1].kind == .IdResult)
310 {
311 if (operands.len < 2) return error.InvalidOperands;
312 if (binary.arith_type_width.get(@enumFromInt(operands[0]))) |width| {
313 const entry = try binary.arith_type_width.getOrPut(self.a, @enumFromInt(operands[1]));
314 if (entry.found_existing) return error.DuplicateId;
315 entry.value_ptr.* = width;
316 }
317 }
318 }
319
320 return binary;
321 }
322
323 pub fn parseFunctionInfo(self: *Parser, binary: BinaryModule) ParseError!FunctionInfo {
324 var entry_points = std.AutoArrayHashMap(ResultId, void).init(self.a);
325 defer entry_points.deinit();
326
327 var functions = std.AutoArrayHashMap(ResultId, FunctionInfo.Fn).init(self.a);
328 errdefer functions.deinit();
329
330 var fn_ty_decls = std.AutoHashMap(ResultId, usize).init(self.a);
331 defer fn_ty_decls.deinit();
332
333 var calls = std.AutoArrayHashMap(ResultId, void).init(self.a);
334 defer calls.deinit();
335
336 var callee_store = std.ArrayList(ResultId).init(self.a);
337 defer callee_store.deinit();
338
339 var maybe_current_function: ?ResultId = null;
340 var begin: usize = undefined;
341 var fn_ty_id: ResultId = undefined;
342
343 var it = binary.iterateInstructions();
344 while (it.next()) |inst| {
345 switch (inst.opcode) {
346 .OpEntryPoint => {
347 const entry = try entry_points.getOrPut(@enumFromInt(inst.operands[1]));
348 if (entry.found_existing) return error.DuplicateId;
349 },
350 .OpTypeFunction => {
351 const entry = try fn_ty_decls.getOrPut(@enumFromInt(inst.operands[0]));
352 if (entry.found_existing) return error.DuplicateId;
353 entry.value_ptr.* = inst.offset;
354 },
355 .OpFunction => {
356 maybe_current_function = @enumFromInt(inst.operands[1]);
357 begin = inst.offset;
358 fn_ty_id = @enumFromInt(inst.operands[3]);
359 },
360 .OpFunctionCall => {
361 const callee: ResultId = @enumFromInt(inst.operands[2]);
362 try calls.put(callee, {});
363 },
364 .OpFunctionEnd => {
365 const current_function = maybe_current_function orelse {
366 log.err("encountered OpFunctionEnd without corresponding OpFunction", .{});
367 return error.InvalidPhysicalFormat;
368 };
369 const entry = try functions.getOrPut(current_function);
370 if (entry.found_existing) return error.DuplicateId;
371
372 const first_callee = callee_store.items.len;
373 try callee_store.appendSlice(calls.keys());
374
375 const type_offset = fn_ty_decls.get(fn_ty_id) orelse {
376 log.err("Invalid OpFunction type", .{});
377 return error.InvalidId;
378 };
379
380 entry.value_ptr.* = .{
381 .begin_offset = begin,
382 .end_offset = it.offset, // Use past-end offset
383 .first_callee = first_callee,
384 .type_offset = type_offset,
385 };
386 maybe_current_function = null;
387 calls.clearRetainingCapacity();
388 },
389 else => {},
390 }
391 }
392
393 if (maybe_current_function != null) {
394 log.err("final OpFunction does not have an OpFunctionEnd", .{});
395 return error.InvalidPhysicalFormat;
396 }
397
398 return FunctionInfo{
399 .functions = functions.unmanaged,
400 .entry_points = try self.a.dupe(ResultId, entry_points.keys()),
401 .callee_store = try callee_store.toOwnedSlice(),
402 };
403 }
404
405 /// Parse offsets in the instruction that contain result-ids.
406 /// Returned offsets are relative to inst.operands.
407 /// Returns in an arraylist to armortize allocations.
408 pub fn parseInstructionResultIds(
409 self: *Parser,
410 binary: BinaryModule,
411 inst: Instruction,
412 offsets: *std.ArrayList(u16),
413 ) !void {
414 const index = self.opcode_table.get(mapSetAndOpcode(.core, @intFromEnum(inst.opcode))).?;
415 const operands = InstructionSet.core.instructions()[index].operands;
416
417 var offset: usize = 0;
418 switch (inst.opcode) {
419 .OpSpecConstantOp => {
420 assert(operands[0].kind == .IdResultType);
421 assert(operands[1].kind == .IdResult);
422 offset = try self.parseOperandsResultIds(binary, inst, operands[0..2], offset, offsets);
423
424 if (offset >= inst.operands.len) return error.InvalidPhysicalFormat;
425 const spec_opcode = std.math.cast(u16, inst.operands[offset]) orelse return error.InvalidPhysicalFormat;
426 const spec_index = self.opcode_table.get(mapSetAndOpcode(.core, spec_opcode)) orelse
427 return error.InvalidPhysicalFormat;
428 const spec_operands = InstructionSet.core.instructions()[spec_index].operands;
429 assert(spec_operands[0].kind == .IdResultType);
430 assert(spec_operands[1].kind == .IdResult);
431 offset = try self.parseOperandsResultIds(binary, inst, spec_operands[2..], offset + 1, offsets);
432 },
433 .OpExtInst => {
434 assert(operands[0].kind == .IdResultType);
435 assert(operands[1].kind == .IdResult);
436 offset = try self.parseOperandsResultIds(binary, inst, operands[0..2], offset, offsets);
437
438 if (offset + 1 >= inst.operands.len) return error.InvalidPhysicalFormat;
439 const set_id: ResultId = @enumFromInt(inst.operands[offset]);
440 const set = binary.ext_inst_map.get(set_id) orelse {
441 log.err("Invalid instruction set {}", .{@intFromEnum(set_id)});
442 return error.InvalidId;
443 };
444 const ext_opcode = std.math.cast(u16, inst.operands[offset + 1]) orelse return error.InvalidPhysicalFormat;
445 const ext_index = self.opcode_table.get(mapSetAndOpcode(set, ext_opcode)) orelse
446 return error.InvalidPhysicalFormat;
447 const ext_operands = set.instructions()[ext_index].operands;
448 offset = try self.parseOperandsResultIds(binary, inst, ext_operands, offset + 2, offsets);
449 },
450 else => {
451 offset = try self.parseOperandsResultIds(binary, inst, operands, offset, offsets);
452 },
453 }
454
455 if (offset != inst.operands.len) return error.InvalidPhysicalFormat;
456 }
457
458 fn parseOperandsResultIds(
459 self: *Parser,
460 binary: BinaryModule,
461 inst: Instruction,
462 operands: []const spec.Operand,
463 start_offset: usize,
464 offsets: *std.ArrayList(u16),
465 ) !usize {
466 var offset = start_offset;
467 for (operands) |operand| {
468 offset = try self.parseOperandResultIds(binary, inst, operand, offset, offsets);
469 }
470 return offset;
471 }
472
473 fn parseOperandResultIds(
474 self: *Parser,
475 binary: BinaryModule,
476 inst: Instruction,
477 operand: spec.Operand,
478 start_offset: usize,
479 offsets: *std.ArrayList(u16),
480 ) !usize {
481 var offset = start_offset;
482 switch (operand.quantifier) {
483 .variadic => while (offset < inst.operands.len) {
484 offset = try self.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets);
485 },
486 .optional => if (offset < inst.operands.len) {
487 offset = try self.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets);
488 },
489 .required => {
490 offset = try self.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets);
491 },
492 }
493 return offset;
494 }
495
496 fn parseOperandKindResultIds(
497 self: *Parser,
498 binary: BinaryModule,
499 inst: Instruction,
500 kind: spec.OperandKind,
501 start_offset: usize,
502 offsets: *std.ArrayList(u16),
503 ) !usize {
504 var offset = start_offset;
505 if (offset >= inst.operands.len) return error.InvalidPhysicalFormat;
506
507 switch (kind.category()) {
508 .bit_enum => {
509 const mask = inst.operands[offset];
510 offset += 1;
511 for (kind.enumerants()) |enumerant| {
512 if ((mask & enumerant.value) != 0) {
513 for (enumerant.parameters) |param_kind| {
514 offset = try self.parseOperandKindResultIds(binary, inst, param_kind, offset, offsets);
515 }
516 }
517 }
518 },
519 .value_enum => {
520 const value = inst.operands[offset];
521 offset += 1;
522 for (kind.enumerants()) |enumerant| {
523 if (value == enumerant.value) {
524 for (enumerant.parameters) |param_kind| {
525 offset = try self.parseOperandKindResultIds(binary, inst, param_kind, offset, offsets);
526 }
527 break;
528 }
529 }
530 },
531 .id => {
532 const this_offset = std.math.cast(u16, offset) orelse return error.InvalidPhysicalFormat;
533 try offsets.append(this_offset);
534 offset += 1;
535 },
536 else => switch (kind) {
537 .LiteralInteger, .LiteralFloat => offset += 1,
538 .LiteralString => while (true) {
539 if (offset >= inst.operands.len) return error.InvalidPhysicalFormat;
540 const word = inst.operands[offset];
541 offset += 1;
542
543 if (word & 0xFF000000 == 0 or
544 word & 0x00FF0000 == 0 or
545 word & 0x0000FF00 == 0 or
546 word & 0x000000FF == 0)
547 {
548 break;
549 }
550 },
551 .LiteralContextDependentNumber => {
552 assert(inst.opcode == .OpConstant or inst.opcode == .OpSpecConstantOp);
553 const bit_width = binary.arith_type_width.get(@enumFromInt(inst.operands[0])) orelse {
554 log.err("invalid LiteralContextDependentNumber type {}", .{inst.operands[0]});
555 return error.InvalidId;
556 };
557 offset += switch (bit_width) {
558 1...32 => 1,
559 33...64 => 2,
560 else => unreachable,
561 };
562 },
563 .LiteralExtInstInteger => unreachable,
564 .LiteralSpecConstantOpInteger => unreachable,
565 .PairLiteralIntegerIdRef => { // Switch case
566 assert(inst.opcode == .OpSwitch);
567 const bit_width = binary.arith_type_width.get(@enumFromInt(inst.operands[0])) orelse {
568 log.err("invalid OpSwitch type {}", .{inst.operands[0]});
569 return error.InvalidId;
570 };
571 offset += switch (bit_width) {
572 1...32 => 1,
573 33...64 => 2,
574 else => unreachable,
575 };
576 const this_offset = std.math.cast(u16, offset) orelse return error.InvalidPhysicalFormat;
577 try offsets.append(this_offset);
578 offset += 1;
579 },
580 .PairIdRefLiteralInteger => {
581 const this_offset = std.math.cast(u16, offset) orelse return error.InvalidPhysicalFormat;
582 try offsets.append(this_offset);
583 offset += 2;
584 },
585 .PairIdRefIdRef => {
586 const a = std.math.cast(u16, offset) orelse return error.InvalidPhysicalFormat;
587 const b = std.math.cast(u16, offset + 1) orelse return error.InvalidPhysicalFormat;
588 try offsets.append(a);
589 try offsets.append(b);
590 offset += 2;
591 },
592 else => unreachable,
593 },
594 }
595 return offset;
596 }
597};
tools/gen_spirv_spec.zig+3-2
......@@ -154,8 +154,9 @@ fn render(writer: anytype, a: Allocator, registry: CoreRegistry, extensions: []c
154154 \\};
155155 \\
156156 \\pub const Word = u32;
157 \\pub const IdResult = struct{
158 \\ id: Word,
157 \\pub const IdResult = enum(Word) {
158 \\ none,
159 \\ _,
159160 \\};
160161 \\pub const IdResultType = IdResult;
161162 \\pub const IdRef = IdResult;