diff --git a/src/Module.zig b/src/Module.zig index 0a16a88..2642b35 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -166,13 +166,6 @@ pub fn init(allocator: std.mem.Allocator, source: []const SpvWord, options: Modu }); } - //@import("pretty").print(allocator, self.results, .{ - // .tab_size = 4, - // .max_depth = 0, - // .struct_max_len = 0, - // .array_max_len = 0, - //}) catch return ModuleError.OutOfMemory; - return self; } diff --git a/src/Runtime.zig b/src/Runtime.zig index 986ee6c..9afbca9 100644 --- a/src/Runtime.zig +++ b/src/Runtime.zig @@ -98,9 +98,11 @@ pub fn getEntryPointByName(self: *const Self, name: []const u8) error{NotFound}! if (blk: { // Not using std.mem.eql as entry point names may have longer size than their content for (0..@min(name.len, entry_point.name.len)) |j| { - if (name[j] != entry_point.name[j]) break :blk false; + if (name[j] != entry_point.name[j]) + break :blk false; } - if (entry_point.name.len != name.len and entry_point.name[name.len] != 0) break :blk false; + if (entry_point.name.len != name.len and entry_point.name[name.len] != 0) + break :blk false; break :blk true; }) return @intCast(i); } @@ -143,6 +145,9 @@ pub fn callEntryPoint(self: *Self, allocator: std.mem.Allocator, entry_point_ind // Spec constants pass try self.pass(allocator, .initMany(&.{ + .SpecConstantTrue, + .SpecConstantFalse, + .SpecConstantComposite, .SpecConstant, .SpecConstantOp, })); @@ -169,25 +174,21 @@ pub fn callEntryPoint(self: *Self, allocator: std.mem.Allocator, entry_point_ind } // Execution pass - try self.pass(allocator, .initFull()); - - //@import("pretty").print(allocator, self.results, .{ - // .tab_size = 4, - // .max_depth = 0, - // .struct_max_len = 0, - // .array_max_len = 0, - //}) catch return RuntimeError.OutOfMemory; + try self.pass(allocator, null); } -fn pass(self: *Self, allocator: std.mem.Allocator, op_set: std.EnumSet(spv.SpvOp)) RuntimeError!void { +fn pass(self: *Self, allocator: std.mem.Allocator, op_set: ?std.EnumSet(spv.SpvOp)) RuntimeError!void { self.it.did_jump = false; // To reset function jump while (self.it.nextOrNull()) |opcode_data| { const word_count = ((opcode_data & (~spv.SpvOpCodeMask)) >> spv.SpvWordCountShift) - 1; const opcode = (opcode_data & spv.SpvOpCodeMask); - if (!op_set.contains(@enumFromInt(opcode))) { - _ = self.it.skipN(word_count); - continue; + if (op_set) |set| { + @branchHint(.unlikely); + if (!set.contains(@enumFromInt(opcode))) { + _ = self.it.skipN(word_count); + continue; + } } var it_tmp = self.it; // Save because operations may iter on this iterator diff --git a/src/Value.zig b/src/Value.zig index 2968c5d..9614bb4 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -38,6 +38,7 @@ pub const Value = union(Type) { Bool: bool, Int: struct { bit_count: usize, + is_signed: bool, value: extern union { sint8: i8, sint16: i16, @@ -139,6 +140,7 @@ pub const Value = union(Type) { .Bool => .{ .Bool = false }, .Int => |i| .{ .Int = .{ .bit_count = i.bit_length, + .is_signed = i.is_signed, .value = .{ .uint64 = 0 }, } }, .Float => |f| .{ .Float = .{ @@ -677,6 +679,7 @@ pub const Value = union(Type) { inline 8, 16, 32, 64 => { dst.* = .{ .Int = .{ .bit_count = bits, + .is_signed = if (value_type == .SInt) true else false, .value = switch (value_type) { .SInt => switch (bits) { 8 => .{ .sint8 = v }, @@ -734,4 +737,49 @@ pub const Value = union(Type) { .UInt => std.meta.Int(.unsigned, BitCount), }; } + + pub fn resolveLaneBitWidth(self: *const Self) RuntimeError!SpvWord { + return switch (self.*) { + .Bool => 8, + .Float => |f| f.bit_length, + .Int => |i| i.bit_length, + .Vector => |v| v[0].resolveLaneBitWidth(), + .Vector4f32, + .Vector3f32, + .Vector2f32, + .Vector4i32, + .Vector3i32, + .Vector2i32, + .Vector4u32, + .Vector3u32, + .Vector2u32, + => return 32, + else => return RuntimeError.InvalidSpirV, + }; + } + + pub fn resolveLaneCount(self: *const Self) RuntimeError!SpvWord { + return switch (self.*) { + .Bool, .Float, .Int => 1, + .Vector => |v| @intCast(v.len), + .Vector4f32, .Vector4i32, .Vector4u32 => 4, + .Vector3f32, .Vector3i32, .Vector3u32 => 3, + .Vector2f32, .Vector2i32, .Vector2u32 => 2, + else => return RuntimeError.InvalidSpirV, + }; + } + + pub fn resolveSign(self: *const Self) RuntimeError!enum { signed, unsigned } { + return switch (self.*) { + .Int => |i| if (i.is_signed) .signed else .unsigned, + .Vector => |v| v[0].resolveSign(), + .Vector4i32 => .signed, + .Vector3i32 => .signed, + .Vector2i32 => .signed, + .Vector4u32 => .unsigned, + .Vector3u32 => .unsigned, + .Vector2u32 => .unsigned, + else => .unsigned, + }; + } }; diff --git a/src/opcodes.zig b/src/opcodes.zig index add164d..458366b 100644 --- a/src/opcodes.zig +++ b/src/opcodes.zig @@ -113,6 +113,7 @@ pub const SetupDispatcher = block: { .FOrdLessThan = autoSetupConstant, .FOrdLessThanEqual = autoSetupConstant, .FOrdNotEqual = autoSetupConstant, + .FRem = autoSetupConstant, .FSub = autoSetupConstant, .FUnordEqual = autoSetupConstant, .FUnordGreaterThan = autoSetupConstant, @@ -159,6 +160,7 @@ pub const SetupDispatcher = block: { .SLessThanEqual = autoSetupConstant, .SMod = autoSetupConstant, .SNegate = autoSetupConstant, + .SRem = autoSetupConstant, .SatConvertSToU = autoSetupConstant, .SatConvertUToS = autoSetupConstant, .Select = autoSetupConstant, @@ -184,12 +186,15 @@ pub const SetupDispatcher = block: { .ULessThan = autoSetupConstant, .ULessThanEqual = autoSetupConstant, .UMod = autoSetupConstant, + .Undef = autoSetupConstant, .Variable = opVariable, + .VectorShuffle = autoSetupConstant, .VectorTimesMatrix = autoSetupConstant, .VectorTimesScalar = autoSetupConstant, - .SpecConstantTrue = opSpecConstantTrue, - .SpecConstantFalse = opSpecConstantFalse, - .SpecConstantComposite = opConstantComposite, + .IAddCarry = autoSetupConstant, + .ISubBorrow = autoSetupConstant, + .UMulExtended = autoSetupConstant, + .SMulExtended = autoSetupConstant, }); }; @@ -232,6 +237,7 @@ pub fn initRuntimeDispatcher() void { 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.FRem)] = MathEngine(.Float, .Rem).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; @@ -271,10 +277,16 @@ pub fn initRuntimeDispatcher() void { 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.SNegate)] = MathEngine(.SInt, .Negate).opSingle; + runtime_dispatcher[@intFromEnum(spv.SpvOp.SRem)] = MathEngine(.SInt, .Rem).op; runtime_dispatcher[@intFromEnum(spv.SpvOp.Select)] = opSelect; 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.SpecConstant)] = opSpecConstant; + runtime_dispatcher[@intFromEnum(spv.SpvOp.SpecConstantComposite)] = opConstantComposite; + runtime_dispatcher[@intFromEnum(spv.SpvOp.SpecConstantFalse)] = opSpecConstantFalse; + runtime_dispatcher[@intFromEnum(spv.SpvOp.SpecConstantOp)] = opSpecConstantOp; + runtime_dispatcher[@intFromEnum(spv.SpvOp.SpecConstantTrue)] = opSpecConstantTrue; 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; @@ -283,10 +295,10 @@ pub fn initRuntimeDispatcher() void { 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.VectorShuffle)] = opVectorShuffle; 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.SpecConstant)] = opSpecConstant; - runtime_dispatcher[@intFromEnum(spv.SpvOp.SpecConstantOp)] = opSpecConstantOp; + runtime_dispatcher[@intFromEnum(spv.SpvOp.SMulExtended)] = opSMulExtended; // zig fmt: on // Extensions init @@ -1453,12 +1465,12 @@ fn opCompositeExtract(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Ru .Vector4f32 => |v| break :blk .{ .Float = .{ .bit_count = 32, .value = .{ .float32 = v[member_id] } } }, .Vector3f32 => |v| break :blk .{ .Float = .{ .bit_count = 32, .value = .{ .float32 = v[member_id] } } }, .Vector2f32 => |v| break :blk .{ .Float = .{ .bit_count = 32, .value = .{ .float32 = v[member_id] } } }, - .Vector4i32 => |v| break :blk .{ .Int = .{ .bit_count = 32, .value = .{ .sint32 = v[member_id] } } }, - .Vector3i32 => |v| break :blk .{ .Int = .{ .bit_count = 32, .value = .{ .sint32 = v[member_id] } } }, - .Vector2i32 => |v| break :blk .{ .Int = .{ .bit_count = 32, .value = .{ .sint32 = v[member_id] } } }, - .Vector4u32 => |v| break :blk .{ .Int = .{ .bit_count = 32, .value = .{ .uint32 = v[member_id] } } }, - .Vector3u32 => |v| break :blk .{ .Int = .{ .bit_count = 32, .value = .{ .uint32 = v[member_id] } } }, - .Vector2u32 => |v| break :blk .{ .Int = .{ .bit_count = 32, .value = .{ .uint32 = v[member_id] } } }, + .Vector4i32 => |v| break :blk .{ .Int = .{ .bit_count = 32, .is_signed = true, .value = .{ .sint32 = v[member_id] } } }, + .Vector3i32 => |v| break :blk .{ .Int = .{ .bit_count = 32, .is_signed = true, .value = .{ .sint32 = v[member_id] } } }, + .Vector2i32 => |v| break :blk .{ .Int = .{ .bit_count = 32, .is_signed = true, .value = .{ .sint32 = v[member_id] } } }, + .Vector4u32 => |v| break :blk .{ .Int = .{ .bit_count = 32, .is_signed = false, .value = .{ .uint32 = v[member_id] } } }, + .Vector3u32 => |v| break :blk .{ .Int = .{ .bit_count = 32, .is_signed = false, .value = .{ .uint32 = v[member_id] } } }, + .Vector2u32 => |v| break :blk .{ .Int = .{ .bit_count = 32, .is_signed = false, .value = .{ .uint32 = v[member_id] } } }, else => return RuntimeError.InvalidValueType, } } @@ -1641,6 +1653,58 @@ fn opConstantComposite(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) R } } +fn opSMulExtended(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { + const result_type_id = try rt.it.next(); + const id = try rt.it.next(); + const lhs = try rt.results[try rt.it.next()].getValue(); + const rhs = try rt.results[try rt.it.next()].getValue(); + const dst = try rt.results[id].getValue(); + + const result_members = switch (dst.*) { + .Structure => |s| s.values, + else => return RuntimeError.InvalidSpirV, + }; + if (result_members.len != 2) return RuntimeError.InvalidSpirV; + + const lsb_dst = &result_members[0]; + const msb_dst = &result_members[1]; + + const result_type = (try rt.results[result_type_id].getVariant()).Type; + const member_types = switch (result_type) { + .Structure => |s| s.members_type_word, + else => return RuntimeError.InvalidSpirV, + }; + if (member_types.len != 2) return RuntimeError.InvalidSpirV; + + const value_type = (try rt.results[member_types[0]].getVariant()).Type; + const lane_count = try Result.resolveLaneCount(value_type); + const lane_bits = try Result.resolveLaneBitWidth(value_type, rt); + + switch (lane_bits) { + inline 8, 16, 32, 64 => |bits| { + //const SIntT = Value.getPrimitiveFieldType(.SInt, bits); + const UIntT = Value.getPrimitiveFieldType(.UInt, bits); + const WideSIntT = std.meta.Int(.signed, bits * 2); + const WideUIntT = std.meta.Int(.unsigned, bits * 2); + + for (0..lane_count) |lane_index| { + const l = try Value.readLane(.SInt, bits, lhs, lane_index); + const r = try Value.readLane(.SInt, bits, rhs, lane_index); + + const product: WideSIntT = @as(WideSIntT, l) * @as(WideSIntT, r); + const product_bits: WideUIntT = @bitCast(product); + + const lsb_bits: UIntT = @truncate(product_bits); + const msb_bits: UIntT = @truncate(product_bits >> bits); + + try Value.writeLane(.SInt, bits, lsb_dst, lane_index, @bitCast(lsb_bits)); + try Value.writeLane(.SInt, bits, msb_dst, lane_index, @bitCast(msb_bits)); + } + }, + else => return RuntimeError.InvalidSpirV, + } +} + fn opSpecConstant(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { const location = rt.it.emitSourceLocation(); _ = rt.it.skip(); @@ -1666,6 +1730,14 @@ fn opSpecConstantTrue(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) Ru .Bool => |*b| b.* = true, else => return RuntimeError.InvalidSpirV, } + + for (target.decorations.items) |decoration| { + if (decoration.rtype == .SpecId) { + if (rt.specialization_constants.get(decoration.literal_1)) |data| { + _ = try (try target.getValue()).writeConst(data); + } + } + } } fn opSpecConstantFalse(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { @@ -1674,6 +1746,14 @@ fn opSpecConstantFalse(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) R .Bool => |*b| b.* = false, else => return RuntimeError.InvalidSpirV, } + + for (target.decorations.items) |decoration| { + if (decoration.rtype == .SpecId) { + if (rt.specialization_constants.get(decoration.literal_1)) |data| { + _ = try (try target.getValue()).writeConst(data); + } + } + } } fn opSpecConstantOp(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { @@ -1689,7 +1769,7 @@ fn opSpecConstantOp(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runt rt.it.forceSkipIndex(2); const pfn = runtime_dispatcher[@intFromEnum(inner_op)] orelse return RuntimeError.UnsupportedSpirV; - try pfn(allocator, word_count, rt); + try pfn(allocator, word_count - 1, rt); } fn opCopyMemory(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { @@ -2159,7 +2239,16 @@ fn opTypeArray(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void .Type => |t| @as(Result.Type, t), else => return RuntimeError.InvalidSpirV, }, - .member_count = try rt.it.next(), + .member_count = switch ((try rt.results[try rt.it.next()].getValue()).*) { + .Int => |i| if (!i.is_signed) @intCast(i.value.uint64) else switch (i.bit_count) { + 8 => @intCast(i.value.sint8), + 16 => @intCast(i.value.sint8), + 32 => @intCast(i.value.sint8), + 64 => @intCast(i.value.sint8), + else => return RuntimeError.InvalidSpirV, + }, + else => return RuntimeError.InvalidSpirV, + }, .stride = blk: { for (target.decorations.items) |decoration| { if (decoration.rtype == .ArrayStride) @@ -2407,6 +2496,248 @@ fn opVariable(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) R _ = initializer; } +fn opVectorShuffle(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { + _ = allocator; + _ = try rt.it.next(); + + const result_id = try rt.it.next(); + const vector_1_id = try rt.it.next(); + const vector_2_id = try rt.it.next(); + + const dst = try rt.results[result_id].getValue(); + const vector_1 = try rt.results[vector_1_id].getValue(); + const vector_2 = try rt.results[vector_2_id].getValue(); + + const Impl = struct { + fn readLane(src: *const Value, lane_index: usize) RuntimeError!Value { + return switch (src.*) { + .Vector => |lanes| { + if (lane_index >= lanes.len) return RuntimeError.InvalidSpirV; + return lanes[lane_index]; + }, + + .Vector2f32 => |lanes| { + if (lane_index >= 2) return RuntimeError.InvalidSpirV; + return .{ + .Float = .{ + .bit_count = 32, + .value = .{ .float32 = lanes[lane_index] }, + }, + }; + }, + .Vector3f32 => |lanes| { + if (lane_index >= 3) return RuntimeError.InvalidSpirV; + return .{ + .Float = .{ + .bit_count = 32, + .value = .{ .float32 = lanes[lane_index] }, + }, + }; + }, + .Vector4f32 => |lanes| { + if (lane_index >= 4) return RuntimeError.InvalidSpirV; + return .{ + .Float = .{ + .bit_count = 32, + .value = .{ .float32 = lanes[lane_index] }, + }, + }; + }, + + .Vector2i32 => |lanes| { + if (lane_index >= 2) return RuntimeError.InvalidSpirV; + return .{ + .Int = .{ + .bit_count = 32, + .is_signed = true, + .value = .{ .sint32 = lanes[lane_index] }, + }, + }; + }, + .Vector3i32 => |lanes| { + if (lane_index >= 3) return RuntimeError.InvalidSpirV; + return .{ + .Int = .{ + .bit_count = 32, + .is_signed = true, + .value = .{ .sint32 = lanes[lane_index] }, + }, + }; + }, + .Vector4i32 => |lanes| { + if (lane_index >= 4) return RuntimeError.InvalidSpirV; + return .{ + .Int = .{ + .bit_count = 32, + .is_signed = true, + .value = .{ .sint32 = lanes[lane_index] }, + }, + }; + }, + + .Vector2u32 => |lanes| { + if (lane_index >= 2) return RuntimeError.InvalidSpirV; + return .{ + .Int = .{ + .bit_count = 32, + .is_signed = false, + .value = .{ .uint32 = lanes[lane_index] }, + }, + }; + }, + .Vector3u32 => |lanes| { + if (lane_index >= 3) return RuntimeError.InvalidSpirV; + return .{ + .Int = .{ + .bit_count = 32, + .is_signed = false, + .value = .{ .uint32 = lanes[lane_index] }, + }, + }; + }, + .Vector4u32 => |lanes| { + if (lane_index >= 4) return RuntimeError.InvalidSpirV; + return .{ + .Int = .{ + .bit_count = 32, + .is_signed = false, + .value = .{ .uint32 = lanes[lane_index] }, + }, + }; + }, + + else => return RuntimeError.InvalidSpirV, + }; + } + + fn writeLane(dst_value: *Value, lane_index: usize, lane_value: Value) RuntimeError!void { + switch (dst_value.*) { + .Vector => |lanes| { + if (lane_index >= lanes.len) return RuntimeError.InvalidSpirV; + lanes[lane_index] = lane_value; + }, + + .Vector2f32 => |*lanes| { + if (lane_index >= 2) return RuntimeError.InvalidSpirV; + switch (lane_value) { + .Float => |f| { + if (f.bit_count != 32) return RuntimeError.InvalidSpirV; + lanes[lane_index] = f.value.float32; + }, + else => return RuntimeError.InvalidSpirV, + } + }, + .Vector3f32 => |*lanes| { + if (lane_index >= 3) return RuntimeError.InvalidSpirV; + switch (lane_value) { + .Float => |f| { + if (f.bit_count != 32) return RuntimeError.InvalidSpirV; + lanes[lane_index] = f.value.float32; + }, + else => return RuntimeError.InvalidSpirV, + } + }, + .Vector4f32 => |*lanes| { + if (lane_index >= 4) return RuntimeError.InvalidSpirV; + switch (lane_value) { + .Float => |f| { + if (f.bit_count != 32) return RuntimeError.InvalidSpirV; + lanes[lane_index] = f.value.float32; + }, + else => return RuntimeError.InvalidSpirV, + } + }, + + .Vector2i32 => |*lanes| { + if (lane_index >= 2) return RuntimeError.InvalidSpirV; + switch (lane_value) { + .Int => |i| { + if (i.bit_count != 32) return RuntimeError.InvalidSpirV; + lanes[lane_index] = i.value.sint32; + }, + else => return RuntimeError.InvalidSpirV, + } + }, + .Vector3i32 => |*lanes| { + if (lane_index >= 3) return RuntimeError.InvalidSpirV; + switch (lane_value) { + .Int => |i| { + if (i.bit_count != 32) return RuntimeError.InvalidSpirV; + lanes[lane_index] = i.value.sint32; + }, + else => return RuntimeError.InvalidSpirV, + } + }, + .Vector4i32 => |*lanes| { + if (lane_index >= 4) return RuntimeError.InvalidSpirV; + switch (lane_value) { + .Int => |i| { + if (i.bit_count != 32) return RuntimeError.InvalidSpirV; + lanes[lane_index] = i.value.sint32; + }, + else => return RuntimeError.InvalidSpirV, + } + }, + + .Vector2u32 => |*lanes| { + if (lane_index >= 2) return RuntimeError.InvalidSpirV; + switch (lane_value) { + .Int => |i| { + if (i.bit_count != 32) return RuntimeError.InvalidSpirV; + lanes[lane_index] = i.value.uint32; + }, + else => return RuntimeError.InvalidSpirV, + } + }, + .Vector3u32 => |*lanes| { + if (lane_index >= 3) return RuntimeError.InvalidSpirV; + switch (lane_value) { + .Int => |i| { + if (i.bit_count != 32) return RuntimeError.InvalidSpirV; + lanes[lane_index] = i.value.uint32; + }, + else => return RuntimeError.InvalidSpirV, + } + }, + .Vector4u32 => |*lanes| { + if (lane_index >= 4) return RuntimeError.InvalidSpirV; + switch (lane_value) { + .Int => |i| { + if (i.bit_count != 32) return RuntimeError.InvalidSpirV; + lanes[lane_index] = i.value.uint32; + }, + else => return RuntimeError.InvalidSpirV, + } + }, + + else => return RuntimeError.InvalidSpirV, + } + } + }; + + const dst_lane_count = try dst.resolveLaneCount(); + const vector_1_lane_count = try vector_1.resolveLaneCount(); + const vector_2_lane_count = try vector_2.resolveLaneCount(); + + for (0..dst_lane_count) |lane_index| { + const selector = try rt.it.next(); + + if (selector == std.math.maxInt(u32)) { + continue; + } + + const lane_value = if (selector < vector_1_lane_count) + try Impl.readLane(vector_1, selector) + else blk: { + const rhs_index = selector - vector_1_lane_count; + if (rhs_index >= vector_2_lane_count) return RuntimeError.InvalidSpirV; + break :blk try Impl.readLane(vector_2, rhs_index); + }; + + try Impl.writeLane(dst, lane_index, lane_value); + } +} + fn readString(allocator: std.mem.Allocator, it: *WordIterator) RuntimeError![]const u8 { var str: std.ArrayList(u8) = .empty; while (it.nextOrNull()) |word| {