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}