adding real runtime
All checks were successful
Test / build (push) Successful in 19s
Build / build (push) Successful in 1m30s

This commit is contained in:
2026-01-11 04:22:57 +01:00
parent 5b9f5c93fb
commit 10da5ee648
6 changed files with 582 additions and 185 deletions

View File

@@ -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", .{});
}

View File

@@ -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,26 +177,39 @@ 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;
}
}
}
_ = it_tmp.skipN(word_count);
rt.it = it_tmp;
}
}
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);
}

View File

@@ -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,

View File

@@ -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,
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}