From 10da5ee648caf406a7667d396559e0142474161d Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Sun, 11 Jan 2026 04:22:57 +0100 Subject: [PATCH] adding real runtime --- example/main.zig | 9 +- src/Module.zig | 61 +++++---- src/Result.zig | 220 ++++++++++++++++++++---------- src/Runtime.zig | 149 ++++++++++++++++++-- src/WordIterator.zig | 14 ++ src/opcodes.zig | 314 +++++++++++++++++++++++++++++++++---------- 6 files changed, 582 insertions(+), 185 deletions(-) diff --git a/example/main.zig b/example/main.zig index ed6f2d2..a969671 100644 --- a/example/main.zig +++ b/example/main.zig @@ -13,10 +13,13 @@ pub fn main() !void { var module = try spv.Module.init(allocator, @ptrCast(@alignCast(shader_source))); defer module.deinit(allocator); - var rt = spv.Runtime.init(&module); - defer rt.deinit(); + var rt = try spv.Runtime.init(allocator, &module); + defer rt.deinit(allocator); - try rt.callEntryPoint(0); + try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main")); + var output: [4]f32 = undefined; + try rt.readOutput(f32, output[0..output.len], try rt.getResultByName("color")); + std.log.info("Result: Vec4[{d}, {d}, {d}, {d}]", .{ output[0], output[1], output[2], output[3] }); } std.log.info("Successfully executed", .{}); } diff --git a/src/Module.zig b/src/Module.zig index 6267faf..cdb4034 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -4,8 +4,6 @@ const lib = @import("lib.zig"); const spv = @import("spv.zig"); const op = @import("opcodes.zig"); -const pretty = @import("pretty"); - const SpvVoid = spv.SpvVoid; const SpvByte = spv.SpvByte; const SpvWord = spv.SpvWord; @@ -60,7 +58,7 @@ memory_model: spv.SpvMemoryModel, files: std.ArrayList(SpvSource), extensions: std.ArrayList([]const u8), -results: std.ArrayList(Result), +results: []Result, entry_points: std.ArrayList(SpvEntryPoint), capabilities: std.EnumSet(spv.SpvCapability), @@ -74,9 +72,9 @@ geometry_output_count: SpvWord, geometry_input: SpvWord, geometry_output: SpvWord, -input_locations: std.AutoHashMap(SpvWord, *Value), -output_locations: std.AutoHashMap(SpvWord, *Value), -bindings: std.AutoHashMap(SpvBinding, *Value), +input_locations: std.AutoHashMap(SpvWord, []Value), +output_locations: std.AutoHashMap(SpvWord, []Value), +bindings: std.AutoHashMap(SpvBinding, []Value), push_constants: []Value, pub fn init(allocator: std.mem.Allocator, source: []const SpvWord) ModuleError!Self { @@ -84,15 +82,14 @@ pub fn init(allocator: std.mem.Allocator, source: []const SpvWord) ModuleError!S .code = allocator.dupe(SpvWord, source) catch return ModuleError.OutOfMemory, .files = std.ArrayList(SpvSource).empty, .extensions = std.ArrayList([]const u8).empty, - .results = std.ArrayList(Result).empty, .entry_points = std.ArrayList(SpvEntryPoint).empty, .capabilities = std.EnumSet(spv.SpvCapability).initEmpty(), .local_size_x = 1, .local_size_y = 1, .local_size_z = 1, - .input_locations = std.AutoHashMap(SpvWord, *Value).init(allocator), - .output_locations = std.AutoHashMap(SpvWord, *Value).init(allocator), - .bindings = std.AutoHashMap(SpvBinding, *Value).init(allocator), + .input_locations = std.AutoHashMap(SpvWord, []Value).init(allocator), + .output_locations = std.AutoHashMap(SpvWord, []Value).init(allocator), + .bindings = std.AutoHashMap(SpvBinding, []Value).init(allocator), }); errdefer self.deinit(allocator); @@ -115,18 +112,15 @@ pub fn init(allocator: std.mem.Allocator, source: []const SpvWord) ModuleError!S self.generator_version = @intCast(generator & 0x0000FFFF); self.bound = self.it.next() catch return ModuleError.InvalidSpirV; - self.results.resize(allocator, self.bound) catch return ModuleError.OutOfMemory; - for (self.results.items) |*result| { + self.results = allocator.alloc(Result, self.bound) catch return ModuleError.OutOfMemory; + for (self.results) |*result| { result.* = Result.init(); } _ = self.it.skip(); // Skip schema - const prepassOps = std.EnumSet(spv.SpvOp).initMany(&[_]spv.SpvOp{ - spv.SpvOp.Name, - }); - try self.pass(allocator, prepassOps); // Pre-pass - try self.pass(allocator, prepassOps.complement()); // Setup pass + try self.pass(allocator); // Setup pass + try self.populateMaps(); if (std.process.hasEnvVarConstant("SPIRV_INTERPRETER_DEBUG_LOGS")) { var capability_set_names: std.ArrayList([]const u8) = .empty; @@ -166,7 +160,7 @@ pub fn init(allocator: std.mem.Allocator, source: []const SpvWord) ModuleError!S entry_points, }); - pretty.print(allocator, self.results, .{ .tab_size = 4, .max_depth = 0 }) catch return ModuleError.OutOfMemory; + //@import("pretty").print(allocator, self.results, .{ .tab_size = 4, .max_depth = 0 }) catch return ModuleError.OutOfMemory; } return self; @@ -183,19 +177,18 @@ fn checkEndiannessFromSpvMagic(magic: SpvWord) bool { return false; } -fn pass(self: *Self, allocator: std.mem.Allocator, opcodes: std.EnumSet(spv.SpvOp)) ModuleError!void { - var rt = Runtime.init(self); - defer rt.deinit(); +fn pass(self: *Self, allocator: std.mem.Allocator) ModuleError!void { + var rt = Runtime.init(allocator, self) catch return ModuleError.OutOfMemory; + defer rt.deinit(allocator); + while (rt.it.nextOrNull()) |opcode_data| { const word_count = ((opcode_data & (~spv.SpvOpCodeMask)) >> spv.SpvWordCountShift) - 1; const opcode = (opcode_data & spv.SpvOpCodeMask); var it_tmp = rt.it; // Save because operations may iter on this iterator if (std.enums.fromInt(spv.SpvOp, opcode)) |spv_op| { - if (opcodes.contains(spv_op)) { - if (op.SetupDispatcher.get(spv_op)) |pfn| { - pfn(allocator, word_count, &rt) catch return ModuleError.InvalidSpirV; - } + if (op.SetupDispatcher.get(spv_op)) |pfn| { + pfn(allocator, word_count, &rt) catch return ModuleError.InvalidSpirV; } } _ = it_tmp.skipN(word_count); @@ -203,6 +196,20 @@ fn pass(self: *Self, allocator: std.mem.Allocator, opcodes: std.EnumSet(spv.SpvO } } +fn populateMaps(self: *Self) ModuleError!void { + for (self.results, 0..) |result, id| { + if (result.variant == null or std.meta.activeTag(result.variant.?) != .Variable) continue; + const variable = result.variant.?.Variable; + switch (variable.storage_class) { + .Output => for (result.decorations.items) |decoration| switch (decoration.rtype) { + .Location => self.output_locations.put(@intCast(id), variable.values) catch return ModuleError.OutOfMemory, + else => {}, + }, + else => {}, + } + } +} + pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { allocator.free(self.code); self.input_locations.deinit(); @@ -220,8 +227,8 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { } self.extensions.deinit(allocator); - for (self.results.items) |*result| { + for (self.results) |*result| { result.deinit(allocator); } - self.results.deinit(allocator); + allocator.free(self.results); } diff --git a/src/Result.zig b/src/Result.zig index 56db38a..7e38bb4 100644 --- a/src/Result.zig +++ b/src/Result.zig @@ -55,7 +55,7 @@ const Decoration = struct { }; pub const Value = union(Type) { - Void, + Void: struct {}, Bool: bool, Int: extern union { sint8: i8, @@ -77,11 +77,11 @@ pub const Value = union(Type) { Array: struct {}, RuntimeArray: struct {}, Structure: []Value, - Function, + Function: struct {}, Image: struct {}, Sampler: struct {}, SampledImage: struct {}, - Pointer, + Pointer: struct {}, fn initMembers(self: *Value, allocator: std.mem.Allocator, results: []const Self, target: SpvWord) RuntimeError!void { const resolved = results[target].resolveType(results); @@ -91,16 +91,19 @@ pub const Value = union(Type) { .Type => |t| switch (t) { .Bool, .Int, .Float => std.debug.assert(member_count == 1), .Structure => |s| { - _ = s; - //self.Structure = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory; - //for (self.Structure, s.members) |*value, member_id| { - // value.* = switch (results[member_id].variant.?.Type) { // wtf ? - // inline else => |tag| @unionInit(Value, @tagName(tag), undefined), - // }; - //} + self.* = .{ + .Structure = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory, + }; + for (self.Structure, s.members) |*value, member| { + value.* = switch (member) { + inline else => |tag| @unionInit(Value, @tagName(tag), undefined), + }; + } }, .Matrix => |m| { - self.Matrix = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory; + self.* = .{ + .Matrix = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory, + }; for (self.Matrix) |*value| { value.* = switch (m.column_type) { inline else => |tag| @unionInit(Value, @tagName(tag), undefined), @@ -111,7 +114,9 @@ pub const Value = union(Type) { _ = a; }, .Vector => |v| { - self.Vector = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory; + self.* = .{ + .Vector = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory, + }; for (self.Vector) |*value| { value.* = switch (v.components_type) { inline else => |tag| @unionInit(Value, @tagName(tag), undefined), @@ -123,47 +128,32 @@ pub const Value = union(Type) { else => {}, } } -}; -//void spvm_member_allocate_typed_value(spvm_member_t val, spvm_result* results, spvm_word type) -//{ -// spvm_result_t type_info = spvm_state_get_type_info(results, &results[type]); -// assert(type != 0); -// -// if (type_info->value_type == spvm_value_type_void || -// type_info->value_type == spvm_value_type_int || -// type_info->value_type == spvm_value_type_float || -// type_info->value_type == spvm_value_type_bool) { -// assert(type_info->member_count == 1u); -// } else { -// spvm_member_allocate_value(val, type_info->member_count); -// } -// -// val->type = type; -// -// if (type_info->value_type == spvm_value_type_struct) { -// for (spvm_word i = 0; i < val->member_count; i++) { -// spvm_member_allocate_typed_value(&val->members[i], results, type_info->params[i]); -// } -// } -// else if (type_info->value_type == spvm_value_type_matrix) { -// for (spvm_word i = 0; i < val->member_count; i++) -// spvm_member_allocate_typed_value(&val->members[i], results, type_info->pointer); -// } -// else if (type_info->value_type == spvm_value_type_array) { -// if (results[type_info->pointer].member_count > 0) -// for (spvm_word i = 0; i < val->member_count; i++) -// spvm_member_allocate_typed_value(&val->members[i], results, type_info->pointer); -// } else if (type_info->value_type == spvm_value_type_vector) { -// for (spvm_word i = 0; i < val->member_count; ++i) -// val->members[i].type = type_info->pointer; -// } else { -// // having nested images/samplers is not supported -// assert(type_info->value_type != spvm_value_type_sampled_image); -// assert(type_info->value_type != spvm_value_type_image); -// assert(type_info->value_type != spvm_value_type_sampler); -// } -//} + /// Performs a deep copy + fn dupe(self: *const Value, allocator: std.mem.Allocator) RuntimeError!Value { + return switch (self.*) { + .Vector => |v| .{ + .Vector = allocator.dupe(Value, v) catch return RuntimeError.OutOfMemory, + }, + .Matrix => |m| .{ + .Matrix = allocator.dupe(Value, m) catch return RuntimeError.OutOfMemory, + }, + .Structure => |s| .{ + .Structure = allocator.dupe(Value, s) catch return RuntimeError.OutOfMemory, + }, + else => self.*, + }; + } + + fn deinit(self: *Value, allocator: std.mem.Allocator) void { + switch (self.*) { + .Structure => |values| allocator.free(values), + .Matrix => |values| allocator.free(values), + .Vector => |values| allocator.free(values), + else => {}, + } + } +}; const Self = @This(); @@ -187,6 +177,7 @@ variant: ?union(Variant) { bit_length: SpvWord, }, Vector: struct { + components_type_word: SpvWord, components_type: Type, member_count: SpvWord, }, @@ -198,10 +189,12 @@ variant: ?union(Variant) { Array: struct {}, RuntimeArray: struct {}, Structure: struct { - members: []const SpvWord, + members_type_word: []const SpvWord, + members: []Type, member_names: std.ArrayList([]const u8), }, Function: struct { + source_location: usize, return_type: SpvWord, params: []const SpvWord, }, @@ -219,13 +212,19 @@ variant: ?union(Variant) { }, Constant: []Value, Function: struct { + source_location: usize, return_type: SpvWord, function_type: SpvWord, params: []const SpvWord, }, - AccessChain: struct {}, + AccessChain: struct { + target: SpvWord, + values: []Value, + }, FunctionParameter: struct {}, - Label: struct {}, + Label: struct { + source_location: usize, + }, }, pub fn init() Self { @@ -246,6 +245,7 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { .Type => |*t| switch (t.*) { .Function => |data| allocator.free(data.params), .Structure => |*data| { + allocator.free(data.members_type_word); allocator.free(data.members); for (data.member_names.items) |name| { allocator.free(name); @@ -254,13 +254,97 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { }, else => {}, }, - .Constant => |values| allocator.free(values), + .Constant => |values| { + for (values) |*value| value.deinit(allocator); + allocator.free(values); + }, + .Variable => |v| { + for (v.values) |*value| value.deinit(allocator); + allocator.free(v.values); + }, else => {}, } } self.decorations.deinit(allocator); } +/// Performs a deep copy +pub fn dupe(self: *const Self, allocator: std.mem.Allocator) RuntimeError!Self { + return .{ + .name = if (self.name) |name| allocator.dupe(u8, name) catch return RuntimeError.OutOfMemory else null, + .decorations = self.decorations.clone(allocator) catch return RuntimeError.OutOfMemory, + .parent = self.parent, + .variant = blk: { + if (self.variant) |variant| { + switch (variant) { + .String => |s| break :blk .{ + .String = allocator.dupe(u8, s) catch return RuntimeError.OutOfMemory, + }, + .Type => |t| switch (t) { + .Structure => |s| break :blk .{ + .Type = .{ + .Structure = .{ + .members_type_word = allocator.dupe(SpvWord, s.members_type_word) catch return RuntimeError.OutOfMemory, + .members = allocator.dupe(Type, s.members) catch return RuntimeError.OutOfMemory, + .member_names = blk2: { + const member_names = s.member_names.clone(allocator) catch return RuntimeError.OutOfMemory; + for (member_names.items, s.member_names.items) |*new_name, name| { + new_name.* = allocator.dupe(u8, name) catch return RuntimeError.OutOfMemory; + } + break :blk2 member_names; + }, + }, + }, + }, + .Function => |f| break :blk .{ + .Type = .{ + .Function = .{ + .source_location = f.source_location, + .return_type = f.return_type, + .params = allocator.dupe(SpvWord, f.params) catch return RuntimeError.OutOfMemory, + }, + }, + }, + else => {}, + }, + .Variable => |v| break :blk .{ + .Variable = .{ + .storage_class = v.storage_class, + .values = blk2: { + const values = allocator.dupe(Value, v.values) catch return RuntimeError.OutOfMemory; + for (values, v.values) |*new_value, value| { + new_value.* = try value.dupe(allocator); + } + break :blk2 values; + }, + }, + }, + .Constant => |c| break :blk .{ + .Constant = blk2: { + const values = allocator.dupe(Value, c) catch return RuntimeError.OutOfMemory; + for (values, c) |*new_value, value| { + new_value.* = try value.dupe(allocator); + } + break :blk2 values; + }, + }, + .Function => |f| break :blk .{ + .Function = .{ + .source_location = f.source_location, + .return_type = f.return_type, + .function_type = f.function_type, + .params = allocator.dupe(SpvWord, f.params) catch return RuntimeError.OutOfMemory, + }, + }, + else => {}, + } + break :blk variant; + } + break :blk null; + }, + }; +} + pub fn resolveType(self: *const Self, results: []const Self) *const Self { return if (self.variant) |variant| switch (variant) { @@ -292,19 +376,7 @@ pub fn getMemberCounts(self: *const Self) usize { return 0; } -pub fn initConstantValue(self: *Self, allocator: std.mem.Allocator, results: []const Self, target: SpvWord) RuntimeError!void { - const resolved = results[target].resolveType(results); - const member_count = resolved.getMemberCounts(); - - if (member_count == 0) return; - - self.variant = .{ .Constant = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory }; - errdefer switch (self.variant.?) { - .Constant => |c| allocator.free(c), - else => unreachable, - }; - const values = self.variant.?.Constant; - +pub fn initValues(allocator: std.mem.Allocator, values: []Value, results: []const Self, resolved: *const Self) RuntimeError!void { switch (resolved.variant.?) { .Type => |t| switch (t) { .Bool => values[0] = .{ .Bool = undefined }, @@ -322,17 +394,17 @@ pub fn initConstantValue(self: *Self, allocator: std.mem.Allocator, results: []c try value.initMembers(allocator, results, m.column_type_word); } }, - .Array => |a| { + .Array => |a| { // TODO _ = a; }, .Structure => |s| { - for (values, s.members) |*value, member_type| { - try value.initMembers(allocator, results, member_type); + for (values, s.members_type_word) |*value, member_type_word| { + try value.initMembers(allocator, results, member_type_word); } }, - .Image => {}, + .Image => {}, // TODO .Sampler => {}, // No op - .SampledImage => {}, + .SampledImage => {}, // TODO else => return RuntimeError.InvalidSpirV, }, else => return RuntimeError.InvalidSpirV, diff --git a/src/Runtime.zig b/src/Runtime.zig index f7a19f5..1375690 100644 --- a/src/Runtime.zig +++ b/src/Runtime.zig @@ -19,26 +19,157 @@ pub const RuntimeError = error{ OutOfMemory, Unreachable, Killed, + InvalidEntryPoint, + ToDo, +}; + +pub const Function = struct { + source_location: usize, + result: *Result, }; mod: *Module, it: WordIterator, -current_function: ?*Result, +/// Local deep copy of module's results to be able to run multiple runtimes concurrently +results: []Result, -pub fn init(module: *Module) Self { - return std.mem.zeroInit(Self, .{ +current_function: ?*Result, +function_stack: std.ArrayList(Function), + +pub fn init(allocator: std.mem.Allocator, module: *Module) RuntimeError!Self { + return .{ .mod = module, .it = module.it, + .results = blk: { + const results = allocator.dupe(Result, module.results) catch return RuntimeError.OutOfMemory; + for (results, module.results) |*new_result, result| { + new_result.* = result.dupe(allocator) catch return RuntimeError.OutOfMemory; + } + break :blk results; + }, .current_function = null, - }); + .function_stack = .empty, + }; } -pub fn deinit(self: *const Self) void { - _ = self; +pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { + for (self.results) |*result| { + result.deinit(allocator); + } + allocator.free(self.results); + self.function_stack.deinit(allocator); } -pub fn callEntryPoint(self: *Self, entry: SpvWord) RuntimeError!void { - _ = self; - _ = entry; +pub fn getEntryPointByName(self: *const Self, name: []const u8) error{NotFound}!SpvWord { + for (self.mod.entry_points.items, 0..) |entry_point, i| { + if (blk: { + // Not using std.mem.eql as entry point names may have longer size than their content + for (0..@min(name.len, entry_point.name.len)) |j| { + if (name[j] != entry_point.name[j]) break :blk false; + } + break :blk true; + }) return @intCast(i); + } + return error.NotFound; +} + +pub fn getResultByName(self: *const Self, name: []const u8) error{NotFound}!SpvWord { + for (self.results, 0..) |result, i| { + if (result.name) |result_name| { + if (blk: { + // Same as entry points + for (0..@min(name.len, result_name.len)) |j| { + if (name[j] != result_name[j]) break :blk false; + } + break :blk true; + }) return @intCast(i); + } + } + return error.NotFound; +} + +/// Calls an entry point, `entry_point_index` being the index of the entry point ordered by declaration in the bytecode +pub fn callEntryPoint(self: *Self, allocator: std.mem.Allocator, entry_point_index: SpvWord) RuntimeError!void { + self.reset(); + + if (entry_point_index > self.mod.entry_points.items.len) return RuntimeError.InvalidEntryPoint; + + { + const entry_point_desc = &self.mod.entry_points.items[entry_point_index]; + const entry_point_result = &self.mod.results[entry_point_desc.id]; + if (entry_point_result.variant) |variant| { + switch (variant) { + .Function => |f| { + if (!self.it.jumpToSourceLocation(f.source_location)) return RuntimeError.InvalidEntryPoint; + }, + else => return RuntimeError.InvalidEntryPoint, + } + } else { + return RuntimeError.InvalidEntryPoint; + } + } + + while (self.it.nextOrNull()) |opcode_data| { + const word_count = ((opcode_data & (~spv.SpvOpCodeMask)) >> spv.SpvWordCountShift) - 1; + const opcode = (opcode_data & spv.SpvOpCodeMask); + + var it_tmp = self.it; // Save because operations may iter on this iterator + if (std.enums.fromInt(spv.SpvOp, opcode)) |spv_op| { + if (op.RuntimeDispatcher.get(spv_op)) |pfn| { + try pfn(allocator, word_count, self); + } + } + _ = it_tmp.skipN(word_count); + self.it = it_tmp; + } +} + +pub fn readOutput(self: *const Self, comptime T: type, output: []T, result: SpvWord) error{NotFound}!void { + if (self.mod.output_locations.get(result)) |out| { + self.readValue(T, output, &out[0]); + } else { + return error.NotFound; + } +} + +fn reset(self: *Self) void { + self.function_stack.clearRetainingCapacity(); + self.current_function = null; +} + +fn readValue(self: *const Self, comptime T: type, output: []T, value: *const Result.Value) void { + switch (value.*) { + .Bool => |b| { + if (T == bool) { + output[0] = b; + } + }, + .Int => |i| { + switch (T) { + i8 => output[0] = i.sint8, + i16 => output[0] = i.sint16, + i32 => output[0] = i.sint32, + i64 => output[0] = i.sint64, + u8 => output[0] = i.uint8, + u16 => output[0] = i.uint16, + u32 => output[0] = i.uint32, + u64 => output[0] = i.uint64, + inline else => unreachable, + } + }, + .Float => |f| { + switch (T) { + f16 => output[0] = f.float16, + f32 => output[0] = f.float32, + f64 => output[0] = f.float64, + inline else => unreachable, + } + }, + .Vector => |values| for (values, 0..) |v, i| self.readValue(T, output[i..], &v), + .Matrix => |values| for (values, 0..) |v, i| self.readValue(T, output[i..], &v), + .Array => unreachable, // TODO + .Structure => |values| for (values, 0..) |v, i| self.readValue(T, output[i..], &v), + else => unreachable, + } } diff --git a/src/WordIterator.zig b/src/WordIterator.zig index 158083f..43efecc 100644 --- a/src/WordIterator.zig +++ b/src/WordIterator.zig @@ -54,3 +54,17 @@ pub fn skipN(self: *Self, count: usize) bool { self.index += count; return true; } + +pub fn skipToEnd(self: *Self) void { + self.index = self.buffer.len; +} + +pub inline fn emitSourceLocation(self: *const Self) usize { + return self.index; +} + +pub inline fn jumpToSourceLocation(self: *Self, source_location: usize) bool { + if (source_location > self.buffer.len) return false; + self.index = source_location; + return true; +} diff --git a/src/opcodes.zig b/src/opcodes.zig index fc9a746..adbff05 100644 --- a/src/opcodes.zig +++ b/src/opcodes.zig @@ -13,25 +13,22 @@ const SpvByte = spv.SpvByte; const SpvWord = spv.SpvWord; const SpvBool = spv.SpvBool; -// DUMB INDEV OPCODES TODO : -// OpVariable X -// OpAccessChain X -// OpStore X -// OpLoad X -// OpCompositeExtract X -// OpReturn X -// OpFunctionEnd X - pub const OpCodeFunc = *const fn (std.mem.Allocator, SpvWord, *Runtime) RuntimeError!void; pub const SetupDispatcher = block: { @setEvalBranchQuota(65535); break :block std.EnumMap(spv.SpvOp, OpCodeFunc).init(.{ .Capability = opCapability, + .CompositeConstruct = opCompositeConstruct, + .CompositeExtract = opCompositeExtract, .Constant = opConstant, .Decorate = opDecorate, .EntryPoint = opEntryPoint, .ExecutionMode = opExecutionMode, + .Function = opFunction, + .FunctionEnd = opFunctionEnd, + .Label = opLabel, + .Load = opLoadSetup, .MemberDecorate = opDecorateMember, .MemberName = opMemberName, .MemoryModel = opMemoryModel, @@ -48,9 +45,16 @@ pub const SetupDispatcher = block: { .TypeVector = opTypeVector, .TypeVoid = opTypeVoid, .Variable = opVariable, - .Function = opFunction, - .Label = opLabel, - .CompositeConstruct = opCompositeConstruct, + }); +}; + +pub const RuntimeDispatcher = block: { + @setEvalBranchQuota(65535); + break :block std.EnumMap(spv.SpvOp, OpCodeFunc).init(.{ + .AccessChain = opAccessChain, + .Load = opLoad, + .Return = opReturn, + .Store = opStore, }); }; @@ -59,7 +63,7 @@ fn opCapability(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!voi } fn addDecoration(allocator: std.mem.Allocator, rt: *Runtime, target: SpvWord, decoration_type: spv.SpvDecoration, member: ?SpvWord) RuntimeError!void { - var decoration = rt.mod.results.items[target].decorations.addOne(allocator) catch return RuntimeError.OutOfMemory; + var decoration = rt.mod.results[target].decorations.addOne(allocator) catch return RuntimeError.OutOfMemory; decoration.rtype = decoration_type; decoration.index = if (member) |memb| memb else 0; @@ -155,12 +159,13 @@ fn opMemberName(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) const id = try rt.it.next(); const memb = try rt.it.next(); - var result = &rt.mod.results.items[id]; + var result = &rt.mod.results[id]; if (result.variant == null) { result.variant = .{ .Type = .{ .Structure = .{ + .members_type_word = undefined, .members = undefined, .member_names = .empty, }, @@ -190,8 +195,8 @@ fn opMemoryModel(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!vo fn opName(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); - if (id >= rt.mod.results.items.len) return RuntimeError.InvalidSpirV; - var result = &rt.mod.results.items[id]; + if (id >= rt.mod.results.len) return RuntimeError.InvalidSpirV; + var result = &rt.mod.results[id]; result.* = Result.init(); result.name = try readStringN(allocator, &rt.it, word_count - 1); } @@ -202,15 +207,15 @@ fn opSource(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) Run file.lang_version = try rt.it.next(); if (word_count > 2) { const id = try rt.it.next(); - if (id >= rt.mod.results.items.len) return RuntimeError.InvalidSpirV; - if (rt.mod.results.items[id].name) |name| { + if (id >= rt.mod.results.len) return RuntimeError.InvalidSpirV; + if (rt.mod.results[id].name) |name| { file.file_name = name; } } if (word_count > 3) { const id = try rt.it.next(); - if (id >= rt.mod.results.items.len) return RuntimeError.InvalidSpirV; - if (rt.mod.results.items[id].name) |name| { + if (id >= rt.mod.results.len) return RuntimeError.InvalidSpirV; + if (rt.mod.results[id].name) |name| { file.source = name; } } @@ -222,7 +227,7 @@ fn opSourceExtension(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Run fn opTypeVoid(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); - rt.mod.results.items[id].variant = .{ + rt.mod.results[id].variant = .{ .Type = .{ .Void = .{}, }, @@ -231,7 +236,7 @@ fn opTypeVoid(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void fn opTypeBool(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); - rt.mod.results.items[id].variant = .{ + rt.mod.results[id].variant = .{ .Type = .{ .Bool = .{}, }, @@ -240,7 +245,7 @@ fn opTypeBool(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void fn opTypeInt(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); - rt.mod.results.items[id].variant = .{ + rt.mod.results[id].variant = .{ .Type = .{ .Int = .{ .bit_length = try rt.it.next(), @@ -252,7 +257,7 @@ fn opTypeInt(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { fn opTypeFloat(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); - rt.mod.results.items[id].variant = .{ + rt.mod.results[id].variant = .{ .Type = .{ .Float = .{ .bit_length = try rt.it.next(), @@ -263,10 +268,12 @@ fn opTypeFloat(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void fn opTypeVector(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); - rt.mod.results.items[id].variant = .{ + const components_type_word = try rt.it.next(); + rt.mod.results[id].variant = .{ .Type = .{ .Vector = .{ - .components_type = switch (rt.mod.results.items[try rt.it.next()].variant.?) { + .components_type_word = components_type_word, + .components_type = switch (rt.mod.results[components_type_word].variant.?) { .Type => |t| @as(Result.Type, t), else => return RuntimeError.InvalidSpirV, }, @@ -279,11 +286,11 @@ fn opTypeVector(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!voi fn opTypeMatrix(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); const column_type_word = try rt.it.next(); - rt.mod.results.items[id].variant = .{ + rt.mod.results[id].variant = .{ .Type = .{ .Matrix = .{ .column_type_word = column_type_word, - .column_type = switch (rt.mod.results.items[column_type_word].variant.?) { + .column_type = switch (rt.mod.results[column_type_word].variant.?) { .Type => |t| @as(Result.Type, t), else => return RuntimeError.InvalidSpirV, }, @@ -295,7 +302,7 @@ fn opTypeMatrix(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!voi fn opTypePointer(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); - rt.mod.results.items[id].variant = .{ + rt.mod.results[id].variant = .{ .Type = .{ .Pointer = .{ .storage_class = try rt.it.nextAs(spv.SpvStorageClass), @@ -307,27 +314,36 @@ fn opTypePointer(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!vo fn opTypeStruct(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); - const members = blk: { - const members = allocator.alloc(SpvWord, word_count - 1) catch return RuntimeError.OutOfMemory; + const members_type_word, const members = blk: { + const members_type_word = allocator.alloc(SpvWord, word_count - 1) catch return RuntimeError.OutOfMemory; + errdefer allocator.free(members_type_word); + + const members = allocator.alloc(Result.Type, word_count - 1) catch return RuntimeError.OutOfMemory; errdefer allocator.free(members); - for (members) |*member| { - member.* = try rt.it.next(); + + for (members_type_word, members) |*member_type_word, *member| { + member_type_word.* = try rt.it.next(); + member.* = rt.mod.results[member_type_word.*].variant.?.Type; } - break :blk members; + break :blk .{ members_type_word, members }; }; - if (rt.mod.results.items[id].variant) |*variant| { + if (rt.mod.results[id].variant) |*variant| { switch (variant.*) { .Type => |*t| switch (t.*) { - .Structure => |*s| s.members = members, + .Structure => |*s| { + s.members_type_word = members_type_word; + s.members = members; + }, else => unreachable, }, else => unreachable, } } else { - rt.mod.results.items[id].variant = .{ + rt.mod.results[id].variant = .{ .Type = .{ .Structure = .{ + .members_type_word = members_type_word, .members = members, .member_names = .empty, }, @@ -338,9 +354,10 @@ fn opTypeStruct(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) fn opTypeFunction(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); - rt.mod.results.items[id].variant = .{ + rt.mod.results[id].variant = .{ .Type = .{ .Function = .{ + .source_location = 0, .return_type = try rt.it.next(), .params = blk: { const params = allocator.alloc(SpvWord, word_count - 2) catch return RuntimeError.OutOfMemory; @@ -356,12 +373,7 @@ fn opTypeFunction(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtim } fn opConstant(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { - const var_type = try rt.it.next(); - const id = try rt.it.next(); - - const target = &rt.mod.results.items[id]; - try target.initConstantValue(allocator, rt.mod.results.items, var_type); - + const target = try setupConstant(allocator, rt); // No check on null and sizes, absolute trust in this shit switch (target.variant.?.Constant[0]) { .Int => |*i| { @@ -382,26 +394,29 @@ fn opConstant(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) R } } -fn opVariable(_: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { +fn opVariable(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { const var_type = try rt.it.next(); const id = try rt.it.next(); const storage_class = try rt.it.nextAs(spv.SpvStorageClass); const initializer: ?SpvWord = if (word_count >= 4) try rt.it.next() else null; - _ = var_type; - _ = id; - _ = storage_class; - _ = initializer; - //rt.mod.results.items[id].variant = .{ - // .Variable = .{ - // .storage_class = storage_class, - // .value = value: { - // const resolved = rt.mod.results.items[var_type].resolveType(rt.mod.results.items); - // _ = resolved; - // break :value undefined; - // }, - // }, - //}; + const target = &rt.mod.results[id]; + + const resolved = rt.mod.results[var_type].resolveType(rt.mod.results); + const member_count = resolved.getMemberCounts(); + if (member_count == 0) { + return RuntimeError.InvalidSpirV; + } + target.variant = .{ + .Variable = .{ + .storage_class = storage_class, + .values = allocator.alloc(Result.Value, member_count) catch return RuntimeError.OutOfMemory, + }, + }; + errdefer allocator.free(target.variant.?.Variable.values); + try Result.initValues(allocator, target.variant.?.Variable.values, rt.mod.results, resolved); + + _ = initializer; } fn opFunction(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { @@ -410,12 +425,15 @@ fn opFunction(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeErr _ = rt.it.skip(); // Skip function control const function_type_id = try rt.it.next(); - rt.mod.results.items[id].variant = .{ + const source_location = rt.it.emitSourceLocation(); + + rt.mod.results[id].variant = .{ .Function = .{ + .source_location = source_location, .return_type = return_type, .function_type = function_type_id, .params = params: { - if (rt.mod.results.items[function_type_id].variant) |variant| { + if (rt.mod.results[function_type_id].variant) |variant| { const params_count = switch (variant) { .Type => |t| switch (t) { .Function => |f| f.params.len, @@ -430,30 +448,167 @@ fn opFunction(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeErr }, }; - rt.current_function = &rt.mod.results.items[id]; + rt.mod.results[function_type_id].variant.?.Type.Function.source_location = source_location; + + rt.current_function = &rt.mod.results[id]; } fn opLabel(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); - rt.mod.results.items[id].variant = .{ - .Label = .{}, + rt.mod.results[id].variant = .{ + .Label = .{ + .source_location = rt.it.emitSourceLocation() - 2, // Original label location + }, }; } -fn opCompositeConstruct(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { - _ = rt; +fn opCompositeConstruct(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { + _ = try setupConstant(allocator, rt); +} + +fn opCompositeExtract(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { + _ = try setupConstant(allocator, rt); +} + +fn opFunctionEnd(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { + rt.current_function = null; +} + +fn opAccessChain(_: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { + const var_type = try rt.it.next(); + const id = try rt.it.next(); + const base_id = try rt.it.next(); + + const target = &rt.results[id]; + + target.variant = .{ + .AccessChain = .{ + .target = var_type, + .values = undefined, + }, + }; + + const base = &rt.results[base_id]; + const values = blk: { + if (base.variant) |variant| { + switch (variant) { + .Variable => |v| break :blk v.values, + else => {}, + } + } + return RuntimeError.InvalidSpirV; + }; + var value_ptr = &values[0]; + + const index_count = word_count - 4; + for (0..index_count) |_| { + const member = &rt.results[try rt.it.next()]; + const member_value = switch (member.variant orelse return RuntimeError.InvalidSpirV) { + .Constant => |c| &c[0], + else => return RuntimeError.InvalidSpirV, + }; + switch (member_value.*) { + .Int => |i| { + if (i.uint32 > values.len) return RuntimeError.InvalidSpirV; + value_ptr = switch (value_ptr.*) { + .Vector => |v| &v[i.uint32], + .Matrix => |m| &m[i.uint32], + .Array => |_| return RuntimeError.ToDo, + .Structure => |s| &s[i.uint32], + else => return RuntimeError.InvalidSpirV, + }; + }, + else => return RuntimeError.InvalidSpirV, + } + } + target.variant.?.AccessChain.values = switch (value_ptr.*) { + .Vector => |v| v, + .Matrix => |m| m, + .Array => |_| return RuntimeError.ToDo, + .Structure => |s| s, + else => @as([*]Result.Value, @ptrCast(value_ptr))[0..1], + }; +} + +fn opStore(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { + const ptr_id = try rt.it.next(); + const val_id = try rt.it.next(); + copyValues( + switch (rt.results[ptr_id].variant orelse return RuntimeError.InvalidSpirV) { + .Variable => |v| v.values, + .Constant => |c| c, + .AccessChain => |a| a.values, + else => return RuntimeError.InvalidSpirV, + }, + switch (rt.results[val_id].variant orelse return RuntimeError.InvalidSpirV) { + .Variable => |v| v.values, + .Constant => |c| c, + .AccessChain => |a| a.values, + else => return RuntimeError.InvalidSpirV, + }, + ); +} + +fn opLoadSetup(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { + _ = try setupConstant(allocator, rt); +} + +fn opLoad(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { + _ = rt.it.skip(); + const id = try rt.it.next(); + const ptr_id = try rt.it.next(); + copyValues( + switch (rt.results[id].variant orelse return RuntimeError.InvalidSpirV) { + .Variable => |v| v.values, + .Constant => |c| c, + .AccessChain => |a| a.values, + else => return RuntimeError.InvalidSpirV, + }, + switch (rt.results[ptr_id].variant orelse return RuntimeError.InvalidSpirV) { + .Variable => |v| v.values, + .Constant => |c| c, + .AccessChain => |a| a.values, + else => return RuntimeError.InvalidSpirV, + }, + ); +} + +fn opReturn(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { + _ = rt.function_stack.pop(); + if (rt.function_stack.getLastOrNull()) |function| { + _ = rt.it.jumpToSourceLocation(function.source_location); + rt.current_function = function.result; + } else { + rt.current_function = null; + rt.it.skipToEnd(); + } +} + +inline fn setupConstant(allocator: std.mem.Allocator, rt: *Runtime) RuntimeError!*Result { + const res_type = try rt.it.next(); + const id = try rt.it.next(); + const target = &rt.mod.results[id]; + + const resolved = rt.mod.results[res_type].resolveType(rt.mod.results); + const member_count = resolved.getMemberCounts(); + if (member_count == 0) { + return RuntimeError.InvalidSpirV; + } + target.variant = .{ .Constant = allocator.alloc(Result.Value, member_count) catch return RuntimeError.OutOfMemory }; + errdefer allocator.free(target.variant.?.Constant); + try Result.initValues(allocator, target.variant.?.Constant, rt.mod.results, resolved); + return target; } fn readString(allocator: std.mem.Allocator, it: *WordIterator) RuntimeError![]const u8 { var str: std.ArrayList(u8) = .empty; while (it.nextOrNull()) |word| { + if (word == 0) break; (str.addOne(allocator) catch return RuntimeError.OutOfMemory).* = @truncate(word & 0x000000FF); (str.addOne(allocator) catch return RuntimeError.OutOfMemory).* = @truncate((word & 0x0000FF00) >> 8); (str.addOne(allocator) catch return RuntimeError.OutOfMemory).* = @truncate((word & 0x00FF0000) >> 16); (str.addOne(allocator) catch return RuntimeError.OutOfMemory).* = @truncate((word & 0xFF000000) >> 24); - if (str.getLast() == 0) { - break; - } + if (str.getLast() == 0) break; } return str.toOwnedSlice(allocator); } @@ -462,14 +617,29 @@ fn readStringN(allocator: std.mem.Allocator, it: *WordIterator, n: usize) Runtim var str = std.ArrayList(u8).initCapacity(allocator, n * 4) catch return RuntimeError.OutOfMemory; for (0..n) |_| { if (it.nextOrNull()) |word| { + if (word == 0) break; str.addOneAssumeCapacity().* = @truncate(word & 0x000000FF); str.addOneAssumeCapacity().* = @truncate((word & 0x0000FF00) >> 8); str.addOneAssumeCapacity().* = @truncate((word & 0x00FF0000) >> 16); str.addOneAssumeCapacity().* = @truncate((word & 0xFF000000) >> 24); - if (str.getLast() == 0) { - break; - } + if (str.getLast() == 0) break; } } return str.toOwnedSlice(allocator); } + +fn copyValue(dst: *Result.Value, src: *const Result.Value) void { + switch (src.*) { + .Vector => |v| copyValues(dst.Vector, v), + .Matrix => |m| copyValues(dst.Matrix, m), + .Array => |_| unreachable, + .Structure => |s| copyValues(dst.Structure, s), + else => dst.* = src.*, + } +} + +inline fn copyValues(dst: []Result.Value, src: []const Result.Value) void { + for (dst, src) |*d, *s| { + copyValue(d, s); + } +}