diff options
Diffstat (limited to 'src/codegen/spirv/Section.zig')
| -rw-r--r-- | src/codegen/spirv/Section.zig | 282 |
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, + }, + }; +} |
