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 = @Int(.unsigned, @bitSizeOf(Word) * 2);
12const Log2Word = std.math.Log2Int(Word);
13
14const Opcode = spec.Opcode;
15
16instructions: std.ArrayList(Word) = .empty,
17
18pub fn deinit(section: *Section, allocator: Allocator) void {
19 section.instructions.deinit(allocator);
20 section.* = undefined;
21}
22
23pub fn reset(section: *Section) void {
24 section.instructions.clearRetainingCapacity();
25}
26
27pub fn toWords(section: Section) []Word {
28 return section.instructions.items;
29}
30
31/// Append the instructions from another section into this section.
32pub fn append(section: *Section, allocator: Allocator, other_section: Section) !void {
33 try section.instructions.appendSlice(allocator, other_section.instructions.items);
34}
35
36pub fn ensureUnusedCapacity(
37 section: *Section,
38 allocator: Allocator,
39 words: usize,
40) !void {
41 try section.instructions.ensureUnusedCapacity(allocator, words);
42}
43
44/// Write an instruction and size, operands are to be inserted manually.
45pub fn emitRaw(
46 section: *Section,
47 allocator: Allocator,
48 opcode: Opcode,
49 operand_words: usize,
50) !void {
51 const word_count = 1 + operand_words;
52 if (word_count > std.math.maxInt(u16)) return error.OutOfMemory;
53 try section.instructions.ensureUnusedCapacity(allocator, word_count);
54 section.writeWord((@as(Word, @intCast(word_count)) << 16) | @backingInt(opcode));
55}
56
57/// Write an entire instruction, including all operands
58pub fn emitRawInstruction(
59 section: *Section,
60 allocator: Allocator,
61 opcode: Opcode,
62 operands: []const Word,
63) !void {
64 try section.emitRaw(allocator, opcode, operands.len);
65 section.writeWords(operands);
66}
67
68pub fn emitAssumeCapacity(
69 section: *Section,
70 comptime opcode: spec.Opcode,
71 operands: opcode.Operands(),
72) !void {
73 const word_count = instructionSize(opcode, operands);
74 if (word_count > std.math.maxInt(u16)) return error.OutOfMemory;
75 section.writeWord((@as(Word, @intCast(word_count)) << 16) | @backingInt(opcode));
76 section.writeOperands(opcode.Operands(), operands);
77}
78
79pub fn emit(
80 section: *Section,
81 allocator: Allocator,
82 comptime opcode: spec.Opcode,
83 operands: opcode.Operands(),
84) !void {
85 const word_count = instructionSize(opcode, operands);
86 if (word_count > std.math.maxInt(u16)) return error.OutOfMemory;
87 try section.instructions.ensureUnusedCapacity(allocator, word_count);
88 section.writeWord((@as(Word, @intCast(word_count)) << 16) | @backingInt(opcode));
89 section.writeOperands(opcode.Operands(), operands);
90}
91
92pub fn writeWord(section: *Section, word: Word) void {
93 section.instructions.appendAssumeCapacity(word);
94}
95
96pub fn writeWords(section: *Section, words: []const Word) void {
97 section.instructions.appendSliceAssumeCapacity(words);
98}
99
100pub fn writeDoubleWord(section: *Section, dword: DoubleWord) void {
101 section.writeWords(&.{
102 @truncate(dword),
103 @truncate(dword >> @bitSizeOf(Word)),
104 });
105}
106
107fn writeOperands(section: *Section, comptime Operands: type, operands: Operands) void {
108 const info = switch (@typeInfo(Operands)) {
109 .@"struct" => |info| info,
110 .void => return,
111 else => unreachable,
112 };
113 inline for (info.field_names, info.field_types) |field_name, field_type| {
114 section.writeOperand(field_type, @field(operands, field_name));
115 }
116}
117
118pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void {
119 switch (Operand) {
120 spec.LiteralSpecConstantOpInteger => unreachable,
121 spec.Id => section.writeWord(@backingInt(operand)),
122 spec.LiteralInteger => section.writeWord(operand),
123 spec.LiteralString => section.writeString(operand),
124 spec.LiteralContextDependentNumber => section.writeContextDependentNumber(operand),
125 spec.LiteralExtInstInteger => section.writeWord(operand.inst),
126 spec.PairLiteralIntegerIdRef => section.writeWords(&.{ operand.value, @fromBackingInt(@intCast(operand.label)) }),
127 spec.PairIdRefLiteralInteger => section.writeWords(&.{ @backingInt(operand.target), operand.member }),
128 spec.PairIdRefIdRef => section.writeWords(&.{ @backingInt(operand[0]), @backingInt(operand[1]) }),
129 else => switch (@typeInfo(Operand)) {
130 .@"enum" => section.writeWord(@backingInt(operand)),
131 .optional => |info| if (operand) |child| section.writeOperand(info.child, child),
132 .pointer => |info| {
133 std.debug.assert(info.size == .slice); // Should be no other pointer types in the spec.
134 for (operand) |item| {
135 section.writeOperand(info.child, item);
136 }
137 },
138 .@"struct" => |info| {
139 if (info.layout == .@"packed") {
140 section.writeWord(@as(Word, @bitCast(operand)));
141 } else {
142 section.writeExtendedMask(Operand, operand);
143 }
144 },
145 .@"union" => section.writeExtendedUnion(Operand, operand),
146 else => unreachable,
147 },
148 }
149}
150
151fn writeString(section: *Section, str: []const u8) void {
152 const zero_terminated_len = str.len + 1;
153 var i: usize = 0;
154 while (i < zero_terminated_len) : (i += @sizeOf(Word)) {
155 var word: Word = 0;
156 var j: usize = 0;
157 while (j < @sizeOf(Word) and i + j < str.len) : (j += 1) {
158 word |= @as(Word, str[i + j]) << @as(Log2Word, @intCast(j * @bitSizeOf(u8)));
159 }
160 section.instructions.appendAssumeCapacity(word);
161 }
162}
163
164fn writeContextDependentNumber(section: *Section, operand: spec.LiteralContextDependentNumber) void {
165 switch (operand) {
166 .int32 => |int| section.writeWord(@bitCast(int)),
167 .uint32 => |int| section.writeWord(@bitCast(int)),
168 .int64 => |int| section.writeDoubleWord(@bitCast(int)),
169 .uint64 => |int| section.writeDoubleWord(@bitCast(int)),
170 .float32 => |float| section.writeWord(@bitCast(float)),
171 .float64 => |float| section.writeDoubleWord(@bitCast(float)),
172 }
173}
174
175fn writeExtendedMask(section: *Section, comptime Operand: type, operand: Operand) void {
176 var mask: Word = 0;
177 const info = @typeInfo(Operand).@"struct";
178 inline for (info.field_names, info.field_types, 0..) |field_name, field_type, bit| {
179 switch (@typeInfo(field_type)) {
180 .optional => if (@field(operand, field_name) != null) {
181 mask |= 1 << @as(u5, @intCast(bit));
182 },
183 .bool => if (@field(operand, field_name)) {
184 mask |= 1 << @as(u5, @intCast(bit));
185 },
186 else => unreachable,
187 }
188 }
189
190 section.writeWord(mask);
191
192 inline for (info.field_names, info.field_types) |field_name, field_type| {
193 switch (@typeInfo(field_type)) {
194 .optional => |opt_info| if (@field(operand, field_name)) |child| {
195 section.writeOperands(opt_info.child, child);
196 },
197 .bool => {},
198 else => unreachable,
199 }
200 }
201}
202
203fn writeExtendedUnion(section: *Section, comptime Operand: type, operand: Operand) void {
204 return switch (operand) {
205 inline else => |op, tag| {
206 section.writeWord(@backingInt(tag));
207 section.writeOperands(
208 @FieldType(Operand, @tagName(tag)),
209 op,
210 );
211 },
212 };
213}
214
215fn instructionSize(comptime opcode: spec.Opcode, operands: opcode.Operands()) usize {
216 return operandsSize(opcode.Operands(), operands) + 1;
217}
218
219fn operandsSize(comptime Operands: type, operands: Operands) usize {
220 const info = switch (@typeInfo(Operands)) {
221 .@"struct" => |info| info,
222 .void => return 0,
223 else => unreachable,
224 };
225
226 var total: usize = 0;
227 inline for (info.field_names, info.field_types) |field_name, field_type| {
228 total += operandSize(field_type, @field(operands, field_name));
229 }
230
231 return total;
232}
233
234fn operandSize(comptime Operand: type, operand: Operand) usize {
235 return switch (Operand) {
236 spec.LiteralSpecConstantOpInteger => unreachable,
237 spec.Id, spec.LiteralInteger, spec.LiteralExtInstInteger => 1,
238 spec.LiteralString => @divCeil(operand.len + 1, @sizeOf(Word)),
239 spec.LiteralContextDependentNumber => switch (operand) {
240 .int32, .uint32, .float32 => 1,
241 .int64, .uint64, .float64 => 2,
242 },
243 spec.PairLiteralIntegerIdRef, spec.PairIdRefLiteralInteger, spec.PairIdRefIdRef => 2,
244 else => switch (@typeInfo(Operand)) {
245 .@"enum" => 1,
246 .optional => |info| if (operand) |child| operandSize(info.child, child) else 0,
247 .pointer => |info| blk: {
248 std.debug.assert(info.size == .slice); // Should be no other pointer types in the spec.
249 var total: usize = 0;
250 for (operand) |item| {
251 total += operandSize(info.child, item);
252 }
253 break :blk total;
254 },
255 .@"struct" => |struct_info| {
256 if (struct_info.layout == .@"packed") return 1;
257
258 var total: usize = 0;
259 inline for (struct_info.field_names, struct_info.field_types) |field_name, field_type| {
260 switch (@typeInfo(field_type)) {
261 .optional => |info| if (@field(operand, field_name)) |child| {
262 total += operandsSize(info.child, child);
263 },
264 .bool => {},
265 else => unreachable,
266 }
267 }
268 return total + 1; // Add one for the mask itself.
269 },
270 .@"union" => switch (operand) {
271 inline else => |op, tag| operandsSize(@FieldType(Operand, @tagName(tag)), op) + 1,
272 },
273 else => unreachable,
274 },
275 };
276}