authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2022-01-21 16:26:23+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2022-01-28 14:38:57+01:00
log72e67aaf05de06e5a7b74596e70ce5afd17b2919
treefc41c5acdadc49ad48c2824fc31f14927c0356b9
parent462d8fd3ac643c48f61dd58a6d42ef60593cce13

spirv: model spir-v section as separate type

The idea is that this type gains the relevant low-level instruction emitting functions, and that higher-level checks and deduplications are performed somewhere else.

1 files changed, 370 insertions(+), 0 deletions(-)

src/codegen/spirv/Section.zig created+370
......@@ -0,0 +1,370 @@
1//! Represents a section or subsection of instructions in a SPIR-V binary. Instructions can be append
2//! to separate sections, which can then later be merged into the final binary.
3const Section = @This();
4
5const std = @import("std");
6const Allocator = std.mem.Allocator;
7const testing = std.testing;
8
9const spec = @import("spec.zig");
10const Word = spec.Word;
11const DoubleWord = std.meta.Int(.unsigned, @bitSizeOf(Word) * 2);
12const Log2Word = std.math.Log2Int(Word);
13
14const Opcode = spec.Opcode;
15
16/// The instructions in this section. Memory is owned by the Module
17/// externally associated to this Section.
18instructions: std.ArrayListUnmanaged(Word) = .{},
19
20pub fn deinit(section: *Section, allocator: Allocator) void {
21 section.instructions.deinit(allocator);
22 section.* = undefined;
23}
24
25fn writeWord(section: *Section, word: Word) void {
26 section.instructions.appendAssumeCapacity(word);
27}
28
29fn writeWords(section: *Section, words: []const Word) void {
30 section.instructions.appendSliceAssumeCapacity(words);
31}
32
33// Clear the instructions in this section
34pub fn reset(section: *Section) void {
35 section.instructions.items.len = 0;
36}
37
38pub fn emit(
39 section: *Section,
40 allocator: Allocator,
41 comptime opcode: spec.Opcode,
42 operands: opcode.Operands(),
43) !void {
44 const word_count = instructionSize(opcode, operands);
45 try section.instructions.ensureUnusedCapacity(allocator, word_count);
46 section.instructions.appendAssumeCapacity(@intCast(Word, word_count << 16) | @enumToInt(opcode));
47 section.writeOperands(opcode.Operands(), operands);
48}
49
50fn writeOperands(section: *Section, comptime Operands: type, operands: Operands) void {
51 const fields = switch (@typeInfo(Operands)) {
52 .Struct => |info| info.fields,
53 .Void => return,
54 else => unreachable,
55 };
56
57 inline for (fields) |field| {
58 section.writeOperand(field.field_type, @field(operands, field.name));
59 }
60}
61
62fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void {
63 switch (Operand) {
64 spec.IdResultType,
65 spec.IdResult,
66 spec.IdRef
67 => section.writeWord(operand.id),
68
69 spec.LiteralInteger => section.writeWord(operand),
70
71 spec.LiteralString => section.writeString(operand),
72
73 spec.LiteralContextDependentNumber => section.writeContextDependentNumber(operand),
74
75 spec.LiteralExtInstInteger => section.writeWord(operand.inst),
76
77 // TODO: Where this type is used (OpSpecConstantOp) is currently not correct in the spec json,
78 // so it most likely needs to be altered into something that can actually describe the entire
79 // instruction in which it is used.
80 spec.LiteralSpecConstantOpInteger => section.writeWord(@enumToInt(operand.opcode)),
81
82 spec.PairLiteralIntegerIdRef => section.writeWords(&.{ operand.value, operand.label.id }),
83 spec.PairIdRefLiteralInteger => section.writeWords(&.{ operand.target.id, operand.member }),
84 spec.PairIdRefIdRef => section.writeWords(&.{ operand[0].id, operand[1].id }),
85
86 else => switch (@typeInfo(Operand)) {
87 .Enum => section.writeWord(@enumToInt(operand)),
88 .Optional => |info| if (operand) |child| {
89 section.writeOperand(info.child, child);
90 },
91 .Pointer => |info| {
92 std.debug.assert(info.size == .Slice); // Should be no other pointer types in the spec.
93 for (operand) |item| {
94 section.writeOperand(info.child, item);
95 }
96 },
97 .Struct => |info| {
98 if (info.layout == .Packed) {
99 section.writeWord(@bitCast(Word, operand));
100 } else {
101 section.writeExtendedMask(Operand, operand);
102 }
103 },
104 .Union => section.writeExtendedUnion(Operand, operand),
105 else => unreachable,
106 },
107 }
108}
109
110fn writeString(section: *Section, str: []const u8) void {
111 // TODO: Not actually sure whether this is correct for big-endian.
112 // See https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#Literal
113 const zero_terminated_len = str.len + 1;
114 var i: usize = 0;
115 while (i < zero_terminated_len) : (i += @sizeOf(Word)) {
116 var word: Word = 0;
117
118 var j: usize = 0;
119 while (j < @sizeOf(Word) and i + j < str.len) : (j += 1) {
120 word |= @as(Word, str[i + j]) << @intCast(Log2Word, j * std.meta.bitCount(u8));
121 }
122
123 section.instructions.appendAssumeCapacity(word);
124 }
125}
126
127fn writeContextDependentNumber(section: *Section, operand: spec.LiteralContextDependentNumber) void {
128 switch (operand) {
129 .int32 => |int| section.writeWord(@bitCast(Word, int)),
130 .uint32 => |int| section.writeWord(@bitCast(Word, int)),
131 .int64 => |int| section.writeDoubleWord(@bitCast(DoubleWord, int)),
132 .uint64 => |int| section.writeDoubleWord(@bitCast(DoubleWord, int)),
133 .float32 => |float| section.writeWord(@bitCast(Word, float)),
134 .float64 => |float| section.writeDoubleWord(@bitCast(DoubleWord, float)),
135 }
136}
137
138fn writeExtendedMask(section: *Section, comptime Operand: type, operand: Operand) void {
139 var mask: Word = 0;
140 inline for (@typeInfo(Operand).Struct.fields) |field, bit| {
141 switch (@typeInfo(field.field_type)) {
142 .Optional => if (@field(operand, field.name) != null) {
143 mask |= 1 << @intCast(u5, bit);
144 },
145 .Bool => if (@field(operand, field.name)) {
146 mask |= 1 << @intCast(u5, bit);
147 },
148 else => unreachable,
149 }
150 }
151
152 if (mask == 0) {
153 return;
154 }
155
156 section.writeWord(mask);
157
158 inline for (@typeInfo(Operand).Struct.fields) |field| {
159 switch (@typeInfo(field.field_type)) {
160 .Optional => |info| if (@field(operand, field.name)) |child| {
161 section.writeOperands(info.child, child);
162 },
163 .Bool => {},
164 else => unreachable,
165 }
166 }
167}
168
169fn writeExtendedUnion(section: *Section, comptime Operand: type, operand: Operand) void {
170 const tag = std.meta.activeTag(operand);
171 section.writeWord(@enumToInt(tag));
172
173 inline for (@typeInfo(Operand).Union.fields) |field| {
174 if (@field(Operand, field.name) == tag) {
175 section.writeOperands(field.field_type, @field(operand, field.name));
176 return;
177 }
178 }
179 unreachable;
180}
181
182fn instructionSize(comptime opcode: spec.Opcode, operands: opcode.Operands()) usize {
183 return 1 + operandsSize(opcode.Operands(), operands);
184}
185
186fn operandsSize(comptime Operands: type, operands: Operands) usize {
187 const fields = switch (@typeInfo(Operands)) {
188 .Struct => |info| info.fields,
189 .Void => return 0,
190 else => unreachable,
191 };
192
193 var total: usize = 0;
194 inline for (fields) |field| {
195 total += operandSize(field.field_type, @field(operands, field.name));
196 }
197
198 return total;
199}
200
201fn operandSize(comptime Operand: type, operand: Operand) usize {
202 return switch (Operand) {
203 spec.IdResultType,
204 spec.IdResult,
205 spec.IdRef,
206 spec.LiteralInteger,
207 spec.LiteralExtInstInteger,
208 => 1,
209
210 spec.LiteralString => std.math.divCeil(usize, operand.len + 1, @sizeOf(Word)) catch unreachable, // Add one for zero-terminator
211
212 spec.LiteralContextDependentNumber => switch (operand) {
213 .int32, .uint32, .float32 => @as(usize, 1),
214 .int64, .uint64, .float64 => @as(usize, 2),
215 },
216
217 // TODO: Where this type is used (OpSpecConstantOp) is currently not correct in the spec
218 // json, so it most likely needs to be altered into something that can actually
219 // describe the entire insturction in which it is used.
220 spec.LiteralSpecConstantOpInteger => 1,
221
222 spec.PairLiteralIntegerIdRef,
223 spec.PairIdRefLiteralInteger,
224 spec.PairIdRefIdRef,
225 => 2,
226
227 else => switch (@typeInfo(Operand)) {
228 .Enum => 1,
229 .Optional => |info| if (operand) |child| operandSize(info.child, child) else 0,
230 .Pointer => |info| blk: {
231 std.debug.assert(info.size == .Slice); // Should be no other pointer types in the spec.
232 var total: usize = 0;
233 for (operand) |item| {
234 total += operandSize(info.child, item);
235 }
236 break :blk total;
237 },
238 .Struct => |info| if (info.layout == .Packed) 1 else extendedMaskSize(Operand, operand),
239 .Union => extendedUnionSize(Operand, operand),
240 else => unreachable,
241 },
242 };
243}
244
245fn extendedMaskSize(comptime Operand: type, operand: Operand) usize {
246 var total: usize = 0;
247 var any_set = false;
248 inline for (@typeInfo(Operand).Struct.fields) |field| {
249 switch (@typeInfo(field.field_type)) {
250 .Optional => |info| if (@field(operand, field.name)) |child| {
251 total += operandsSize(info.child, child);
252 any_set = true;
253 },
254 .Bool => if (@field(operand, field.name)) {
255 any_set = true;
256 },
257 else => unreachable,
258 }
259 }
260 if (!any_set) {
261 return 0;
262 }
263 return total + 1; // Add one for the mask itself.
264}
265
266fn extendedUnionSize(comptime Operand: type, operand: Operand) usize {
267 const tag = std.meta.activeTag(operand);
268 inline for (@typeInfo(Operand).Union.fields) |field| {
269 if (@field(Operand, field.name) == tag) {
270 // Add one for the tag itself.
271 return 1 + operandsSize(field.field_type, @field(operand, field.name));
272 }
273 }
274 unreachable;
275}
276
277test "SPIR-V Section emit() - no operands" {
278 var section = Section{};
279 defer section.deinit(std.testing.allocator);
280
281 try section.emit(std.testing.allocator, .OpNop, {});
282
283 try testing.expect(section.instructions.items[0] == (@as(Word, 1) << 16) | @enumToInt(Opcode.OpNop));
284}
285
286test "SPIR-V Section emit() - simple" {
287 var section = Section{};
288 defer section.deinit(std.testing.allocator);
289
290 try section.emit(std.testing.allocator, .OpUndef, .{
291 .id_result_type = .{.id = 0},
292 .id_result = .{.id = 1},
293 });
294
295 try testing.expectEqualSlices(Word, &.{
296 (@as(Word, 3) << 16) | @enumToInt(Opcode.OpUndef),
297 0,
298 1,
299 }, section.instructions.items);
300}
301
302test "SPIR-V Section emit() - string" {
303 var section = Section{};
304 defer section.deinit(std.testing.allocator);
305
306 try section.emit(std.testing.allocator, .OpSource, .{
307 .source_language = .Unknown,
308 .version = 123,
309 .file = .{.id = 456},
310 .source = "pub fn main() void {}",
311 });
312
313 try testing.expectEqualSlices(Word, &.{
314 (@as(Word, 10) << 16) | @enumToInt(Opcode.OpSource),
315 @enumToInt(spec.SourceLanguage.Unknown),
316 123,
317 456,
318 std.mem.bytesToValue(Word, "pub "),
319 std.mem.bytesToValue(Word, "fn m"),
320 std.mem.bytesToValue(Word, "ain("),
321 std.mem.bytesToValue(Word, ") vo"),
322 std.mem.bytesToValue(Word, "id {"),
323 std.mem.bytesToValue(Word, "}\x00\x00\x00"),
324 }, section.instructions.items);
325}
326
327test "SPIR-V Section emit()- extended mask" {
328 var section = Section{};
329 defer section.deinit(std.testing.allocator);
330
331 try section.emit(std.testing.allocator, .OpLoopMerge, .{
332 .merge_block = .{.id = 10},
333 .continue_target = .{.id = 20},
334 .loop_control = .{
335 .Unroll = true,
336 .DependencyLength = .{
337 .literal_integer = 2,
338 },
339 },
340 });
341
342 try testing.expectEqualSlices(Word, &.{
343 (@as(Word, 5) << 16) | @enumToInt(Opcode.OpLoopMerge),
344 10,
345 20,
346 @bitCast(Word, spec.LoopControl{.Unroll = true, .DependencyLength = true}),
347 2,
348 }, section.instructions.items);
349}
350
351test "SPIR-V Section emit() - extended union" {
352 var section = Section{};
353 defer section.deinit(std.testing.allocator);
354
355 try section.emit(std.testing.allocator, .OpExecutionMode, .{
356 .entry_point = .{.id = 888},
357 .mode = .{
358 .LocalSize = .{.x_size = 4, .y_size = 8, .z_size = 16},
359 },
360 });
361
362 try testing.expectEqualSlices(Word, &.{
363 (@as(Word, 6) << 16) | @enumToInt(Opcode.OpExecutionMode),
364 888,
365 @enumToInt(spec.ExecutionMode.LocalSize),
366 4,
367 8,
368 16,
369 }, section.instructions.items);
370}