aboutsummaryrefslogtreecommitdiff
path: root/src/codegen/spirv/Section.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/codegen/spirv/Section.zig')
-rw-r--r--src/codegen/spirv/Section.zig282
1 files changed, 282 insertions, 0 deletions
diff --git a/src/codegen/spirv/Section.zig b/src/codegen/spirv/Section.zig
new file mode 100644
index 0000000000..b5851c3b7c
--- /dev/null
+++ b/src/codegen/spirv/Section.zig
@@ -0,0 +1,282 @@
+//! Represents a section or subsection of instructions in a SPIR-V binary. Instructions can be append
+//! to separate sections, which can then later be merged into the final binary.
+const Section = @This();
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const testing = std.testing;
+
+const spec = @import("spec.zig");
+const Word = spec.Word;
+const DoubleWord = std.meta.Int(.unsigned, @bitSizeOf(Word) * 2);
+const Log2Word = std.math.Log2Int(Word);
+
+const Opcode = spec.Opcode;
+
+instructions: std.ArrayListUnmanaged(Word) = .empty,
+
+pub fn deinit(section: *Section, allocator: Allocator) void {
+ section.instructions.deinit(allocator);
+ section.* = undefined;
+}
+
+pub fn reset(section: *Section) void {
+ section.instructions.items.len = 0;
+}
+
+pub fn toWords(section: Section) []Word {
+ return section.instructions.items;
+}
+
+/// Append the instructions from another section into this section.
+pub fn append(section: *Section, allocator: Allocator, other_section: Section) !void {
+ try section.instructions.appendSlice(allocator, other_section.instructions.items);
+}
+
+pub fn ensureUnusedCapacity(
+ section: *Section,
+ allocator: Allocator,
+ words: usize,
+) !void {
+ try section.instructions.ensureUnusedCapacity(allocator, words);
+}
+
+/// Write an instruction and size, operands are to be inserted manually.
+pub fn emitRaw(
+ section: *Section,
+ allocator: Allocator,
+ opcode: Opcode,
+ operand_words: usize,
+) !void {
+ const word_count = 1 + operand_words;
+ try section.instructions.ensureUnusedCapacity(allocator, word_count);
+ section.writeWord((@as(Word, @intCast(word_count << 16))) | @intFromEnum(opcode));
+}
+
+/// Write an entire instruction, including all operands
+pub fn emitRawInstruction(
+ section: *Section,
+ allocator: Allocator,
+ opcode: Opcode,
+ operands: []const Word,
+) !void {
+ try section.emitRaw(allocator, opcode, operands.len);
+ section.writeWords(operands);
+}
+
+pub fn emitAssumeCapacity(
+ section: *Section,
+ comptime opcode: spec.Opcode,
+ operands: opcode.Operands(),
+) !void {
+ const word_count = instructionSize(opcode, operands);
+ section.writeWord(@as(Word, @intCast(word_count << 16)) | @intFromEnum(opcode));
+ section.writeOperands(opcode.Operands(), operands);
+}
+
+pub fn emit(
+ section: *Section,
+ allocator: Allocator,
+ comptime opcode: spec.Opcode,
+ operands: opcode.Operands(),
+) !void {
+ const word_count = instructionSize(opcode, operands);
+ try section.instructions.ensureUnusedCapacity(allocator, word_count);
+ section.writeWord(@as(Word, @intCast(word_count << 16)) | @intFromEnum(opcode));
+ section.writeOperands(opcode.Operands(), operands);
+}
+
+pub fn emitBranch(
+ section: *Section,
+ allocator: Allocator,
+ target_label: spec.Id,
+) !void {
+ try section.emit(allocator, .OpBranch, .{
+ .target_label = target_label,
+ });
+}
+
+pub fn writeWord(section: *Section, word: Word) void {
+ section.instructions.appendAssumeCapacity(word);
+}
+
+pub fn writeWords(section: *Section, words: []const Word) void {
+ section.instructions.appendSliceAssumeCapacity(words);
+}
+
+pub fn writeDoubleWord(section: *Section, dword: DoubleWord) void {
+ section.writeWords(&.{
+ @truncate(dword),
+ @truncate(dword >> @bitSizeOf(Word)),
+ });
+}
+
+fn writeOperands(section: *Section, comptime Operands: type, operands: Operands) void {
+ const fields = switch (@typeInfo(Operands)) {
+ .@"struct" => |info| info.fields,
+ .void => return,
+ else => unreachable,
+ };
+ inline for (fields) |field| {
+ section.writeOperand(field.type, @field(operands, field.name));
+ }
+}
+
+pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void {
+ switch (Operand) {
+ spec.LiteralSpecConstantOpInteger => unreachable,
+ spec.Id => section.writeWord(@intFromEnum(operand)),
+ spec.LiteralInteger => section.writeWord(operand),
+ spec.LiteralString => section.writeString(operand),
+ spec.LiteralContextDependentNumber => section.writeContextDependentNumber(operand),
+ spec.LiteralExtInstInteger => section.writeWord(operand.inst),
+ spec.PairLiteralIntegerIdRef => section.writeWords(&.{ operand.value, @enumFromInt(operand.label) }),
+ spec.PairIdRefLiteralInteger => section.writeWords(&.{ @intFromEnum(operand.target), operand.member }),
+ spec.PairIdRefIdRef => section.writeWords(&.{ @intFromEnum(operand[0]), @intFromEnum(operand[1]) }),
+ else => switch (@typeInfo(Operand)) {
+ .@"enum" => section.writeWord(@intFromEnum(operand)),
+ .optional => |info| if (operand) |child| section.writeOperand(info.child, child),
+ .pointer => |info| {
+ std.debug.assert(info.size == .slice); // Should be no other pointer types in the spec.
+ for (operand) |item| {
+ section.writeOperand(info.child, item);
+ }
+ },
+ .@"struct" => |info| {
+ if (info.layout == .@"packed") {
+ section.writeWord(@as(Word, @bitCast(operand)));
+ } else {
+ section.writeExtendedMask(Operand, operand);
+ }
+ },
+ .@"union" => section.writeExtendedUnion(Operand, operand),
+ else => unreachable,
+ },
+ }
+}
+
+fn writeString(section: *Section, str: []const u8) void {
+ const zero_terminated_len = str.len + 1;
+ var i: usize = 0;
+ while (i < zero_terminated_len) : (i += @sizeOf(Word)) {
+ var word: Word = 0;
+ var j: usize = 0;
+ while (j < @sizeOf(Word) and i + j < str.len) : (j += 1) {
+ word |= @as(Word, str[i + j]) << @as(Log2Word, @intCast(j * @bitSizeOf(u8)));
+ }
+ section.instructions.appendAssumeCapacity(word);
+ }
+}
+
+fn writeContextDependentNumber(section: *Section, operand: spec.LiteralContextDependentNumber) void {
+ switch (operand) {
+ .int32 => |int| section.writeWord(@bitCast(int)),
+ .uint32 => |int| section.writeWord(@bitCast(int)),
+ .int64 => |int| section.writeDoubleWord(@bitCast(int)),
+ .uint64 => |int| section.writeDoubleWord(@bitCast(int)),
+ .float32 => |float| section.writeWord(@bitCast(float)),
+ .float64 => |float| section.writeDoubleWord(@bitCast(float)),
+ }
+}
+
+fn writeExtendedMask(section: *Section, comptime Operand: type, operand: Operand) void {
+ var mask: Word = 0;
+ inline for (@typeInfo(Operand).@"struct".fields, 0..) |field, bit| {
+ switch (@typeInfo(field.type)) {
+ .optional => if (@field(operand, field.name) != null) {
+ mask |= 1 << @as(u5, @intCast(bit));
+ },
+ .bool => if (@field(operand, field.name)) {
+ mask |= 1 << @as(u5, @intCast(bit));
+ },
+ else => unreachable,
+ }
+ }
+
+ section.writeWord(mask);
+
+ inline for (@typeInfo(Operand).@"struct".fields) |field| {
+ switch (@typeInfo(field.type)) {
+ .optional => |info| if (@field(operand, field.name)) |child| {
+ section.writeOperands(info.child, child);
+ },
+ .bool => {},
+ else => unreachable,
+ }
+ }
+}
+
+fn writeExtendedUnion(section: *Section, comptime Operand: type, operand: Operand) void {
+ return switch (operand) {
+ inline else => |op, tag| {
+ section.writeWord(@intFromEnum(tag));
+ section.writeOperands(
+ @FieldType(Operand, @tagName(tag)),
+ op,
+ );
+ },
+ };
+}
+
+fn instructionSize(comptime opcode: spec.Opcode, operands: opcode.Operands()) usize {
+ return operandsSize(opcode.Operands(), operands) + 1;
+}
+
+fn operandsSize(comptime Operands: type, operands: Operands) usize {
+ const fields = switch (@typeInfo(Operands)) {
+ .@"struct" => |info| info.fields,
+ .void => return 0,
+ else => unreachable,
+ };
+
+ var total: usize = 0;
+ inline for (fields) |field| {
+ total += operandSize(field.type, @field(operands, field.name));
+ }
+
+ return total;
+}
+
+fn operandSize(comptime Operand: type, operand: Operand) usize {
+ return switch (Operand) {
+ spec.LiteralSpecConstantOpInteger => unreachable,
+ spec.Id, spec.LiteralInteger, spec.LiteralExtInstInteger => 1,
+ spec.LiteralString => std.math.divCeil(usize, operand.len + 1, @sizeOf(Word)) catch unreachable,
+ spec.LiteralContextDependentNumber => switch (operand) {
+ .int32, .uint32, .float32 => 1,
+ .int64, .uint64, .float64 => 2,
+ },
+ spec.PairLiteralIntegerIdRef, spec.PairIdRefLiteralInteger, spec.PairIdRefIdRef => 2,
+ else => switch (@typeInfo(Operand)) {
+ .@"enum" => 1,
+ .optional => |info| if (operand) |child| operandSize(info.child, child) else 0,
+ .pointer => |info| blk: {
+ std.debug.assert(info.size == .slice); // Should be no other pointer types in the spec.
+ var total: usize = 0;
+ for (operand) |item| {
+ total += operandSize(info.child, item);
+ }
+ break :blk total;
+ },
+ .@"struct" => |struct_info| {
+ if (struct_info.layout == .@"packed") return 1;
+
+ var total: usize = 0;
+ inline for (@typeInfo(Operand).@"struct".fields) |field| {
+ switch (@typeInfo(field.type)) {
+ .optional => |info| if (@field(operand, field.name)) |child| {
+ total += operandsSize(info.child, child);
+ },
+ .bool => {},
+ else => unreachable,
+ }
+ }
+ return total + 1; // Add one for the mask itself.
+ },
+ .@"union" => switch (operand) {
+ inline else => |op, tag| operandsSize(@FieldType(Operand, @tagName(tag)), op) + 1,
+ },
+ else => unreachable,
+ },
+ };
+}