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