diff --git a/build.zig b/build.zig index 3a865ea..e53b783 100644 --- a/build.zig +++ b/build.zig @@ -14,6 +14,7 @@ pub fn build(b: *std.Build) void { .name = "spirv_interpreter", .root_module = mod, .linkage = .dynamic, + //.use_llvm = true, }); const lib_install = b.addInstallArtifact(lib, .{}); diff --git a/example/main.zig b/example/main.zig index 84430d3..ed6f2d2 100644 --- a/example/main.zig +++ b/example/main.zig @@ -13,7 +13,7 @@ pub fn main() !void { var module = try spv.Module.init(allocator, @ptrCast(@alignCast(shader_source))); defer module.deinit(allocator); - var rt = try spv.Runtime.init(&module); + var rt = spv.Runtime.init(&module); defer rt.deinit(); try rt.callEntryPoint(0); diff --git a/src/Module.zig b/src/Module.zig index 32633a8..ef0d307 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -12,6 +12,8 @@ const SpvBool = spv.SpvBool; const SpvMember = spv.SpvMember; const SpvBinding = spv.SpvBinding; +const Result = @import("Result.zig"); +const Runtime = @import("Runtime.zig"); const WordIterator = @import("WordIterator.zig"); const Self = @This(); @@ -23,6 +25,13 @@ const SpvEntryPoint = struct { globals: []SpvWord, }; +const SpvSource = struct { + file_name: []const u8, + lang: spv.SpvSourceLanguage, + lang_version: SpvWord, + source: []const u8, +}; + const ModuleError = error{ InvalidSpirV, InvalidMagic, @@ -45,6 +54,11 @@ code: []const SpvWord, addressing: spv.SpvAddressingModel, memory_model: spv.SpvMemoryModel, +files: std.ArrayList(SpvSource), +extensions: std.ArrayList([]const u8), + +results: std.ArrayList(Result), + entry_points: std.ArrayList(SpvEntryPoint), capabilities: std.EnumSet(spv.SpvCapability), @@ -52,6 +66,11 @@ local_size_x: SpvWord, local_size_y: SpvWord, local_size_z: SpvWord, +geometry_invocations: SpvWord, +geometry_output_count: SpvWord, +geometry_input: SpvWord, +geometry_output: SpvWord, + input_locations: std.AutoHashMap(SpvWord, *SpvMember), output_locations: std.AutoHashMap(SpvWord, *SpvMember), bindings: std.AutoHashMap(SpvBinding, *SpvMember), @@ -60,6 +79,9 @@ push_constants: []SpvMember, pub fn init(allocator: std.mem.Allocator, source: []const SpvWord) ModuleError!Self { var self: Self = std.mem.zeroInit(Self, .{ .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, @@ -69,7 +91,7 @@ pub fn init(allocator: std.mem.Allocator, source: []const SpvWord) ModuleError!S .output_locations = std.AutoHashMap(SpvWord, *SpvMember).init(allocator), .bindings = std.AutoHashMap(SpvBinding, *SpvMember).init(allocator), }); - errdefer allocator.free(self.code); + errdefer self.deinit(allocator); self.it = WordIterator.init(self.code); @@ -90,40 +112,57 @@ pub fn init(allocator: std.mem.Allocator, source: []const SpvWord) ModuleError!S self.generator_version = @intCast(generator & 0x0000FFFF); self.bound = self.it.next() orelse return ModuleError.InvalidSpirV; + self.results.resize(allocator, self.bound) catch return ModuleError.OutOfMemory; + for (self.results.items) |*result| { + result.* = Result.init(); + } _ = self.it.skip(); // Skip schema - const it_save = self.it; - while (self.it.next()) |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.SetupDispatcher.get(spv_op)) |pfn| { - pfn(allocator, word_count, &self) catch {}; - } - } - _ = it_tmp.skipN(word_count); - self.it = it_tmp; - } - self.it = it_save; - - std.log.scoped(.SPIRV_Interpreter).debug( - \\Loaded shader module with infos: - \\ SPIR-V version: {d}.{d} - \\ Generator: {s} (ID {d}), encoded version 0x{X} - \\ Capabilities count: {d} - \\ Entry points count: {d} - , .{ - self.version_major, - self.version_minor, - spv.vendorName(self.generator_id), - self.generator_id, - self.generator_version, - self.capabilities.count(), - self.entry_points.items.len, + 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 + + if (std.process.hasEnvVarConstant("SPIRV_INTERPRETER_DEBUG_LOGS")) { + var capability_set_names: std.ArrayList([]const u8) = .empty; + defer capability_set_names.deinit(allocator); + + var it = self.capabilities.iterator(); + while (it.next()) |cap| { + capability_set_names.append(allocator, @tagName(cap)) catch return ModuleError.OutOfMemory; + } + + const capabilities = std.mem.join(allocator, ", ", capability_set_names.items) catch return ModuleError.OutOfMemory; + defer allocator.free(capabilities); + + var entry_points_names = std.ArrayList([]const u8).initCapacity(allocator, self.entry_points.items.len) catch return ModuleError.OutOfMemory; + defer entry_points_names.deinit(allocator); + + for (self.entry_points.items) |entry_point| { + entry_points_names.appendAssumeCapacity(entry_point.name); + } + + const entry_points = std.mem.join(allocator, ", ", entry_points_names.items) catch return ModuleError.OutOfMemory; + defer allocator.free(entry_points); + + std.log.scoped(.SPIRV_Interpreter).debug( + \\Loaded shader module with infos: + \\ SPIR-V version: {d}.{d} + \\ Generator: {s} (ID {d}), encoded version 0x{X} + \\ Capabilities: [{s}] + \\ Entry points: [{s}] + , .{ + self.version_major, + self.version_minor, + spv.vendorName(self.generator_id), + self.generator_id, + self.generator_version, + capabilities, + entry_points, + }); + } return self; } @@ -139,6 +178,26 @@ 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(); + while (rt.it.next()) |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; + } + } + } + _ = it_tmp.skipN(word_count); + rt.it = it_tmp; + } +} + pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { allocator.free(self.code); self.input_locations.deinit(); @@ -149,4 +208,15 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { allocator.free(entry.globals); } self.entry_points.deinit(allocator); + self.files.deinit(allocator); + + for (self.extensions.items) |ext| { + allocator.free(ext); + } + self.extensions.deinit(allocator); + + for (self.results.items) |*result| { + result.deinit(allocator); + } + self.results.deinit(allocator); } diff --git a/src/Result.zig b/src/Result.zig new file mode 100644 index 0000000..01808a0 --- /dev/null +++ b/src/Result.zig @@ -0,0 +1,79 @@ +const std = @import("std"); +const spv = @import("spv.zig"); + +const SpvVoid = spv.SpvVoid; +const SpvByte = spv.SpvByte; +const SpvWord = spv.SpvWord; +const SpvBool = spv.SpvBool; + +const RType = enum { + None, + String, + Extension, + Function_type, + Type, + Variable, + Constant, + Function, + Access_chain, + Function_parameter, + Label, +}; + +const ImageInfo = struct { + dim: spv.SpvDim, + depth: SpvByte, + arrayed: SpvByte, + ms: SpvByte, + sampled: SpvByte, + format: spv.SpvImageFormat, + access: spv.SpvAccessQualifier, +}; + +const Decoration = struct { + rtype: spv.SpvDecoration, + literal_1: SpvWord, + literal_2: SpvWord, + index: SpvWord, +}; + +const Self = @This(); + +name: ?[]const u8, +ptr: SpvWord, + +storage_class: spv.SpvStorageClass, +parent: ?*const Self, + +member_names: std.ArrayList([]const u8), +members: std.ArrayList(spv.SpvMember), + +decorations: std.ArrayList(Decoration), + +/// Only for functions +return_type: SpvWord, + +rtype: RType, + +pub fn init() Self { + return std.mem.zeroInit(Self, .{ + .name = null, + .parent = null, + .member_names = std.ArrayList([]const u8).empty, + .members = std.ArrayList(spv.SpvMember).empty, + .decorations = std.ArrayList(Decoration).empty, + .rtype = RType.None, + }); +} + +pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { + if (self.name) |name| { + allocator.free(name); + } + for (self.member_names.items) |name| { + allocator.free(name); + } + self.member_names.deinit(allocator); + self.members.deinit(allocator); + self.decorations.deinit(allocator); +} diff --git a/src/Runtime.zig b/src/Runtime.zig index d910905..89a5914 100644 --- a/src/Runtime.zig +++ b/src/Runtime.zig @@ -1,5 +1,6 @@ const std = @import("std"); const spv = @import("spv.zig"); +const op = @import("opcodes.zig"); const SpvVoid = spv.SpvVoid; const SpvByte = spv.SpvByte; @@ -7,35 +8,36 @@ const SpvWord = spv.SpvWord; const SpvBool = spv.SpvBool; const Module = @import("Module.zig"); +const Result = @import("Result.zig"); const WordIterator = @import("WordIterator.zig"); const Self = @This(); -pub const CallError = error{ +pub const RuntimeError = error{ + InvalidSpirV, + OutOfMemory, Unreachable, Killed, - Error, - InitEnd, - ExecEnd, }; -module: *Module, +mod: *Module, it: WordIterator, -stack_frames: std.SinglyLinkedList, -pub fn init(module: *Module) !Self { - return .{ - .module = module, +current_function: ?*Result, + +pub fn init(module: *Module) Self { + return std.mem.zeroInit(Self, .{ + .mod = module, .it = module.it, - .stack_frames = .{}, - }; + .current_function = null, + }); } pub fn deinit(self: *const Self) void { _ = self; } -pub fn callEntryPoint(self: *Self, entry: SpvWord) CallError!void { +pub fn callEntryPoint(self: *Self, entry: SpvWord) RuntimeError!void { _ = self; _ = entry; } diff --git a/src/WordIterator.zig b/src/WordIterator.zig index c928cd2..8075958 100644 --- a/src/WordIterator.zig +++ b/src/WordIterator.zig @@ -1,3 +1,4 @@ +const std = @import("std"); const spv = @import("spv.zig"); const SpvWord = spv.SpvWord; @@ -20,6 +21,12 @@ pub fn next(self: *Self) ?SpvWord { return word; } +pub fn nextAs(self: *Self, comptime E: type) ?E { + const word = self.peek() orelse return null; + self.index += 1; + return std.enums.fromInt(E, word); +} + pub fn peek(self: *const Self) ?SpvWord { return if (self.index >= self.buffer.len) null else self.buffer[self.index]; } diff --git a/src/opcodes.zig b/src/opcodes.zig index 9715f7c..a2739ce 100644 --- a/src/opcodes.zig +++ b/src/opcodes.zig @@ -3,58 +3,173 @@ const spv = @import("spv.zig"); const Module = @import("Module.zig"); const Runtime = @import("Runtime.zig"); +const Result = @import("Result.zig"); const WordIterator = @import("WordIterator.zig"); +const RuntimeError = Runtime.RuntimeError; + const SpvVoid = spv.SpvVoid; const SpvByte = spv.SpvByte; const SpvWord = spv.SpvWord; const SpvBool = spv.SpvBool; -pub const OpCodeSetupFunc = *const fn (std.mem.Allocator, SpvWord, *Module) anyerror!void; -pub const OpCodeExecFunc = *const fn (std.mem.Allocator, SpvWord, *Runtime) anyerror!void; +// DUMB INDEV OPCODES TODO : +// OpDecorate X +// OpMemberDecorate X +// OpTypeVoid X +// OpTypeFunction X +// OpTypeFloat X +// OpTypeVector X +// OpTypePointer X +// OpTypeStruct X +// OpTypeInt X +// OpConstant X +// OpVariable X +// OpFunction X +// OpLabel X +// OpCompositeConstruct 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, OpCodeSetupFunc).init(.{ + break :block std.EnumMap(spv.SpvOp, OpCodeFunc).init(.{ .Capability = opCapability, .EntryPoint = opEntryPoint, + .ExecutionMode = opExecutionMode, + .MemoryModel = opMemoryModel, + .MemberName = opMemberName, + .Name = opName, + .Source = opSource, + .SourceExtension = opSourceExtension, }); }; -fn opCapability(_: std.mem.Allocator, _: SpvWord, mod: *Module) !void { - if (std.enums.fromInt(spv.SpvCapability, mod.it.next() orelse return)) |capability| { - mod.capabilities.insert(capability); +fn opCapability(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { + if (rt.it.nextAs(spv.SpvCapability)) |capability| { + rt.mod.capabilities.insert(capability); } } -fn opEntryPoint(allocator: std.mem.Allocator, word_count: SpvWord, mod: *Module) !void { - const entry = try mod.entry_points.addOne(allocator); - entry.exec_model = std.enums.fromInt(spv.SpvExecutionModel, mod.it.next() orelse return) orelse return; - entry.id = mod.it.next() orelse return; - entry.name = try readString(allocator, &mod.it); +fn opEntryPoint(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { + const entry = rt.mod.entry_points.addOne(allocator) catch return RuntimeError.OutOfMemory; + entry.exec_model = rt.it.nextAs(spv.SpvExecutionModel) orelse return RuntimeError.InvalidSpirV; + entry.id = rt.it.next() orelse return RuntimeError.InvalidSpirV; + entry.name = try readString(allocator, &rt.it); var interface_count = word_count - @divExact(entry.name.len, 4) - 2; + entry.globals = try allocator.alloc(SpvWord, interface_count); if (interface_count != 0) { - entry.globals = try allocator.alloc(SpvWord, interface_count); var interface_index: u32 = 0; while (interface_count != 0) { - entry.globals[interface_index] = mod.it.next() orelse return; + entry.globals[interface_index] = rt.it.next() orelse return RuntimeError.InvalidSpirV; interface_index += 1; interface_count -= 1; } } } -fn readString(allocator: std.mem.Allocator, it: *WordIterator) ![]const u8 { +fn opExecutionMode(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { + _ = rt.it.skip(); + const mode = rt.it.nextAs(spv.SpvExecutionMode) orelse return RuntimeError.InvalidSpirV; + + switch (mode) { + .LocalSize => { + rt.mod.local_size_x = rt.it.next() orelse return RuntimeError.InvalidSpirV; + rt.mod.local_size_y = rt.it.next() orelse return RuntimeError.InvalidSpirV; + rt.mod.local_size_z = rt.it.next() orelse return RuntimeError.InvalidSpirV; + }, + .Invocations => rt.mod.geometry_invocations = rt.it.next() orelse return RuntimeError.InvalidSpirV, + .OutputVertices => rt.mod.geometry_output_count = rt.it.next() orelse return RuntimeError.InvalidSpirV, + .InputPoints, .InputLines, .Triangles, .InputLinesAdjacency, .InputTrianglesAdjacency => rt.mod.geometry_input = @intFromEnum(mode), + .OutputPoints, .OutputLineStrip, .OutputTriangleStrip => rt.mod.geometry_output = @intFromEnum(mode), + else => {}, + } +} + +fn opMemberName(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { + const id = rt.it.next() orelse return RuntimeError.InvalidSpirV; + const memb = rt.it.next() orelse return RuntimeError.InvalidSpirV; + + var result = &rt.mod.results.items[id]; + + if (memb + 1 > result.member_names.items.len) { + _ = result.member_names.resize(allocator, memb + 1) catch return RuntimeError.OutOfMemory; + } + + const slen = word_count - 2; + result.member_names.items[memb] = try readStringN(allocator, &rt.it, slen); +} + +fn opMemoryModel(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { + rt.mod.addressing = rt.it.nextAs(spv.SpvAddressingModel) orelse return RuntimeError.InvalidSpirV; + rt.mod.memory_model = rt.it.nextAs(spv.SpvMemoryModel) orelse return RuntimeError.InvalidSpirV; +} + +fn opName(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { + const id = rt.it.next() orelse return RuntimeError.InvalidSpirV; + if (id >= rt.mod.results.items.len) return RuntimeError.InvalidSpirV; + var result = &rt.mod.results.items[id]; + result.* = Result.init(); + result.name = try readStringN(allocator, &rt.it, word_count - 1); +} + +fn opSource(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { + var file = rt.mod.files.addOne(allocator) catch return RuntimeError.OutOfMemory; + file.lang = rt.it.nextAs(spv.SpvSourceLanguage) orelse return RuntimeError.InvalidSpirV; + file.lang_version = rt.it.next() orelse return RuntimeError.InvalidSpirV; + if (word_count > 2) { + const id = rt.it.next() orelse return RuntimeError.InvalidSpirV; + if (id >= rt.mod.results.items.len) return RuntimeError.InvalidSpirV; + if (rt.mod.results.items[id].name) |name| { + file.file_name = name; + } + } + if (word_count > 3) { + const id = rt.it.next() orelse return RuntimeError.InvalidSpirV; + if (id >= rt.mod.results.items.len) return RuntimeError.InvalidSpirV; + if (rt.mod.results.items[id].name) |name| { + file.source = name; + } + } +} + +fn opSourceExtension(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { + rt.mod.extensions.append(allocator, try readStringN(allocator, &rt.it, word_count)) catch return RuntimeError.OutOfMemory; +} + +fn readString(allocator: std.mem.Allocator, it: *WordIterator) RuntimeError![]const u8 { var str: std.ArrayList(u8) = .empty; while (it.next()) |word| { - (try str.addOne(allocator)).* = @truncate(word & 0x000000FF); - (try str.addOne(allocator)).* = @truncate((word & 0x0000FF00) >> 8); - (try str.addOne(allocator)).* = @truncate((word & 0x00FF0000) >> 16); - (try str.addOne(allocator)).* = @truncate((word & 0xFF000000) >> 24); + (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; } } return str.toOwnedSlice(allocator); } + +fn readStringN(allocator: std.mem.Allocator, it: *WordIterator, n: usize) RuntimeError![]const u8 { + var str = std.ArrayList(u8).initCapacity(allocator, n * 4) catch return RuntimeError.OutOfMemory; + for (0..n) |_| { + if (it.next()) |word| { + 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; + } + } + } + return str.toOwnedSlice(allocator); +}