const std = @import("std"); 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; const ValueType = enum { Float, SInt, UInt, }; const MathOp = enum { Add, Sub, Mul, Div, Mod, }; const CondOp = enum { Equal, NotEqual, Greater, GreaterEqual, Less, LessEqual, }; 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(.{ .Bitcast = autoSetupConstant, .Capability = opCapability, .CompositeConstruct = autoSetupConstant, .Constant = opConstant, .ConvertFToS = autoSetupConstant, .ConvertFToU = autoSetupConstant, .ConvertPtrToU = autoSetupConstant, .ConvertSToF = autoSetupConstant, .ConvertUToF = autoSetupConstant, .ConvertUToPtr = autoSetupConstant, .Decorate = opDecorate, .EntryPoint = opEntryPoint, .ExecutionMode = opExecutionMode, .FAdd = autoSetupConstant, .FConvert = autoSetupConstant, .FDiv = autoSetupConstant, .FMod = autoSetupConstant, .FMul = autoSetupConstant, .FOrdEqual = autoSetupConstant, .FOrdGreaterThan = autoSetupConstant, .FOrdGreaterThanEqual = autoSetupConstant, .FOrdLessThan = autoSetupConstant, .FOrdLessThanEqual = autoSetupConstant, .FOrdNotEqual = autoSetupConstant, .FSub = autoSetupConstant, .FUnordEqual = autoSetupConstant, .FUnordGreaterThan = autoSetupConstant, .FUnordGreaterThanEqual = autoSetupConstant, .FUnordLessThan = autoSetupConstant, .FUnordLessThanEqual = autoSetupConstant, .FUnordNotEqual = autoSetupConstant, .Function = opFunction, .FunctionCall = autoSetupConstant, .FunctionEnd = opFunctionEnd, .FunctionParameter = opFunctionParameter, .IAdd = autoSetupConstant, .IEqual = autoSetupConstant, .IMul = autoSetupConstant, .INotEqual = autoSetupConstant, .ISub = autoSetupConstant, .Label = opLabel, .Load = autoSetupConstant, .MemberDecorate = opDecorateMember, .MemberName = opMemberName, .MemoryModel = opMemoryModel, .Name = opName, .QuantizeToF16 = autoSetupConstant, .SConvert = autoSetupConstant, .SDiv = autoSetupConstant, .SGreaterThan = autoSetupConstant, .SGreaterThanEqual = autoSetupConstant, .SLessThan = autoSetupConstant, .SLessThanEqual = autoSetupConstant, .SMod = autoSetupConstant, .SatConvertSToU = autoSetupConstant, .SatConvertUToS = autoSetupConstant, .Source = opSource, .SourceExtension = opSourceExtension, .TypeBool = opTypeBool, .TypeFloat = opTypeFloat, .TypeFunction = opTypeFunction, .TypeInt = opTypeInt, .TypeMatrix = opTypeMatrix, .TypePointer = opTypePointer, .TypeStruct = opTypeStruct, .TypeVector = opTypeVector, .TypeVoid = opTypeVoid, .UConvert = autoSetupConstant, .UDiv = autoSetupConstant, .UGreaterThan = autoSetupConstant, .UGreaterThanEqual = autoSetupConstant, .ULessThan = autoSetupConstant, .ULessThanEqual = autoSetupConstant, .UMod = autoSetupConstant, .Variable = opVariable, }); }; pub const RuntimeDispatcher = block: { @setEvalBranchQuota(65535); break :block std.EnumMap(spv.SpvOp, OpCodeFunc).init(.{ .AccessChain = opAccessChain, .Bitcast = opBitcast, .Branch = opBranch, .BranchConditional = opBranchConditional, .CompositeConstruct = opCompositeConstruct, .CompositeExtract = opCompositeExtract, .ConvertFToS = ConversionEngine(.Float, .SInt).op, .ConvertFToU = ConversionEngine(.Float, .UInt).op, .ConvertSToF = ConversionEngine(.SInt, .Float).op, .ConvertUToF = ConversionEngine(.UInt, .Float).op, .FAdd = MathEngine(.Float, .Add).op, .FConvert = ConversionEngine(.Float, .Float).op, .FDiv = MathEngine(.Float, .Div).op, .FMod = MathEngine(.Float, .Mod).op, .FMul = MathEngine(.Float, .Mul).op, .FOrdEqual = CondEngine(.Float, .Equal).op, .FOrdGreaterThan = CondEngine(.Float, .Greater).op, .FOrdGreaterThanEqual = CondEngine(.Float, .GreaterEqual).op, .FOrdLessThan = CondEngine(.Float, .Less).op, .FOrdLessThanEqual = CondEngine(.Float, .LessEqual).op, .FOrdNotEqual = CondEngine(.Float, .NotEqual).op, .FSub = MathEngine(.Float, .Sub).op, .FUnordEqual = CondEngine(.Float, .Equal).op, .FUnordGreaterThan = CondEngine(.Float, .Greater).op, .FUnordGreaterThanEqual = CondEngine(.Float, .GreaterEqual).op, .FUnordLessThan = CondEngine(.Float, .Less).op, .FUnordLessThanEqual = CondEngine(.Float, .LessEqual).op, .FUnordNotEqual = CondEngine(.Float, .NotEqual).op, .FunctionCall = opFunctionCall, .IAdd = MathEngine(.SInt, .Add).op, .IEqual = CondEngine(.SInt, .Equal).op, .IMul = MathEngine(.SInt, .Mul).op, .INotEqual = CondEngine(.SInt, .NotEqual).op, .ISub = MathEngine(.SInt, .Sub).op, .Load = opLoad, .Return = opReturn, .ReturnValue = opReturnValue, .SConvert = ConversionEngine(.SInt, .SInt).op, .SDiv = MathEngine(.SInt, .Div).op, .SGreaterThan = CondEngine(.SInt, .Greater).op, .SGreaterThanEqual = CondEngine(.SInt, .GreaterEqual).op, .SLessThan = CondEngine(.SInt, .Less).op, .SLessThanEqual = CondEngine(.SInt, .LessEqual).op, .SMod = MathEngine(.SInt, .Mod).op, .Store = opStore, .UConvert = ConversionEngine(.UInt, .UInt).op, .UDiv = MathEngine(.UInt, .Div).op, .UGreaterThan = CondEngine(.UInt, .Greater).op, .UGreaterThanEqual = CondEngine(.UInt, .GreaterEqual).op, .ULessThan = CondEngine(.UInt, .Less).op, .ULessThanEqual = CondEngine(.UInt, .LessEqual).op, .UMod = MathEngine(.UInt, .Mod).op, //.QuantizeToF16 = , //.ConvertPtrToU = , //.SatConvertSToU = , //.SatConvertUToS = , //.ConvertUToPtr = , }); }; fn CondEngine(comptime T: ValueType, comptime Op: CondOp) type { return struct { fn op(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { sw: switch ((rt.results[try rt.it.next()].variant orelse return RuntimeError.InvalidSpirV).Type) { .Vector => |v| continue :sw (rt.results[v.components_type_word].variant orelse return RuntimeError.InvalidSpirV).Type, .Bool => {}, else => return RuntimeError.InvalidSpirV, } const value = try rt.results[try rt.it.next()].getValue(); const op1_result = &rt.results[try rt.it.next()]; const op1_type = try op1_result.getValueTypeWord(); const op1_value = try op1_result.getValue(); const op2_value = try rt.results[try rt.it.next()].getValue(); const size = sw: switch ((rt.results[op1_type].variant orelse return RuntimeError.InvalidSpirV).Type) { .Vector => |v| continue :sw (rt.results[v.components_type_word].variant orelse return RuntimeError.InvalidSpirV).Type, .Float => |f| if (T == .Float) f.bit_length else return RuntimeError.InvalidSpirV, .Int => |i| if (T == .SInt or T == .UInt) i.bit_length else return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV, }; const operator = struct { fn operation(comptime TT: type, op1: TT, op2: TT) RuntimeError!bool { return switch (Op) { .Equal => op1 == op2, .NotEqual => op1 != op2, .Greater => op1 > op2, .GreaterEqual => op1 >= op2, .Less => op1 < op2, .LessEqual => op1 <= op2, }; } fn process(bit_count: SpvWord, v: *Result.Value, op1_v: *const Result.Value, op2_v: *const Result.Value) RuntimeError!void { switch (bit_count) { inline 8, 16, 32, 64 => |i| { if (i == 8 and T == .Float) { // No f8 return RuntimeError.InvalidSpirV; } v.Bool = try operation( getValuePrimitiveFieldType(T, i), (try getValuePrimitiveField(T, i, @constCast(op1_v))).*, (try getValuePrimitiveField(T, i, @constCast(op2_v))).*, ); }, else => return RuntimeError.InvalidSpirV, } } }; switch (value.*) { .Bool => try operator.process(size, value, op1_value, op2_value), .Vector => |vec| for (vec, op1_value.Vector, op2_value.Vector) |*val, op1_v, op2_v| try operator.process(size, val, &op1_v, &op2_v), else => return RuntimeError.InvalidSpirV, } } }; } fn ConversionEngine(comptime From: ValueType, comptime To: ValueType) type { return struct { fn op(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const target_type = (rt.results[try rt.it.next()].variant orelse return RuntimeError.InvalidSpirV).Type; const value = try rt.results[try rt.it.next()].getValue(); const op_result = &rt.results[try rt.it.next()]; const op_type = try op_result.getValueTypeWord(); const op_value = try op_result.getValue(); const from_size = sw: switch ((rt.results[op_type].variant orelse return RuntimeError.InvalidSpirV).Type) { .Vector => |v| continue :sw (rt.results[v.components_type_word].variant orelse return RuntimeError.InvalidSpirV).Type, .Float => |f| if (From == .Float) f.bit_length else return RuntimeError.InvalidSpirV, .Int => |i| if (From == .SInt or From == .UInt) i.bit_length else return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV, }; const to_size = sw: switch (target_type) { .Vector => |v| continue :sw (rt.results[v.components_type_word].variant orelse return RuntimeError.InvalidSpirV).Type, .Float => |f| if (To == .Float) f.bit_length else return RuntimeError.InvalidSpirV, .Int => |i| if (To == .SInt or To == .UInt) i.bit_length else return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV, }; const operator = struct { fn process(from_bit_count: SpvWord, to_bit_count: SpvWord, to: *Result.Value, from: *Result.Value) RuntimeError!void { switch (to_bit_count) { inline 8, 16, 32, 64 => |i| { if (i == 8 and To == .Float) { return RuntimeError.InvalidSpirV; // No f8 } const ToType = getValuePrimitiveFieldType(To, i); (try getValuePrimitiveField(To, i, to)).* = std.math.lossyCast( ToType, switch (from_bit_count) { inline 8, 16, 32, 64 => |j| blk: { if (j == 8 and From == .Float) { return RuntimeError.InvalidSpirV; // Same } break :blk (try getValuePrimitiveField(From, j, from)).*; }, else => return RuntimeError.InvalidSpirV, }, ); }, else => return RuntimeError.InvalidSpirV, } } }; switch (value.*) { .Float => if (To == .Float) try operator.process(from_size, to_size, value, op_value) else return RuntimeError.InvalidSpirV, .Int => if (To == .SInt or To == .UInt) try operator.process(from_size, to_size, value, op_value) else return RuntimeError.InvalidSpirV, .Vector => |vec| for (vec, op_value.Vector) |*val, *op_v| try operator.process(from_size, to_size, val, op_v), else => return RuntimeError.InvalidSpirV, } } }; } fn MathEngine(comptime T: ValueType, comptime Op: MathOp) type { return struct { fn op(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const target_type = (rt.results[try rt.it.next()].variant orelse return RuntimeError.InvalidSpirV).Type; const value = try rt.results[try rt.it.next()].getValue(); const op1_value = try rt.results[try rt.it.next()].getValue(); const op2_value = try rt.results[try rt.it.next()].getValue(); const size = sw: switch (target_type) { .Vector => |v| continue :sw (rt.results[v.components_type_word].variant orelse return RuntimeError.InvalidSpirV).Type, .Float => |f| if (T == .Float) f.bit_length else return RuntimeError.InvalidSpirV, .Int => |i| if (T == .SInt or T == .UInt) i.bit_length else return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV, }; const operator = struct { fn operation(comptime TT: type, op1: TT, op2: TT) RuntimeError!TT { return switch (Op) { .Add => if (@typeInfo(TT) == .int) @addWithOverflow(op1, op2)[0] else op1 + op2, .Sub => if (@typeInfo(TT) == .int) @subWithOverflow(op1, op2)[0] else op1 - op2, .Mul => if (@typeInfo(TT) == .int) @mulWithOverflow(op1, op2)[0] else op1 * op2, .Div => blk: { if (op2 == 0) return RuntimeError.DivisionByZero; break :blk if (@typeInfo(TT) == .int) @divTrunc(op1, op2) else op1 / op2; }, .Mod => blk: { if (op2 == 0) return RuntimeError.DivisionByZero; break :blk @mod(op1, op2); }, }; } fn process(bit_count: SpvWord, v: *Result.Value, op1_v: *const Result.Value, op2_v: *const Result.Value) RuntimeError!void { switch (bit_count) { inline 8, 16, 32, 64 => |i| { if (i == 8 and T == .Float) { // No f8 return RuntimeError.InvalidSpirV; } (try getValuePrimitiveField(T, i, v)).* = try operation( getValuePrimitiveFieldType(T, i), (try getValuePrimitiveField(T, i, @constCast(op1_v))).*, (try getValuePrimitiveField(T, i, @constCast(op2_v))).*, ); }, else => return RuntimeError.InvalidSpirV, } } }; switch (value.*) { .Float => if (T == .Float) try operator.process(size, value, op1_value, op2_value) else return RuntimeError.InvalidSpirV, .Int => if (T == .SInt or T == .UInt) try operator.process(size, value, op1_value, op2_value) else return RuntimeError.InvalidSpirV, .Vector => |vec| for (vec, op1_value.Vector, op2_value.Vector) |*val, op1_v, op2_v| try operator.process(size, val, &op1_v, &op2_v), else => return RuntimeError.InvalidSpirV, } } }; } fn addDecoration(allocator: std.mem.Allocator, rt: *Runtime, target: SpvWord, decoration_type: spv.SpvDecoration, member: ?SpvWord) RuntimeError!void { 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; switch (decoration_type) { .SpecId, .ArrayStride, .MatrixStride, .BuiltIn, .UniformId, .Stream, .Location, .Component, .Index, .Binding, .DescriptorSet, .Offset, .XfbBuffer, .XfbStride, .FuncParamAttr, .FPRoundingMode, .FPFastMathMode, .InputAttachmentIndex, .Alignment, .MaxByteOffset, .AlignmentId, .MaxByteOffsetId, .SecondaryViewportRelativeNV, .CounterBuffer, .UserSemantic, .UserTypeGOOGLE, => { decoration.literal_1 = try rt.it.next(); decoration.literal_2 = null; }, .LinkageAttributes => { decoration.literal_1 = try rt.it.next(); decoration.literal_2 = try rt.it.next(); }, else => {}, } } fn autoSetupConstant(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { _ = try setupConstant(allocator, rt); } fn opBitcast(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { _ = rt.it.skip(); const to_value = try rt.results[try rt.it.next()].getValue(); const from_value = try rt.results[try rt.it.next()].getValue(); const caster = struct { /// Asumes that values passed are primitives ints or floats fn cast(to: *Result.Value, from: *const Result.Value) RuntimeError!void { const from_bytes: u64 = switch (from.*) { .Float => |f| @bitCast(f.float64), .Int => |i| i.uint64, else => return RuntimeError.InvalidSpirV, }; switch (to.*) { .Float => |*f| f.float64 = @bitCast(from_bytes), .Int => |*i| i.uint64 = from_bytes, else => return RuntimeError.InvalidSpirV, } } }; switch (to_value.*) { .Int, .Float => try caster.cast(to_value, from_value), .Vector => |vec| for (vec, from_value.Vector) |*t, *f| try caster.cast(t, f), else => return RuntimeError.InvalidSpirV, } } fn copyValue(dst: *Result.Value, src: *const Result.Value) void { if (src.getCompositeDataOrNull()) |src_slice| { if (dst.getCompositeDataOrNull()) |dst_slice| { for (0..@min(dst_slice.len, src_slice.len)) |i| { copyValue(&dst_slice[i], &src_slice[i]); } } else { unreachable; } } else { dst.* = src.*; } } fn getValuePrimitiveField(comptime T: ValueType, comptime BitCount: SpvWord, v: *Result.Value) RuntimeError!*getValuePrimitiveFieldType(T, BitCount) { return switch (T) { .Float => switch (BitCount) { inline 16, 32, 64 => |i| &@field(v.Float, std.fmt.comptimePrint("float{}", .{i})), else => return RuntimeError.InvalidSpirV, }, .SInt => switch (BitCount) { inline 8, 16, 32, 64 => |i| &@field(v.Int, std.fmt.comptimePrint("sint{}", .{i})), else => return RuntimeError.InvalidSpirV, }, .UInt => switch (BitCount) { inline 8, 16, 32, 64 => |i| &@field(v.Int, std.fmt.comptimePrint("uint{}", .{i})), else => return RuntimeError.InvalidSpirV, }, }; } fn getValuePrimitiveFieldType(comptime T: ValueType, comptime BitCount: SpvWord) type { return switch (T) { .Float => std.meta.Float(BitCount), .SInt => std.meta.Int(.signed, BitCount), .UInt => std.meta.Int(.unsigned, BitCount), }; } 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 base = &rt.results[base_id]; var value_ptr = try base.getValue(); const index_count = word_count - 3; 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.value, .Variable => |v| &v.value, else => return RuntimeError.InvalidSpirV, }; switch (member_value.*) { .Int => |i| { switch (value_ptr.*) { .Vector => |v| { if (i.uint32 > v.len) return RuntimeError.InvalidSpirV; value_ptr = &v[i.uint32]; }, .Matrix => |m| { if (i.uint32 > m.len) return RuntimeError.InvalidSpirV; value_ptr = &m[i.uint32]; }, .Array => |_| return RuntimeError.ToDo, .Structure => |s| { if (i.uint32 > s.len) return RuntimeError.InvalidSpirV; value_ptr = &s[i.uint32]; }, else => return RuntimeError.InvalidSpirV, } }, else => return RuntimeError.InvalidSpirV, } } rt.results[id].variant = .{ .AccessChain = .{ .target = var_type, .value = value_ptr.*, }, }; } fn opBranch(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); _ = rt.it.jumpToSourceLocation(switch (rt.results[id].variant orelse return RuntimeError.InvalidSpirV) { .Label => |l| l.source_location, else => return RuntimeError.InvalidSpirV, }); } fn opBranchConditional(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const cond_value = try rt.results[try rt.it.next()].getValue(); const true_branch = switch (rt.results[try rt.it.next()].variant orelse return RuntimeError.InvalidSpirV) { .Label => |l| l.source_location, else => return RuntimeError.InvalidSpirV, }; const false_branch = switch (rt.results[try rt.it.next()].variant orelse return RuntimeError.InvalidSpirV) { .Label => |l| l.source_location, else => return RuntimeError.InvalidSpirV, }; if (cond_value.Bool) { _ = rt.it.jumpToSourceLocation(true_branch); } else { _ = rt.it.jumpToSourceLocation(false_branch); } } fn opCapability(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { rt.mod.capabilities.insert(try rt.it.nextAs(spv.SpvCapability)); } fn opCompositeConstruct(_: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { _ = rt.it.skip(); const id = try rt.it.next(); const index_count = word_count - 2; const target = (rt.results[id].variant orelse return RuntimeError.InvalidSpirV).Constant.value.getCompositeDataOrNull() orelse return RuntimeError.InvalidSpirV; for (target[0..index_count]) |*elem| { const value = (rt.results[try rt.it.next()].variant orelse return RuntimeError.InvalidSpirV).Constant.value; elem.* = value; } } fn opCompositeExtract(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { const res_type = try rt.it.next(); const id = try rt.it.next(); const composite_id = try rt.it.next(); const index_count = word_count - 3; var composite = (rt.results[composite_id].variant orelse return RuntimeError.InvalidSpirV).Constant.value; for (0..index_count) |_| { const member_id = try rt.it.next(); composite = (composite.getCompositeDataOrNull() orelse return RuntimeError.InvalidSpirV)[member_id]; } rt.results[id].variant = .{ .Constant = .{ .type_word = res_type, .type = switch (rt.results[res_type].variant orelse return RuntimeError.InvalidSpirV) { .Type => |t| @as(Result.Type, t), else => return RuntimeError.InvalidSpirV, }, .value = try composite.dupe(allocator), }, }; } fn opConstant(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { const target = try setupConstant(allocator, rt); // No check on null and sizes, absolute trust in this shit switch (target.variant.?.Constant.value) { .Int => |*i| { if (word_count - 2 != 1) { const low = @as(u64, try rt.it.next()); const high = @as(u64, try rt.it.next()); i.uint64 = (high << 32) | low; } else { i.uint32 = try rt.it.next(); } }, .Float => |*f| { if (word_count - 2 != 1) { const low = @as(u64, try rt.it.next()); const high = @as(u64, try rt.it.next()); f.float64 = @bitCast((high << 32) | low); } else { f.float32 = @bitCast(try rt.it.next()); } }, else => return RuntimeError.InvalidSpirV, } } fn opDecorate(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const target = try rt.it.next(); const decoration_type = try rt.it.nextAs(spv.SpvDecoration); try addDecoration(allocator, rt, target, decoration_type, null); } fn opDecorateMember(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const target = try rt.it.next(); const member = try rt.it.next(); const decoration_type = try rt.it.nextAs(spv.SpvDecoration); try addDecoration(allocator, rt, target, decoration_type, member); } 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 = try rt.it.nextAs(spv.SpvExecutionModel); entry.id = try rt.it.next(); 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) { var interface_index: u32 = 0; while (interface_count != 0) { entry.globals[interface_index] = try rt.it.next(); interface_index += 1; interface_count -= 1; } } } fn opExecutionMode(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { _ = rt.it.skip(); const mode = try rt.it.nextAs(spv.SpvExecutionMode); switch (mode) { .LocalSize => { rt.mod.local_size_x = try rt.it.next(); rt.mod.local_size_y = try rt.it.next(); rt.mod.local_size_z = try rt.it.next(); }, .Invocations => rt.mod.geometry_invocations = try rt.it.next(), .OutputVertices => rt.mod.geometry_output_count = try rt.it.next(), .InputPoints, .InputLines, .Triangles, .InputLinesAdjacency, .InputTrianglesAdjacency => rt.mod.geometry_input = @intFromEnum(mode), .OutputPoints, .OutputLineStrip, .OutputTriangleStrip => rt.mod.geometry_output = @intFromEnum(mode), else => {}, } } fn opFunction(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const return_type = try rt.it.next(); const id = try rt.it.next(); _ = rt.it.skip(); // Skip function control const function_type_id = try rt.it.next(); 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[function_type_id].variant) |variant| { const params_count = switch (variant) { .Type => |t| switch (t) { .Function => |f| f.params.len, else => return RuntimeError.InvalidSpirV, }, else => return RuntimeError.InvalidSpirV, }; break :params allocator.alloc(SpvWord, params_count) catch return RuntimeError.OutOfMemory; } return RuntimeError.InvalidSpirV; }, }, }; rt.mod.results[function_type_id].variant.?.Type.Function.source_location = source_location; rt.current_function = &rt.mod.results[id]; } fn opFunctionCall(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { _ = rt.it.skip(); const ret = &rt.results[try rt.it.next()]; const func = &rt.results[try rt.it.next()]; for ((func.variant orelse return RuntimeError.InvalidSpirV).Function.params) |param| { const arg = &rt.results[try rt.it.next()]; (rt.results[param].variant orelse return RuntimeError.InvalidSpirV).FunctionParameter.value_ptr = try arg.getValue(); } rt.function_stack.items[rt.function_stack.items.len - 1].source_location = rt.it.emitSourceLocation(); const source_location = (func.variant orelse return RuntimeError.InvalidSpirV).Function.source_location; rt.function_stack.append(allocator, .{ .source_location = source_location, .result = func, .ret = ret, }) catch return RuntimeError.OutOfMemory; if (!rt.it.jumpToSourceLocation(source_location)) return RuntimeError.InvalidSpirV; rt.current_parameter_index = 0; } fn opFunctionEnd(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { rt.current_function = null; } fn opFunctionParameter(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const var_type = try rt.it.next(); const id = try rt.it.next(); 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 = .{ .FunctionParameter = .{ .type_word = var_type, .type = switch (resolved.variant orelse return RuntimeError.InvalidSpirV) { .Type => |t| @as(Result.Type, t), else => return RuntimeError.InvalidSpirV, }, .value_ptr = null, }, }; ((rt.current_function orelse return RuntimeError.InvalidSpirV).variant orelse return RuntimeError.InvalidSpirV).Function.params[rt.current_parameter_index] = id; rt.current_parameter_index += 1; } fn opLabel(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); rt.mod.results[id].variant = .{ .Label = .{ .source_location = rt.it.emitSourceLocation() - 2, // Original label location }, }; } 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(); copyValue(try rt.results[id].getValue(), try rt.results[ptr_id].getValue()); } fn opMemberName(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); const memb = try rt.it.next(); var result = &rt.mod.results[id]; if (result.variant == null) { result.variant = .{ .Type = .{ .Structure = .{ .members_type_word = undefined, .members = undefined, .member_names = .empty, }, }, }; } switch (result.variant.?) { .Type => |*t| switch (t.*) { .Structure => |*s| { if (memb + 1 > s.member_names.items.len) { _ = s.member_names.resize(allocator, memb + 1) catch return RuntimeError.OutOfMemory; } const slen = word_count - 2; s.member_names.items[memb] = try readStringN(allocator, &rt.it, slen); }, else => unreachable, }, else => unreachable, } } fn opMemoryModel(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { rt.mod.addressing = try rt.it.nextAs(spv.SpvAddressingModel); rt.mod.memory_model = try rt.it.nextAs(spv.SpvMemoryModel); } fn opName(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); var result = &rt.mod.results[id]; result.name = try readStringN(allocator, &rt.it, word_count - 1); } 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(); } } fn opReturnValue(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { if (rt.function_stack.getLastOrNull()) |function| { var ret_res = rt.results[try rt.it.next()]; copyValue(try function.ret.getValue(), try ret_res.getValue()); } else { return RuntimeError.InvalidSpirV; // No current function ??? } _ = 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(); } } 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 = try rt.it.nextAs(spv.SpvSourceLanguage); file.lang_version = try rt.it.next(); if (word_count > 2) { const id = try rt.it.next(); 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.len) return RuntimeError.InvalidSpirV; if (rt.mod.results[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 opStore(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const ptr_id = try rt.it.next(); const val_id = try rt.it.next(); copyValue(try rt.results[ptr_id].getValue(), try rt.results[val_id].getValue()); } fn opTypeBool(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); rt.mod.results[id].variant = .{ .Type = .{ .Bool = .{}, }, }; } fn opTypeFloat(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); rt.mod.results[id].variant = .{ .Type = .{ .Float = .{ .bit_length = try rt.it.next(), }, }, }; } fn opTypeFunction(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); 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; errdefer allocator.free(params); for (params) |*param| { param.* = try rt.it.next(); } break :blk params; }, }, }, }; } fn opTypeInt(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); rt.mod.results[id].variant = .{ .Type = .{ .Int = .{ .bit_length = try rt.it.next(), .is_signed = if (try rt.it.next() != 0) true else false, }, }, }; } 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[id].variant = .{ .Type = .{ .Matrix = .{ .column_type_word = column_type_word, .column_type = switch (rt.mod.results[column_type_word].variant orelse return RuntimeError.InvalidSpirV) { .Type => |t| @as(Result.Type, t), else => return RuntimeError.InvalidSpirV, }, .member_count = try rt.it.next(), }, }, }; } fn opTypePointer(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); rt.mod.results[id].variant = .{ .Type = .{ .Pointer = .{ .storage_class = try rt.it.nextAs(spv.SpvStorageClass), .target = try rt.it.next(), }, }, }; } fn opTypeStruct(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); 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_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_type_word, members }; }; if (rt.mod.results[id].variant) |*variant| { switch (variant.*) { .Type => |*t| switch (t.*) { .Structure => |*s| { s.members_type_word = members_type_word; s.members = members; }, else => unreachable, }, else => unreachable, } } else { rt.mod.results[id].variant = .{ .Type = .{ .Structure = .{ .members_type_word = members_type_word, .members = members, .member_names = .empty, }, }, }; } } fn opTypeVector(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); const components_type_word = try rt.it.next(); rt.mod.results[id].variant = .{ .Type = .{ .Vector = .{ .components_type_word = components_type_word, .components_type = switch (rt.mod.results[components_type_word].variant orelse return RuntimeError.InvalidSpirV) { .Type => |t| @as(Result.Type, t), else => return RuntimeError.InvalidSpirV, }, .member_count = try rt.it.next(), }, }, }; } fn opTypeVoid(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); rt.mod.results[id].variant = .{ .Type = .{ .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; 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, .type_word = var_type, .type = switch (resolved.variant orelse return RuntimeError.InvalidSpirV) { .Type => |t| @as(Result.Type, t), else => return RuntimeError.InvalidSpirV, }, .value = try Result.initValue(allocator, member_count, rt.mod.results, resolved), }, }; _ = initializer; } 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; } 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.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; } } return str.toOwnedSlice(allocator); } 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 = .{ .value = try Result.initValue(allocator, member_count, rt.mod.results, resolved), .type_word = res_type, .type = switch (resolved.variant orelse return RuntimeError.InvalidSpirV) { .Type => |t| @as(Result.Type, t), else => return RuntimeError.InvalidSpirV, }, }, }; return target; }