Files
SPIRV-Interpreter/src/opcodes.zig
Kbz-8 96ad7f12f9
All checks were successful
Build / build (push) Successful in 1m44s
Test / build (push) Successful in 6m46s
adding GLSL std 450 base
2026-01-24 02:46:02 +01:00

1694 lines
73 KiB
Zig
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const std = @import("std");
const spv = @import("spv.zig");
const GLSL_std_450 = @import("GLSL_std_450/opcodes.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 ValueType = enum {
Bool,
Float,
SInt,
UInt,
};
const MathOp = enum {
Add,
Div,
MatrixTimesMatrix,
MatrixTimesScalar,
MatrixTimesVector,
Mod,
Mul,
Rem,
Sub,
VectorTimesMatrix,
VectorTimesScalar,
};
const CondOp = enum {
Equal,
Greater,
GreaterEqual,
Less,
LessEqual,
NotEqual,
LogicalEqual,
LogicalNotEqual,
LogicalAnd,
LogicalOr,
LogicalNot,
};
const BitOp = enum {
BitCount,
BitFieldInsert,
BitFieldSExtract,
BitFieldUExtract,
BitReverse,
BitwiseAnd,
BitwiseOr,
BitwiseXor,
Not,
ShiftLeft,
ShiftRight,
ShiftRightArithmetic,
};
pub const OpCodeFunc = *const fn (std.mem.Allocator, SpvWord, *Runtime) RuntimeError!void;
pub const OpCodeExtFunc = *const fn (std.mem.Allocator, SpvWord, SpvWord, SpvWord, *Runtime) RuntimeError!void;
pub const SetupDispatcher = block: {
@setEvalBranchQuota(65535);
break :block std.EnumMap(spv.SpvOp, OpCodeFunc).init(.{
.BitCount = autoSetupConstant,
.BitFieldInsert = autoSetupConstant,
.BitFieldSExtract = autoSetupConstant,
.BitFieldUExtract = autoSetupConstant,
.BitReverse = autoSetupConstant,
.Bitcast = autoSetupConstant,
.BitwiseAnd = autoSetupConstant,
.BitwiseOr = autoSetupConstant,
.BitwiseXor = autoSetupConstant,
.Capability = opCapability,
.CompositeConstruct = autoSetupConstant,
.Constant = opConstant,
.ConvertFToS = autoSetupConstant,
.ConvertFToU = autoSetupConstant,
.ConvertPtrToU = autoSetupConstant,
.ConvertSToF = autoSetupConstant,
.ConvertUToF = autoSetupConstant,
.ConvertUToPtr = autoSetupConstant,
.Decorate = opDecorate,
.Dot = autoSetupConstant,
.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,
.LogicalAnd = autoSetupConstant,
.LogicalEqual = autoSetupConstant,
.LogicalNot = autoSetupConstant,
.LogicalNotEqual = autoSetupConstant,
.LogicalOr = autoSetupConstant,
.MatrixTimesMatrix = autoSetupConstant,
.MatrixTimesScalar = autoSetupConstant,
.MatrixTimesVector = autoSetupConstant,
.MemberDecorate = opDecorateMember,
.MemberName = opMemberName,
.MemoryModel = opMemoryModel,
.Name = opName,
.Not = autoSetupConstant,
.QuantizeToF16 = autoSetupConstant,
.SConvert = autoSetupConstant,
.SDiv = autoSetupConstant,
.SGreaterThan = autoSetupConstant,
.SGreaterThanEqual = autoSetupConstant,
.SLessThan = autoSetupConstant,
.SLessThanEqual = autoSetupConstant,
.SMod = autoSetupConstant,
.SatConvertSToU = autoSetupConstant,
.SatConvertUToS = autoSetupConstant,
.ShiftLeftLogical = autoSetupConstant,
.ShiftRightArithmetic = autoSetupConstant,
.ShiftRightLogical = autoSetupConstant,
.Source = opSource,
.SourceExtension = opSourceExtension,
.TypeArray = opTypeArray,
.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,
.VectorTimesMatrix = autoSetupConstant,
.VectorTimesScalar = autoSetupConstant,
.ExtInst = autoSetupConstant,
.ExtInstImport = opExtInstImport,
});
};
/// Not an EnumMap as it is way too slow for this purpose
pub var runtime_dispatcher = [_]?OpCodeFunc{null} ** spv.SpvOpMaxValue;
pub fn initRuntimeDispatcher() void {
// zig fmt: off
runtime_dispatcher[@intFromEnum(spv.SpvOp.AccessChain)] = opAccessChain;
runtime_dispatcher[@intFromEnum(spv.SpvOp.BitCount)] = BitEngine(.UInt, .BitCount).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.BitFieldInsert)] = BitEngine(.UInt, .BitFieldInsert).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.BitFieldSExtract)] = BitEngine(.SInt, .BitFieldSExtract).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.BitFieldUExtract)] = BitEngine(.UInt, .BitFieldUExtract).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.BitReverse)] = BitEngine(.UInt, .BitReverse).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.Bitcast)] = opBitcast;
runtime_dispatcher[@intFromEnum(spv.SpvOp.BitwiseAnd)] = BitEngine(.UInt, .BitwiseAnd).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.BitwiseOr)] = BitEngine(.UInt, .BitwiseOr).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.BitwiseXor)] = BitEngine(.UInt, .BitwiseXor).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.Branch)] = opBranch;
runtime_dispatcher[@intFromEnum(spv.SpvOp.BranchConditional)] = opBranchConditional;
runtime_dispatcher[@intFromEnum(spv.SpvOp.CompositeConstruct)] = opCompositeConstruct;
runtime_dispatcher[@intFromEnum(spv.SpvOp.CompositeExtract)] = opCompositeExtract;
runtime_dispatcher[@intFromEnum(spv.SpvOp.ConvertFToS)] = ConversionEngine(.Float, .SInt).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.ConvertFToU)] = ConversionEngine(.Float, .UInt).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.ConvertSToF)] = ConversionEngine(.SInt, .Float).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.ConvertUToF)] = ConversionEngine(.UInt, .Float).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.CopyMemory)] = opCopyMemory;
runtime_dispatcher[@intFromEnum(spv.SpvOp.Dot)] = opDot;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FAdd)] = MathEngine(.Float, .Add).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FConvert)] = ConversionEngine(.Float, .Float).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FDiv)] = MathEngine(.Float, .Div).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FMod)] = MathEngine(.Float, .Mod).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FMul)] = MathEngine(.Float, .Mul).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FOrdEqual)] = CondEngine(.Float, .Equal).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FOrdGreaterThan)] = CondEngine(.Float, .Greater).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FOrdGreaterThanEqual)] = CondEngine(.Float, .GreaterEqual).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FOrdLessThan)] = CondEngine(.Float, .Less).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FOrdLessThanEqual)] = CondEngine(.Float, .LessEqual).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FOrdNotEqual)] = CondEngine(.Float, .NotEqual).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FSub)] = MathEngine(.Float, .Sub).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FUnordEqual)] = CondEngine(.Float, .Equal).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FUnordGreaterThan)] = CondEngine(.Float, .Greater).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FUnordGreaterThanEqual)] = CondEngine(.Float, .GreaterEqual).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FUnordLessThan)] = CondEngine(.Float, .Less).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FUnordLessThanEqual)] = CondEngine(.Float, .LessEqual).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FUnordNotEqual)] = CondEngine(.Float, .NotEqual).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.FunctionCall)] = opFunctionCall;
runtime_dispatcher[@intFromEnum(spv.SpvOp.IAdd)] = MathEngine(.SInt, .Add).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.IEqual)] = CondEngine(.SInt, .Equal).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.IMul)] = MathEngine(.SInt, .Mul).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.INotEqual)] = CondEngine(.SInt, .NotEqual).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.ISub)] = MathEngine(.SInt, .Sub).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.Kill)] = opKill;
runtime_dispatcher[@intFromEnum(spv.SpvOp.Load)] = opLoad;
runtime_dispatcher[@intFromEnum(spv.SpvOp.LogicalAnd)] = CondEngine(.Bool, .LogicalAnd).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.LogicalEqual)] = CondEngine(.Bool, .LogicalEqual).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.LogicalNot)] = CondEngine(.Bool, .LogicalNot).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.LogicalNotEqual)] = CondEngine(.Bool, .LogicalNotEqual).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.LogicalOr)] = CondEngine(.Bool, .LogicalOr).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.MatrixTimesMatrix)] = MathEngine(.Float, .MatrixTimesMatrix).op; // TODO
runtime_dispatcher[@intFromEnum(spv.SpvOp.MatrixTimesScalar)] = MathEngine(.Float, .MatrixTimesScalar).op; // TODO
runtime_dispatcher[@intFromEnum(spv.SpvOp.MatrixTimesVector)] = MathEngine(.Float, .MatrixTimesVector).op; // TODO
runtime_dispatcher[@intFromEnum(spv.SpvOp.Not)] = BitEngine(.UInt, .Not).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.Return)] = opReturn;
runtime_dispatcher[@intFromEnum(spv.SpvOp.ReturnValue)] = opReturnValue;
runtime_dispatcher[@intFromEnum(spv.SpvOp.SConvert)] = ConversionEngine(.SInt, .SInt).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.SDiv)] = MathEngine(.SInt, .Div).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.SGreaterThan)] = CondEngine(.SInt, .Greater).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.SGreaterThanEqual)] = CondEngine(.SInt, .GreaterEqual).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.SLessThan)] = CondEngine(.SInt, .Less).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.SLessThanEqual)] = CondEngine(.SInt, .LessEqual).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.SMod)] = MathEngine(.SInt, .Mod).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.ShiftLeftLogical)] = BitEngine(.UInt, .ShiftLeft).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.ShiftRightArithmetic)] = BitEngine(.SInt, .ShiftRightArithmetic).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.ShiftRightLogical)] = BitEngine(.UInt, .ShiftRight).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.Store)] = opStore;
runtime_dispatcher[@intFromEnum(spv.SpvOp.UConvert)] = ConversionEngine(.UInt, .UInt).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.UDiv)] = MathEngine(.UInt, .Div).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.UGreaterThan)] = CondEngine(.UInt, .Greater).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.UGreaterThanEqual)] = CondEngine(.UInt, .GreaterEqual).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.ULessThan)] = CondEngine(.UInt, .Less).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.ULessThanEqual)] = CondEngine(.UInt, .LessEqual).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.UMod)] = MathEngine(.UInt, .Mod).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.VectorTimesMatrix)] = MathEngine(.Float, .VectorTimesMatrix).op; // TODO
runtime_dispatcher[@intFromEnum(spv.SpvOp.VectorTimesScalar)] = MathEngine(.Float, .VectorTimesScalar).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.ExtInst)] = opExtInst;
// zig fmt: on
// Extensions init
GLSL_std_450.initRuntimeDispatcher();
}
fn extEqlName(a: []const u8, b: []const u8) bool {
for (0..@min(a.len, b.len)) |i| {
if (a[i] != b[i]) return false;
}
return true;
}
const extensions_map = std.StaticStringMapWithEql([]?OpCodeExtFunc, extEqlName).initComptime(.{
.{ "GLSL.std.450", GLSL_std_450.runtime_dispatcher[0..] },
});
fn BitOperator(comptime T: ValueType, comptime Op: BitOp) type {
return struct {
comptime {
if (T == .Float) @compileError("Invalid value type");
}
inline fn isUnaryOp() bool {
return comptime switch (Op) {
.Not, .BitCount, .BitReverse => true,
else => false,
};
}
inline fn bitMask(bits: u64) u64 {
return if (bits >= 32) ~@as(u64, 0) else (@as(u64, 0x1) << @intCast(bits)) - 1;
}
inline fn bitInsert(comptime TT: type, base: TT, insert: TT, offset: u64, count: u64) TT {
const mask: TT = @intCast(bitMask(count) << @intCast(offset));
return @as(TT, @intCast((base & ~mask) | ((insert << @intCast(offset)) & mask)));
}
inline fn bitExtract(comptime TT: type, v: TT, offset: TT, count: u64) TT {
return (v >> @intCast(offset)) & @as(TT, @intCast(bitMask(count)));
}
fn operationUnary(comptime TT: type, op1: TT) RuntimeError!TT {
return switch (Op) {
.BitCount => @as(TT, @intCast(@bitSizeOf(TT))), // keep return type TT
.BitReverse => @bitReverse(op1),
.Not => ~op1,
else => RuntimeError.InvalidSpirV,
};
}
fn operationBinary(comptime TT: type, rt: *Runtime, op1: TT, op2: TT) RuntimeError!TT {
return switch (Op) {
.BitFieldInsert => blk: {
const offset = try rt.results[try rt.it.next()].getValue();
const count = try rt.results[try rt.it.next()].getValue();
break :blk bitInsert(TT, op1, op2, offset.Int.uint64, count.Int.uint64);
},
.BitFieldSExtract => blk: {
if (T == .UInt) return RuntimeError.InvalidSpirV;
const count = try rt.results[try rt.it.next()].getValue();
break :blk bitExtract(TT, op1, op2, count.Int.uint64);
},
.BitFieldUExtract => blk: {
if (T == .SInt) return RuntimeError.InvalidSpirV;
const count = try rt.results[try rt.it.next()].getValue();
break :blk bitExtract(TT, op1, op2, count.Int.uint64);
},
.BitwiseAnd => op1 & op2,
.BitwiseOr => op1 | op2,
.BitwiseXor => op1 ^ op2,
.ShiftLeft => op1 << @intCast(op2),
.ShiftRight, .ShiftRightArithmetic => op1 >> @intCast(op2),
else => RuntimeError.InvalidSpirV,
};
}
fn applyScalarBits(rt: *Runtime, bit_count: SpvWord, dst: *Result.Value, op1_v: *const Result.Value, op2_v: ?*const Result.Value) RuntimeError!void {
switch (bit_count) {
inline 8, 16, 32, 64 => |bits| {
const TT = getValuePrimitiveFieldType(T, bits);
const a = (try getValuePrimitiveField(T, bits, @constCast(op1_v))).*;
const out = if (comptime isUnaryOp()) blk: {
break :blk try operationUnary(TT, a);
} else blk: {
const b_ptr = op2_v orelse return RuntimeError.InvalidSpirV;
const b = (try getValuePrimitiveField(T, bits, @constCast(b_ptr))).*;
break :blk try operationBinary(TT, rt, a, b);
};
(try getValuePrimitiveField(T, bits, dst)).* = out;
},
else => return RuntimeError.InvalidSpirV,
}
}
fn laneRhsPtr(op2_value: ?*Result.Value, index: usize) ?*const Result.Value {
if (comptime isUnaryOp()) return null;
const v = op2_value orelse return null;
return &v.Vector[index];
}
fn applyFixedVector(comptime ElemT: type, comptime N: usize, dst: *[N]ElemT, op1: *[N]ElemT, op2_value: ?*Result.Value) RuntimeError!void {
if (comptime isUnaryOp()) {
inline for (0..N) |i| dst[i] = try operationUnary(ElemT, op1[i]);
} else {
const op2 = op2_value orelse return RuntimeError.InvalidSpirV;
const b: *const [N]ElemT = switch (N) {
2 => &op2.*.Vector2u32, // will be overridden by call sites per ElemT/tag
3 => &op2.*.Vector3u32,
4 => &op2.*.Vector4u32,
else => unreachable,
};
// NOTE: the above dummy mapping isn’t type-correct for i32; call sites below pass correct rhs pointer.
_ = b;
return RuntimeError.InvalidSpirV;
}
}
fn applyFixedVectorBinary(
comptime ElemT: type,
comptime N: usize,
rt: *Runtime,
dst: *[N]ElemT,
op1: *[N]ElemT,
op2: *[N]ElemT,
) RuntimeError!void {
inline for (0..N) |i| dst[i] = try operationBinary(ElemT, rt, op1[i], op2[i]);
}
fn applyFixedVectorUnary(
comptime ElemT: type,
comptime N: usize,
dst: *[N]ElemT,
op1: *[N]ElemT,
) RuntimeError!void {
inline for (0..N) |i| dst[i] = try operationUnary(ElemT, op1[i]);
}
};
}
fn BitEngine(comptime T: ValueType, comptime Op: BitOp) type {
return struct {
fn op(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
const target_type = (try rt.results[try rt.it.next()].getVariant()).Type;
const dst = try rt.results[try rt.it.next()].getValue();
const op1 = try rt.results[try rt.it.next()].getValue();
const operator = BitOperator(T, Op);
const op2_value: ?*Result.Value = if (comptime operator.isUnaryOp()) null else try rt.results[try rt.it.next()].getValue();
const lane_bits = try Result.resolveLaneBitWidth(target_type, rt);
switch (dst.*) {
.Int => try operator.applyScalarBits(rt, lane_bits, dst, op1, if (comptime operator.isUnaryOp()) null else op2_value),
.Vector => |dst_vec| {
const op1_vec = op1.Vector;
if (dst_vec.len != op1_vec.len) return RuntimeError.InvalidSpirV;
for (dst_vec, op1_vec, 0..) |*d_lane, a_lane, i| {
var tmp_a = a_lane;
const b_ptr = operator.laneRhsPtr(op2_value, i);
try operator.applyScalarBits(rt, lane_bits, d_lane, &tmp_a, b_ptr);
}
},
.Vector4i32 => |*d| {
if (comptime operator.isUnaryOp())
try operator.applyFixedVectorUnary(i32, 4, d, &op1.Vector4i32)
else
try operator.applyFixedVectorBinary(i32, 4, rt, d, &op1.Vector4i32, &op2_value.?.Vector4i32);
},
.Vector3i32 => |*d| {
if (comptime operator.isUnaryOp())
try operator.applyFixedVectorUnary(i32, 3, d, &op1.Vector3i32)
else
try operator.applyFixedVectorBinary(i32, 3, rt, d, &op1.Vector3i32, &op2_value.?.Vector3i32);
},
.Vector2i32 => |*d| {
if (comptime operator.isUnaryOp())
try operator.applyFixedVectorUnary(i32, 2, d, &op1.Vector2i32)
else
try operator.applyFixedVectorBinary(i32, 2, rt, d, &op1.Vector2i32, &op2_value.?.Vector2i32);
},
.Vector4u32 => |*d| {
if (comptime operator.isUnaryOp())
try operator.applyFixedVectorUnary(u32, 4, d, &op1.Vector4u32)
else
try operator.applyFixedVectorBinary(u32, 4, rt, d, &op1.Vector4u32, &op2_value.?.Vector4u32);
},
.Vector3u32 => |*d| {
if (comptime operator.isUnaryOp())
try operator.applyFixedVectorUnary(u32, 3, d, &op1.Vector3u32)
else
try operator.applyFixedVectorBinary(u32, 3, rt, d, &op1.Vector3u32, &op2_value.?.Vector3u32);
},
.Vector2u32 => |*d| {
if (comptime operator.isUnaryOp())
try operator.applyFixedVectorUnary(u32, 2, d, &op1.Vector2u32)
else
try operator.applyFixedVectorBinary(u32, 2, rt, d, &op1.Vector2u32, &op2_value.?.Vector2u32);
},
else => return RuntimeError.InvalidSpirV,
}
}
};
}
fn CondOperator(comptime T: ValueType, comptime Op: CondOp) type {
return struct {
fn operation(comptime TT: type, a: TT, b: TT) RuntimeError!bool {
return switch (Op) {
.Equal, .LogicalEqual => a == b,
.NotEqual, .LogicalNotEqual => a != b,
.Greater => a > b,
.GreaterEqual => a >= b,
.Less => a < b,
.LessEqual => a <= b,
.LogicalAnd => a and b,
.LogicalOr => a or b,
else => RuntimeError.InvalidSpirV,
};
}
fn operationUnary(comptime TT: type, a: TT) RuntimeError!bool {
return switch (Op) {
.LogicalNot => !a,
else => RuntimeError.InvalidSpirV,
};
}
fn applyLane(bit_count: SpvWord, dst_bool: *Result.Value, a_v: *const Result.Value, b_v: ?*const Result.Value) RuntimeError!void {
switch (bit_count) {
inline 8, 16, 32, 64 => |bits| {
if (bits == 8 and T == .Float) return RuntimeError.InvalidSpirV;
const TT = getValuePrimitiveFieldType(T, bits);
const a = (try getValuePrimitiveField(T, bits, @constCast(a_v))).*;
if (comptime Op == .LogicalNot) {
dst_bool.Bool = try operationUnary(TT, a);
} else {
const b_ptr = b_v orelse return RuntimeError.InvalidSpirV;
const b = (try getValuePrimitiveField(T, bits, @constCast(b_ptr))).*;
dst_bool.Bool = try operation(TT, a, b);
}
},
else => return RuntimeError.InvalidSpirV,
}
}
fn laneRhsPtr(op2_value: ?*Result.Value, index: usize) ?*const Result.Value {
if (comptime Op == .LogicalNot) return null;
const v = op2_value orelse return null;
return &v.Vector[index];
}
};
}
fn CondEngine(comptime T: ValueType, comptime Op: CondOp) type {
return struct {
fn op(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
sw: switch ((try rt.results[try rt.it.next()].getVariant()).Type) {
.Vector => |v| continue :sw (try rt.results[v.components_type_word].getVariant()).Type,
.Bool => {},
else => return RuntimeError.InvalidSpirV,
}
const dst = 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: ?*Result.Value = if (comptime Op == .LogicalNot) null else try rt.results[try rt.it.next()].getValue();
const lane_bits = try Result.resolveLaneBitWidth((try rt.results[op1_type].getVariant()).Type, rt);
const operator = CondOperator(T, Op);
switch (dst.*) {
.Bool => try operator.applyLane(lane_bits, dst, op1_value, op2_value),
.Vector => |dst_vec| for (dst_vec, op1_value.Vector, 0..) |*d_lane, a_lane, i| {
const b_ptr = operator.laneRhsPtr(op2_value, i);
try operator.applyLane(lane_bits, d_lane, &a_lane, b_ptr);
},
else => return RuntimeError.InvalidSpirV,
}
}
};
}
fn ConversionEngine(comptime from_kind: ValueType, comptime to_kind: ValueType) type {
return struct {
fn op(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
const target_type = (try rt.results[try rt.it.next()].getVariant()).Type;
const dst_value = try rt.results[try rt.it.next()].getValue();
const src_result = &rt.results[try rt.it.next()];
const src_type_word = try src_result.getValueTypeWord();
const src_value = try src_result.getValue();
const from_bits = try Result.resolveLaneBitWidth((try rt.results[src_type_word].getVariant()).Type, rt);
const to_bits = try Result.resolveLaneBitWidth(target_type, rt);
const caster = struct {
fn castLane(comptime ToT: type, from_bit_count: SpvWord, from: *Result.Value) RuntimeError!ToT {
return switch (from_bit_count) {
inline 8, 16, 32, 64 => |bits| blk: {
if (bits == 8 and from_kind == .Float) return RuntimeError.InvalidSpirV; // No f8
const v = (try getValuePrimitiveField(from_kind, bits, from)).*;
break :blk std.math.lossyCast(ToT, v);
},
else => return RuntimeError.InvalidSpirV,
};
}
fn applyScalar(from_bit_count: SpvWord, to_bit_count: SpvWord, dst: *Result.Value, from: *Result.Value) RuntimeError!void {
switch (to_bit_count) {
inline 8, 16, 32, 64 => |bits| {
if (bits == 8 and to_kind == .Float) return RuntimeError.InvalidSpirV; // No f8
const ToT = getValuePrimitiveFieldType(to_kind, bits);
(try getValuePrimitiveField(to_kind, bits, dst)).* = try castLane(ToT, from_bit_count, from);
},
else => return RuntimeError.InvalidSpirV,
}
}
fn castSIMDVector(comptime ToT: type, comptime N: usize, dst_arr: *[N]ToT, src_arr: *const [N]ToT) void {
inline for (0..N) |i| dst_arr[i] = std.math.lossyCast(ToT, src_arr[i]);
}
fn castSIMDVectorFromOther(comptime ToT: type, comptime FromT: type, comptime N: usize, dst_arr: *[N]ToT, src_arr: *const [N]FromT) void {
inline for (0..N) |i| dst_arr[i] = std.math.lossyCast(ToT, src_arr[i]);
}
};
switch (dst_value.*) {
.Float => {
if (to_kind != .Float) return RuntimeError.InvalidSpirV;
try caster.applyScalar(from_bits, to_bits, dst_value, src_value);
},
.Int => {
if (to_kind != .SInt and to_kind != .UInt) return RuntimeError.InvalidSpirV;
try caster.applyScalar(from_bits, to_bits, dst_value, src_value);
},
.Vector => |dst_vec| {
const src_vec = src_value.Vector;
if (dst_vec.len != src_vec.len) return RuntimeError.InvalidSpirV;
for (dst_vec, src_vec) |*d_lane, *s_lane| {
try caster.applyScalar(from_bits, to_bits, d_lane, s_lane);
}
},
.Vector4f32 => |*dst| switch (src_value.*) {
.Vector4f32 => caster.castSIMDVector(f32, 4, dst, &src_value.Vector4f32),
.Vector4i32 => caster.castSIMDVectorFromOther(f32, i32, 4, dst, &src_value.Vector4i32),
.Vector4u32 => caster.castSIMDVectorFromOther(f32, u32, 4, dst, &src_value.Vector4u32),
else => return RuntimeError.InvalidSpirV,
},
.Vector3f32 => |*dst| switch (src_value.*) {
.Vector3f32 => caster.castSIMDVector(f32, 3, dst, &src_value.Vector3f32),
.Vector3i32 => caster.castSIMDVectorFromOther(f32, i32, 3, dst, &src_value.Vector3i32),
.Vector3u32 => caster.castSIMDVectorFromOther(f32, u32, 3, dst, &src_value.Vector3u32),
else => return RuntimeError.InvalidSpirV,
},
.Vector2f32 => |*dst| switch (src_value.*) {
.Vector2f32 => caster.castSIMDVector(f32, 2, dst, &src_value.Vector2f32),
.Vector2i32 => caster.castSIMDVectorFromOther(f32, i32, 2, dst, &src_value.Vector2i32),
.Vector2u32 => caster.castSIMDVectorFromOther(f32, u32, 2, dst, &src_value.Vector2u32),
else => return RuntimeError.InvalidSpirV,
},
.Vector4i32 => |*dst| switch (src_value.*) {
.Vector4f32 => caster.castSIMDVectorFromOther(i32, f32, 4, dst, &src_value.Vector4f32),
.Vector4i32 => caster.castSIMDVector(i32, 4, dst, &src_value.Vector4i32),
.Vector4u32 => caster.castSIMDVectorFromOther(i32, u32, 4, dst, &src_value.Vector4u32),
else => return RuntimeError.InvalidSpirV,
},
.Vector3i32 => |*dst| switch (src_value.*) {
.Vector3f32 => caster.castSIMDVectorFromOther(i32, f32, 3, dst, &src_value.Vector3f32),
.Vector3i32 => caster.castSIMDVector(i32, 3, dst, &src_value.Vector3i32),
.Vector3u32 => caster.castSIMDVectorFromOther(i32, u32, 3, dst, &src_value.Vector3u32),
else => return RuntimeError.InvalidSpirV,
},
.Vector2i32 => |*dst| switch (src_value.*) {
.Vector2f32 => caster.castSIMDVectorFromOther(i32, f32, 2, dst, &src_value.Vector2f32),
.Vector2i32 => caster.castSIMDVector(i32, 2, dst, &src_value.Vector2i32),
.Vector2u32 => caster.castSIMDVectorFromOther(i32, u32, 2, dst, &src_value.Vector2u32),
else => return RuntimeError.InvalidSpirV,
},
.Vector4u32 => |*dst| switch (src_value.*) {
.Vector4f32 => caster.castSIMDVectorFromOther(u32, f32, 4, dst, &src_value.Vector4f32),
.Vector4i32 => caster.castSIMDVectorFromOther(u32, i32, 4, dst, &src_value.Vector4i32),
.Vector4u32 => caster.castSIMDVector(u32, 4, dst, &src_value.Vector4u32),
else => return RuntimeError.InvalidSpirV,
},
.Vector3u32 => |*dst| switch (src_value.*) {
.Vector3f32 => caster.castSIMDVectorFromOther(u32, f32, 3, dst, &src_value.Vector3f32),
.Vector3i32 => caster.castSIMDVectorFromOther(u32, i32, 3, dst, &src_value.Vector3i32),
.Vector3u32 => caster.castSIMDVector(u32, 3, dst, &src_value.Vector3u32),
else => return RuntimeError.InvalidSpirV,
},
.Vector2u32 => |*dst| switch (src_value.*) {
.Vector2f32 => caster.castSIMDVectorFromOther(u32, f32, 2, dst, &src_value.Vector2f32),
.Vector2i32 => caster.castSIMDVectorFromOther(u32, i32, 2, dst, &src_value.Vector2i32),
.Vector2u32 => caster.castSIMDVector(u32, 2, dst, &src_value.Vector2u32),
else => return RuntimeError.InvalidSpirV,
},
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 = (try rt.results[try rt.it.next()].getVariant()).Type;
const dst = try rt.results[try rt.it.next()].getValue();
const lhs = try rt.results[try rt.it.next()].getValue();
const rhs = try rt.results[try rt.it.next()].getValue();
const lane_bits = try Result.resolveLaneBitWidth(target_type, rt);
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 => if (op2 == 0) return RuntimeError.DivisionByZero else @mod(op1, op2),
.Rem => if (op2 == 0) return RuntimeError.DivisionByZero else @rem(op1, op2),
else => return RuntimeError.InvalidSpirV,
};
}
fn applyScalar(bit_count: SpvWord, d: *Result.Value, l: *Result.Value, r: *Result.Value) RuntimeError!void {
switch (bit_count) {
inline 8, 16, 32, 64 => |bits| {
if (bits == 8 and T == .Float) return RuntimeError.InvalidSpirV;
const ScalarT = getValuePrimitiveFieldType(T, bits);
const d_field = try getValuePrimitiveField(T, bits, d);
const l_field = try getValuePrimitiveField(T, bits, l);
const r_field = try getValuePrimitiveField(T, bits, r);
d_field.* = try operation(ScalarT, l_field.*, r_field.*);
},
else => return RuntimeError.InvalidSpirV,
}
}
inline fn applyVectorTimesScalarF32(d: []Result.Value, l: []const Result.Value, r: f32) void {
for (d, l) |*d_v, l_v| {
d_v.Float.float32 = l_v.Float.float32 * r;
}
}
inline fn applySIMDVector(comptime ElemT: type, comptime N: usize, d: *@Vector(N, ElemT), l: *const @Vector(N, ElemT), r: *const @Vector(N, ElemT)) RuntimeError!void {
inline for (0..N) |i| {
d[i] = try operation(ElemT, l[i], r[i]);
}
}
inline fn applyVectorSIMDTimesScalarF32(comptime N: usize, d: *@Vector(N, f32), l: *const @Vector(N, f32), r: f32) void {
inline for (0..N) |i| {
d[i] = l[i] * r;
}
}
inline fn applySIMDVectorf32(comptime N: usize, d: *@Vector(N, f32), l: *const @Vector(N, f32), r: *const Result.Value) RuntimeError!void {
switch (Op) {
.VectorTimesScalar => applyVectorSIMDTimesScalarF32(N, d, l, r.Float.float32),
else => {
const rh: *const @Vector(N, f32) = switch (N) {
2 => &r.Vector2f32,
3 => &r.Vector3f32,
4 => &r.Vector4f32,
else => unreachable,
};
try applySIMDVector(f32, N, d, l, rh);
},
}
}
};
switch (dst.*) {
.Int, .Float => try operator.applyScalar(lane_bits, dst, lhs, rhs),
.Vector => |dst_vec| switch (Op) {
.VectorTimesScalar => operator.applyVectorTimesScalarF32(dst_vec, lhs.Vector, rhs.Float.float32),
else => for (dst_vec, lhs.Vector, rhs.Vector) |*d_lane, *l_lane, *r_lane| {
try operator.applyScalar(lane_bits, d_lane, l_lane, r_lane);
},
},
.Vector4f32 => |*d| try operator.applySIMDVectorf32(4, d, &lhs.Vector4f32, rhs),
.Vector3f32 => |*d| try operator.applySIMDVectorf32(3, d, &lhs.Vector3f32, rhs),
.Vector2f32 => |*d| try operator.applySIMDVectorf32(2, d, &lhs.Vector2f32, rhs),
.Vector4i32 => |*d| try operator.applySIMDVector(i32, 4, d, &lhs.Vector4i32, &rhs.Vector4i32),
.Vector3i32 => |*d| try operator.applySIMDVector(i32, 3, d, &lhs.Vector3i32, &rhs.Vector3i32),
.Vector2i32 => |*d| try operator.applySIMDVector(i32, 2, d, &lhs.Vector2i32, &rhs.Vector2i32),
.Vector4u32 => |*d| try operator.applySIMDVector(u32, 4, d, &lhs.Vector4u32, &rhs.Vector4u32),
.Vector3u32 => |*d| try operator.applySIMDVector(u32, 3, d, &lhs.Vector3u32, &rhs.Vector3u32),
.Vector2u32 => |*d| try operator.applySIMDVector(u32, 2, d, &lhs.Vector2u32, &rhs.Vector2u32),
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),
// TODO: vectors specializations
else => return RuntimeError.InvalidSpirV,
}
}
fn copyValue(dst: *Result.Value, src: *const Result.Value) void {
switch (src.*) {
.Vector, .Matrix, .Array, .Structure => |src_slice| {
const dst_slice = switch (dst.*) {
.Vector, .Matrix, .Array, .Structure => |d| d,
else => unreachable,
};
for (0..@min(dst_slice.len, src_slice.len)) |i| {
copyValue(&dst_slice[i], &src_slice[i]);
}
},
else => dst.* = src.*,
}
}
pub fn getValuePrimitiveField(comptime T: ValueType, comptime BitCount: SpvWord, v: *Result.Value) RuntimeError!*getValuePrimitiveFieldType(T, BitCount) {
return switch (T) {
.Bool => &v.Bool,
.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,
},
};
}
pub fn getValuePrimitiveFieldType(comptime T: ValueType, comptime BitCount: SpvWord) type {
return switch (T) {
.Bool => bool,
.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;
rt.results[id].variant = .{
.AccessChain = .{
.target = var_type,
.value = blk: {
for (0..index_count) |_| {
const member = &rt.results[try rt.it.next()];
const member_value = switch ((try member.getVariant()).*) {
.Constant => |c| &c.value,
.Variable => |v| &v.value,
else => return RuntimeError.InvalidSpirV,
};
switch (member_value.*) {
.Int => |i| {
switch (value_ptr.*) {
.Vector, .Matrix, .Array, .Structure => |v| {
if (i.uint32 > v.len) return RuntimeError.InvalidSpirV;
value_ptr = &v[i.uint32];
},
//.Vector4f32 => |v| {
// if (i.uint32 > 4) return RuntimeError.InvalidSpirV;
// break :blk .{
// .Float = .{ .float32 = v[i.uint32] },
// };
//},
//.Vector2f32 => |v| {
// if (i.uint32 > 2) return RuntimeError.InvalidSpirV;
// break :blk .{
// .Float = .{ .float32 = v[i.uint32] },
// };
//},
else => return RuntimeError.InvalidSpirV,
}
},
else => return RuntimeError.InvalidSpirV,
}
}
break :blk value_ptr;
},
},
};
}
fn opBranch(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
const id = try rt.it.next();
_ = rt.it.jumpToSourceLocation(switch ((try rt.results[id].getVariant()).*) {
.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 ((try rt.results[try rt.it.next()].getVariant()).*) {
.Label => |l| l.source_location,
else => return RuntimeError.InvalidSpirV,
};
const false_branch = switch ((try rt.results[try rt.it.next()].getVariant()).*) {
.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 value = &(try rt.results[id].getVariant()).Constant.value;
if (value.getCompositeDataOrNull()) |target| {
for (target[0..index_count]) |*elem| {
const elem_value = (try rt.results[try rt.it.next()].getVariant()).Constant.value;
elem.* = elem_value;
}
return;
}
switch (value.*) {
.Vector4f32 => |*vec| inline for (0..4) |i| {
vec[i] = (try rt.results[try rt.it.next()].getVariant()).Constant.value.Float.float32;
},
.Vector3f32 => |*vec| inline for (0..3) |i| {
vec[i] = (try rt.results[try rt.it.next()].getVariant()).Constant.value.Float.float32;
},
.Vector2f32 => |*vec| inline for (0..2) |i| {
vec[i] = (try rt.results[try rt.it.next()].getVariant()).Constant.value.Float.float32;
},
.Vector4i32 => |*vec| inline for (0..4) |i| {
vec[i] = (try rt.results[try rt.it.next()].getVariant()).Constant.value.Int.sint32;
},
.Vector3i32 => |*vec| inline for (0..3) |i| {
vec[i] = (try rt.results[try rt.it.next()].getVariant()).Constant.value.Int.sint32;
},
.Vector2i32 => |*vec| inline for (0..2) |i| {
vec[i] = (try rt.results[try rt.it.next()].getVariant()).Constant.value.Int.sint32;
},
.Vector4u32 => |*vec| inline for (0..4) |i| {
vec[i] = (try rt.results[try rt.it.next()].getVariant()).Constant.value.Int.uint32;
},
.Vector3u32 => |*vec| inline for (0..3) |i| {
vec[i] = (try rt.results[try rt.it.next()].getVariant()).Constant.value.Int.uint32;
},
.Vector2u32 => |*vec| inline for (0..2) |i| {
vec[i] = (try rt.results[try rt.it.next()].getVariant()).Constant.value.Int.uint32;
},
else => return RuntimeError.InvalidSpirV,
}
}
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;
rt.results[id].variant = .{
.Constant = .{
.type_word = res_type,
.type = switch ((try rt.results[res_type].getVariant()).*) {
.Type => |t| @as(Result.Type, t),
else => return RuntimeError.InvalidSpirV,
},
.value = blk: {
var composite = (try rt.results[composite_id].getVariant()).Constant.value;
for (0..index_count) |_| {
const member_id = try rt.it.next();
if (composite.getCompositeDataOrNull()) |v| {
composite = v[member_id];
continue;
}
switch (composite) {
.Vector4f32 => |v| break :blk .{ .Float = .{ .float32 = v[member_id] } },
.Vector3f32 => |v| break :blk .{ .Float = .{ .float32 = v[member_id] } },
.Vector2f32 => |v| break :blk .{ .Float = .{ .float32 = v[member_id] } },
.Vector4i32 => |v| break :blk .{ .Int = .{ .sint32 = v[member_id] } },
.Vector3i32 => |v| break :blk .{ .Int = .{ .sint32 = v[member_id] } },
.Vector2i32 => |v| break :blk .{ .Int = .{ .sint32 = v[member_id] } },
.Vector4u32 => |v| break :blk .{ .Int = .{ .uint32 = v[member_id] } },
.Vector3u32 => |v| break :blk .{ .Int = .{ .uint32 = v[member_id] } },
.Vector2u32 => |v| break :blk .{ .Int = .{ .uint32 = v[member_id] } },
else => return RuntimeError.InvalidSpirV,
}
}
break :blk 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 opCopyMemory(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
const target = try rt.it.next();
const source = try rt.it.next();
copyValue(try rt.results[target].getValue(), try rt.results[source].getValue());
}
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 opDot(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
const target_type = (try rt.results[try rt.it.next()].getVariant()).Type;
var 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 = switch (target_type) {
.Float => |f| f.bit_length,
else => return RuntimeError.InvalidSpirV,
};
value.Float.float64 = 0.0;
switch (op1_value.*) {
.Vector => |vec| for (vec, op2_value.Vector) |*op1_v, *op2_v| {
switch (size) {
inline 16, 32, 64 => |i| {
(try getValuePrimitiveField(.Float, i, value)).* += (try getValuePrimitiveField(.Float, i, op1_v)).* * (try getValuePrimitiveField(.Float, i, op2_v)).*;
},
else => return RuntimeError.InvalidSpirV,
}
},
.Vector4f32 => |*vec| inline for (0..4) |i| {
value.Float.float32 += vec[i] * op2_value.Vector4f32[i];
},
.Vector3f32 => |*vec| inline for (0..3) |i| {
value.Float.float32 += vec[i] * op2_value.Vector3f32[i];
},
.Vector2f32 => |*vec| inline for (0..2) |i| {
value.Float.float32 += vec[i] * op2_value.Vector2f32[i];
},
else => return RuntimeError.InvalidSpirV,
}
}
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 opExtInst(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void {
const target_type = try rt.it.next();
const id = try rt.it.next();
const set = try rt.it.next();
const inst = try rt.it.next();
switch ((try rt.results[set].getVariant()).*) {
.Extension => |ext| if (ext.dispatcher[inst]) |pfn| {
try pfn(allocator, target_type, id, word_count, rt);
},
else => return RuntimeError.InvalidSpirV,
}
}
fn opExtInstImport(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void {
const id = try rt.it.next();
const name = try readStringN(allocator, &rt.it, word_count - 1);
rt.mod.results[id].name = name;
rt.mod.results[id].variant = .{
.Extension = .{
.dispatcher = if (extensions_map.get(name)) |map| map else return RuntimeError.UnsupportedExtension,
},
};
}
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 ((try func.getVariant()).Function.params) |param| {
const arg = &rt.results[try rt.it.next()];
((try rt.results[param].getVariant()).*).FunctionParameter.value_ptr = try arg.getValue();
}
rt.function_stack.items[rt.function_stack.items.len - 1].source_location = rt.it.emitSourceLocation();
const source_location = (try func.getVariant()).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 ((try resolved.getConstVariant()).*) {
.Type => |t| @as(Result.Type, t),
else => return RuntimeError.InvalidSpirV,
},
.value_ptr = null,
},
};
(try (rt.current_function orelse return RuntimeError.InvalidSpirV).getVariant()).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 opKill(_: std.mem.Allocator, _: SpvWord, _: *Runtime) RuntimeError!void {
return RuntimeError.Killed;
}
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 opTypeArray(_: 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 = .{
.Array = .{
.components_type_word = components_type_word,
.components_type = switch ((try rt.mod.results[components_type_word].getVariant()).*) {
.Type => |t| @as(Result.Type, t),
else => return RuntimeError.InvalidSpirV,
},
.member_count = try rt.it.next(),
},
},
};
}
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 ((try rt.mod.results[column_type_word].getVariant()).*) {
.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();
var components_type_size: usize = 0;
const components_type_concrete = try rt.mod.results[components_type_word].getVariant();
const components_type = switch (components_type_concrete.*) {
.Type => |t| blk: {
switch (t) {
.Int => |i| components_type_size = i.bit_length,
.Float => |f| components_type_size = f.bit_length,
else => {},
}
break :blk @as(Result.Type, t);
},
else => return RuntimeError.InvalidSpirV,
};
const member_count = try rt.it.next();
rt.mod.results[id].variant = .{ .Type = blk: {
if (components_type_size == 32 and rt.mod.options.use_simd_vectors_specializations) {
switch (components_type) {
.Float => switch (member_count) {
2 => break :blk .{ .Vector2f32 = .{} },
3 => break :blk .{ .Vector3f32 = .{} },
4 => break :blk .{ .Vector4f32 = .{} },
else => {},
},
.Int => {
const is_signed = components_type_concrete.Type.Int.is_signed;
switch (member_count) {
2 => break :blk if (is_signed) .{ .Vector2i32 = .{} } else .{ .Vector2u32 = .{} },
3 => break :blk if (is_signed) .{ .Vector3i32 = .{} } else .{ .Vector3u32 = .{} },
4 => break :blk if (is_signed) .{ .Vector4i32 = .{} } else .{ .Vector4u32 = .{} },
else => {},
}
},
else => {},
}
}
break :blk .{
.Vector = .{
.components_type_word = components_type_word,
.components_type = components_type,
.member_count = member_count,
},
};
} };
}
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 ((try resolved.getConstVariant()).*) {
.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();
target.variant = .{
.Constant = .{
.value = try Result.initValue(allocator, member_count, rt.mod.results, resolved),
.type_word = res_type,
.type = switch ((try resolved.getConstVariant()).*) {
.Type => |t| @as(Result.Type, t),
else => return RuntimeError.InvalidSpirV,
},
},
};
return target;
}