adding missing opcodes, fixing runtime array pointer windows
Build / build (push) Successful in 1m20s
Test / build (push) Successful in 9m40s

This commit is contained in:
2026-06-29 14:30:16 +02:00
parent b9b6087fef
commit fcf303a0c6
6 changed files with 854 additions and 212 deletions
+18 -3
View File
@@ -258,10 +258,25 @@ fn readIntegralLaneAsI32(src: *const Value, lane_index: usize) RuntimeError!i32
const lane_bits = try src.resolveLaneBitWidth(); const lane_bits = try src.resolveLaneBitWidth();
const sign = try src.resolveSign(); const sign = try src.resolveSign();
return switch (lane_bits) { return switch (lane_bits) {
inline 8, 16, 32, 64 => |bits| if (sign == .signed) inline 8, 16, 32, 64 => |bits| blk: {
@intCast(try Value.readLane(.SInt, bits, src, lane_index)) if (sign == .signed) {
const value = try Value.readLane(.SInt, bits, src, lane_index);
if (bits > 32) {
break :blk std.math.cast(i32, value) orelse if (value < 0)
std.math.minInt(i32)
else else
@intCast(try Value.readLane(.UInt, bits, src, lane_index)), std.math.maxInt(i32);
}
break :blk @intCast(value);
} else {
const value = try Value.readLane(.UInt, bits, src, lane_index);
const bits32: u32 = if (bits > 32)
std.math.cast(u32, value) orelse std.math.maxInt(u32)
else
@intCast(value);
break :blk @bitCast(bits32);
}
},
else => RuntimeError.InvalidSpirV, else => RuntimeError.InvalidSpirV,
}; };
} }
+2
View File
@@ -54,6 +54,8 @@ pub const ReflectionInfos = struct {
needs_derivatives: bool, needs_derivatives: bool,
has_control_barriers: bool, has_control_barriers: bool,
has_atomics: bool,
early_fragment_tests: bool,
}; };
options: ModuleOptions, options: ModuleOptions,
-1
View File
@@ -482,7 +482,6 @@ pub fn getMemberCounts(self: *const Self) usize {
pub inline fn flushPtr(self: *Self, allocator: std.mem.Allocator) RuntimeError!void { pub inline fn flushPtr(self: *Self, allocator: std.mem.Allocator) RuntimeError!void {
if (self.variant) |*variant| switch (variant.*) { if (self.variant) |*variant| switch (variant.*) {
.Variable => |*v| try v.value.flushPtr(allocator),
.AccessChain => |*a| { .AccessChain => |*a| {
if (!std.mem.allEqual(u8, std.mem.asBytes(&a.value), 0xaa)) if (!std.mem.allEqual(u8, std.mem.asBytes(&a.value), 0xaa))
try a.value.flushPtr(allocator); try a.value.flushPtr(allocator);
+369 -38
View File
@@ -47,6 +47,11 @@ pub const SpecializationEntry = struct {
size: usize, size: usize,
}; };
pub const WorkgroupMemory = struct {
result: SpvWord,
bytes: []u8,
};
pub const Derivative = struct { pub const Derivative = struct {
dx: Value, dx: Value,
dy: Value, dy: Value,
@@ -118,14 +123,16 @@ function_stack: std.ArrayList(Function),
current_label: ?SpvWord, current_label: ?SpvWord,
previous_label: ?SpvWord, previous_label: ?SpvWord,
active_entry_point: ?SpvWord,
specialization_constants: std.AutoHashMapUnmanaged(u32, []const u8), specialization_constants: std.AutoHashMapUnmanaged(u32, []const u8),
derivatives: std.AutoHashMapUnmanaged(SpvWord, Derivative), derivatives: std.AutoHashMapUnmanaged(SpvWord, Derivative),
phi_values: std.AutoHashMapUnmanaged(SpvWord, Value),
image_api: ImageAPI, image_api: ImageAPI,
pub fn init(allocator: std.mem.Allocator, module: *Module, image_api: ImageAPI) RuntimeError!Self { pub fn init(allocator: std.mem.Allocator, module: *Module, image_api: ImageAPI) RuntimeError!Self {
return .{ var self: Self = .{
.mod = module, .mod = module,
.it = module.it, .it = module.it,
.results = blk: { .results = blk: {
@@ -150,10 +157,15 @@ pub fn init(allocator: std.mem.Allocator, module: *Module, image_api: ImageAPI)
.function_stack = .empty, .function_stack = .empty,
.current_label = null, .current_label = null,
.previous_label = null, .previous_label = null,
.active_entry_point = null,
.specialization_constants = .empty, .specialization_constants = .empty,
.derivatives = .empty, .derivatives = .empty,
.phi_values = .empty,
.image_api = image_api, .image_api = image_api,
}; };
errdefer self.deinit(allocator);
try self.refreshValueLayouts();
return self;
} }
pub fn initFrom(allocator: std.mem.Allocator, other: *const Self, image_api: ImageAPI) RuntimeError!Self { pub fn initFrom(allocator: std.mem.Allocator, other: *const Self, image_api: ImageAPI) RuntimeError!Self {
@@ -180,13 +192,16 @@ pub fn initFrom(allocator: std.mem.Allocator, other: *const Self, image_api: Ima
.function_stack = .empty, .function_stack = .empty,
.current_label = null, .current_label = null,
.previous_label = null, .previous_label = null,
.active_entry_point = other.active_entry_point,
.specialization_constants = .empty, .specialization_constants = .empty,
.derivatives = .empty, .derivatives = .empty,
.phi_values = .empty,
.image_api = image_api, .image_api = image_api,
}; };
errdefer self.deinit(allocator); errdefer self.deinit(allocator);
try self.copySpecializationConstantsFrom(allocator, other); try self.copySpecializationConstantsFrom(allocator, other);
try self.refreshValueLayouts();
return self; return self;
} }
@@ -207,6 +222,9 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
entry.value_ptr.deinit(allocator); entry.value_ptr.deinit(allocator);
} }
self.derivatives.deinit(allocator); self.derivatives.deinit(allocator);
self.clearPhiValues(allocator);
self.phi_values.deinit(allocator);
} }
pub fn addSpecializationInfo(self: *Self, allocator: std.mem.Allocator, entry: SpecializationEntry, data: []const u8) RuntimeError!void { pub fn addSpecializationInfo(self: *Self, allocator: std.mem.Allocator, entry: SpecializationEntry, data: []const u8) RuntimeError!void {
@@ -225,6 +243,169 @@ pub fn copySpecializationConstantsFrom(self: *Self, allocator: std.mem.Allocator
} }
} }
fn applyValueLayout(results: []Result, value: *Value, type_word: SpvWord) RuntimeError!void {
const resolved_type_word = results[type_word].resolveTypeWordOrNull() orelse type_word;
const type_data = (try results[resolved_type_word].getConstVariant()).Type;
switch (value.*) {
.Structure => |*structure| switch (type_data) {
.Structure => |type_structure| {
@memcpy(@constCast(structure.offsets), type_structure.members_offsets);
@memcpy(@constCast(structure.matrix_strides), type_structure.members_matrix_strides);
@memcpy(@constCast(structure.row_major), type_structure.members_row_major);
for (structure.values, type_structure.members_type_word, 0..) |*member_value, member_type_word, member_index| {
if (member_value.* == .RuntimeArray) {
member_value.RuntimeArray.matrix_stride = type_structure.members_matrix_strides[member_index];
member_value.RuntimeArray.row_major = type_structure.members_row_major[member_index];
}
try applyValueLayout(results, member_value, member_type_word);
}
},
else => {},
},
.Array => |array| switch (type_data) {
.Array => |type_array| for (array.values) |*element| {
try applyValueLayout(results, element, type_array.components_type_word);
},
else => {},
},
.Matrix => |columns| switch (type_data) {
.Matrix => |type_matrix| for (columns) |*column| {
try applyValueLayout(results, column, type_matrix.column_type_word);
},
else => {},
},
.Vector => |elements| switch (type_data) {
.Vector => |type_vector| for (elements) |*element| {
try applyValueLayout(results, element, type_vector.components_type_word);
},
else => {},
},
else => {},
}
}
fn refreshValueLayouts(self: *Self) RuntimeError!void {
for (self.results) |*result| {
if (result.variant) |*variant| switch (variant.*) {
.Variable => |*v| switch (v.storage_class) {
.StorageBuffer,
.Uniform,
.PushConstant,
.Workgroup,
=> try applyValueLayout(self.results, &v.value, v.type_word),
else => {},
},
else => {},
};
}
}
fn typePlainMemorySize(self: *const Self, type_word: SpvWord) RuntimeError!usize {
const resolved_word = self.results[type_word].resolveTypeWordOrNull() orelse type_word;
const target_type = (try self.results[resolved_word].getConstVariant()).Type;
return switch (target_type) {
.Array => |a| blk: {
const stride: usize = if (a.stride != 0)
@intCast(a.stride)
else
try self.typePlainMemorySize(a.components_type_word);
break :blk stride * @as(usize, @intCast(a.member_count));
},
.RuntimeArray => return RuntimeError.InvalidValueType,
.Structure => |s| blk: {
var size: usize = 0;
for (s.members_type_word, 0..) |member_type_word, i| {
const member_offset: usize = @intCast(s.members_offsets[i] orelse size);
size = @max(size, member_offset + try self.typePlainMemorySize(member_type_word));
}
break :blk size;
},
else => target_type.getSize(self.results),
};
}
pub fn createWorkgroupMemory(self: *Self, allocator: std.mem.Allocator) RuntimeError![]WorkgroupMemory {
var count: usize = 0;
for (self.results) |result| {
if (result.variant) |variant| switch (variant) {
.Variable => |v| {
if (v.storage_class == .Workgroup) count += 1;
},
else => {},
};
}
const memories = allocator.alloc(WorkgroupMemory, count) catch return RuntimeError.OutOfMemory;
errdefer allocator.free(memories);
var index: usize = 0;
for (self.results, 0..) |result, result_id| {
if (result.variant) |variant| switch (variant) {
.Variable => |v| {
if (v.storage_class == .Workgroup) {
const size = try self.typePlainMemorySize(v.type_word);
const bytes = allocator.alloc(u8, size) catch return RuntimeError.OutOfMemory;
@memset(bytes, 0);
memories[index] = .{
.result = @intCast(result_id),
.bytes = bytes,
};
index += 1;
}
},
else => {},
};
}
return memories;
}
pub fn destroyWorkgroupMemory(_: *Self, allocator: std.mem.Allocator, memories: []WorkgroupMemory) void {
for (memories) |memory| allocator.free(memory.bytes);
allocator.free(memories);
}
pub fn bindWorkgroupMemory(self: *Self, memories: []const WorkgroupMemory) RuntimeError!void {
for (memories) |memory| {
_ = try (try self.results[memory.result].getValue()).write(memory.bytes);
}
}
fn clearPhiValues(self: *Self, allocator: std.mem.Allocator) void {
var it = self.phi_values.iterator();
while (it.next()) |entry| {
entry.value_ptr.deinit(allocator);
}
self.phi_values.clearRetainingCapacity();
}
pub fn snapshotPhiValues(self: *Self, allocator: std.mem.Allocator) RuntimeError!void {
self.clearPhiValues(allocator);
for (self.results, 0..) |*result, result_id| {
const value = switch (result.variant orelse continue) {
.Constant => |*constant| &constant.value,
.FunctionParameter => |*parameter| parameter.value_ptr orelse continue,
else => continue,
};
if (std.meta.activeTag(value.*) == .Pointer) continue;
const snapshot = try value.dupe(allocator);
const gop = self.phi_values.getOrPut(allocator, @intCast(result_id)) catch {
var tmp = snapshot;
tmp.deinit(allocator);
return RuntimeError.OutOfMemory;
};
if (gop.found_existing) gop.value_ptr.deinit(allocator);
gop.value_ptr.* = snapshot;
}
}
pub fn getPhiValueSnapshot(self: *Self, id: SpvWord) ?*const Value {
return self.phi_values.getPtr(id);
}
pub fn setDerivative(self: *Self, allocator: std.mem.Allocator, result: SpvWord, dx: *const Value, dy: *const Value) RuntimeError!void { pub fn setDerivative(self: *Self, allocator: std.mem.Allocator, result: SpvWord, dx: *const Value, dy: *const Value) RuntimeError!void {
const derivative: Derivative = .{ const derivative: Derivative = .{
.dx = try dx.dupe(allocator), .dx = try dx.dupe(allocator),
@@ -283,33 +464,70 @@ pub fn copyDerivative(self: *Self, allocator: std.mem.Allocator, dst: SpvWord, s
pub fn applySpecializationLayout(self: *Self, allocator: std.mem.Allocator) RuntimeError!void { pub fn applySpecializationLayout(self: *Self, allocator: std.mem.Allocator) RuntimeError!void {
self.reset(); self.reset();
try self.applySpecializationConstants(allocator);
try self.applySpecializationDependentLayout(allocator);
}
pub fn applySpecializationInvocationLayout(self: *Self, allocator: std.mem.Allocator) RuntimeError!void {
self.reset();
if (self.specialization_constants.count() != 0)
try self.applySpecializationConstants(allocator);
try self.applySpecializationDependentLayout(allocator);
}
fn applySpecializationConstants(self: *Self, allocator: std.mem.Allocator) RuntimeError!void {
try self.pass(allocator, .initMany(&.{ try self.pass(allocator, .initMany(&.{
.SpecConstantTrue, .SpecConstantTrue,
.SpecConstantFalse, .SpecConstantFalse,
.SpecConstantComposite, .SpecConstantComposite,
.ConstantNull,
.SpecConstant, .SpecConstant,
.SpecConstantOp, .SpecConstantOp,
}));
}
fn applySpecializationDependentLayout(self: *Self, allocator: std.mem.Allocator) RuntimeError!void {
try self.pass(allocator, .initMany(&.{
.TypeArray, .TypeArray,
.Variable, .Variable,
})); }));
} }
pub fn getEntryPointByName(self: *const Self, name: []const u8) RuntimeError!SpvWord { pub fn getEntryPointByName(self: *const Self, name: []const u8) RuntimeError!SpvWord {
for (self.mod.entry_points.items, 0..) |entry_point, i| { if (self.active_entry_point) |entry_point| {
if (blk: { if (entryPointNameMatches(self.mod.entry_points.items[entry_point].name, name))
// Not using std.mem.eql as entry point names may have longer size than their content return entry_point;
for (0..@min(name.len, entry_point.name.len)) |j| {
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; for (self.mod.entry_points.items, 0..) |entry_point, i| {
break :blk true; if (entryPointNameMatches(entry_point.name, name)) return @intCast(i);
}) return @intCast(i);
} }
return RuntimeError.NotFound; return RuntimeError.NotFound;
} }
pub fn getEntryPointByNameAndExecutionModel(self: *const Self, name: []const u8, execution_model: spv.SpvExecutionModel) RuntimeError!SpvWord {
for (self.mod.entry_points.items, 0..) |entry_point, i| {
if (entry_point.exec_model == execution_model and entryPointNameMatches(entry_point.name, name))
return @intCast(i);
}
return RuntimeError.NotFound;
}
fn entryPointNameMatches(entry_point_name: []const u8, name: []const u8) bool {
// 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])
return false;
}
return entry_point_name.len == name.len or entry_point_name[name.len] == 0;
}
pub fn selectEntryPoint(self: *Self, entry_point_index: SpvWord) RuntimeError!void {
if (entry_point_index >= self.mod.entry_points.items.len)
return RuntimeError.InvalidEntryPoint;
self.active_entry_point = entry_point_index;
}
pub fn getResultByName(self: *const Self, name: []const u8) RuntimeError!SpvWord { pub fn getResultByName(self: *const Self, name: []const u8) RuntimeError!SpvWord {
for (self.results, 0..) |result, i| { for (self.results, 0..) |result, i| {
if (result.name) |result_name| { if (result.name) |result_name| {
@@ -332,17 +550,76 @@ pub inline fn getResultByLocation(self: *const Self, location: SpvWord, kind: Lo
} }
pub fn getResultByLocationComponent(self: *const Self, location: SpvWord, component: SpvWord, kind: LocationKind) RuntimeError!SpvWord { pub fn getResultByLocationComponent(self: *const Self, location: SpvWord, component: SpvWord, kind: LocationKind) RuntimeError!SpvWord {
switch (kind) { const locations = switch (kind) {
.input => if (location < self.mod.input_locations.len and component < 4 and self.mod.input_locations[location][component] != 0) { .input => &self.mod.input_locations,
return self.mod.input_locations[location][component]; .output => &self.mod.output_locations,
}, };
.output => if (location < self.mod.output_locations.len and component < 4 and self.mod.output_locations[location][component] != 0) { if (location >= locations.len or component >= 4)
return self.mod.output_locations[location][component]; return RuntimeError.NotFound;
},
const result = locations[location][component];
if (result != 0 and self.resultIsInActiveInterface(result))
return result;
if (self.active_entry_point) |entry_point_index| {
const entry_point = self.mod.entry_points.items[entry_point_index];
for (entry_point.globals) |global| {
if (global >= self.results.len)
continue;
const variant = self.results[global].variant orelse continue;
const variable = switch (variant) {
.Variable => |v| v,
else => continue,
};
const storage_class_matches = switch (kind) {
.input => variable.storage_class == .Input,
.output => variable.storage_class == .Output,
};
if (!storage_class_matches)
continue;
for (self.results[global].decorations.items) |decoration| {
if (decoration.rtype == .Location and
decoration.literal_1 == location and
self.resultComponent(global) == component)
{
return global;
}
}
}
} }
return RuntimeError.NotFound; return RuntimeError.NotFound;
} }
fn resultIsInActiveInterface(self: *const Self, result: SpvWord) bool {
const entry_point_index = self.active_entry_point orelse return true;
const global = self.resultGlobal(result) orelse return false;
return std.mem.indexOfScalar(SpvWord, self.mod.entry_points.items[entry_point_index].globals, global) != null;
}
fn resultGlobal(self: *const Self, result: SpvWord) ?SpvWord {
if (result >= self.results.len)
return null;
const variant = self.results[result].variant orelse return result;
return switch (variant) {
.AccessChain => |access_chain| access_chain.base,
else => result,
};
}
fn resultComponent(self: *const Self, result: SpvWord) SpvWord {
if (result >= self.results.len)
return 0;
for (self.results[result].decorations.items) |decoration| {
if (decoration.rtype == .Component)
return decoration.literal_1;
}
return 0;
}
pub fn getResultPrimitiveType(self: *const Self, result: SpvWord) RuntimeError!PrimitiveType { pub fn getResultPrimitiveType(self: *const Self, result: SpvWord) RuntimeError!PrimitiveType {
if (result >= self.results.len) if (result >= self.results.len)
return RuntimeError.OutOfBounds; return RuntimeError.OutOfBounds;
@@ -354,6 +631,7 @@ pub fn getWorkgroupSize(self: *Self, allocator: std.mem.Allocator) RuntimeError!
.SpecConstantTrue, .SpecConstantTrue,
.SpecConstantFalse, .SpecConstantFalse,
.SpecConstantComposite, .SpecConstantComposite,
.ConstantNull,
.SpecConstant, .SpecConstant,
.SpecConstantOp, .SpecConstantOp,
})); }));
@@ -410,13 +688,8 @@ pub fn beginEntryPoint(self: *Self, allocator: std.mem.Allocator, entry_point_in
return RuntimeError.InvalidEntryPoint; return RuntimeError.InvalidEntryPoint;
// Spec constants pass // Spec constants pass
try self.pass(allocator, .initMany(&.{ if (self.specialization_constants.count() != 0)
.SpecConstantTrue, try self.applySpecializationConstants(allocator);
.SpecConstantFalse,
.SpecConstantComposite,
.SpecConstant,
.SpecConstantOp,
}));
{ {
const entry_point_desc = &self.mod.entry_points.items[entry_point_index]; const entry_point_desc = &self.mod.entry_points.items[entry_point_index];
@@ -681,20 +954,22 @@ const InputLocationTarget = struct {
}; };
fn resolveInputLocationTarget(self: *const Self, location: SpvWord) RuntimeError!InputLocationTarget { fn resolveInputLocationTarget(self: *const Self, location: SpvWord) RuntimeError!InputLocationTarget {
if (location < self.mod.input_locations.len and if (self.getResultByLocationComponent(location, 0, .input)) |result| {
self.mod.input_locations[location][0] != 0 and if (self.results[result].variant == null)
self.results[self.mod.input_locations[location][0]].variant != null) return RuntimeError.NotFound;
{
const result = self.mod.input_locations[location][0];
const value = try self.results[result].getConstValue(); const value = try self.results[result].getConstValue();
switch (value.*) { switch (value.*) {
.Array => |arr| { .Array => |arr| {
if (arr.values.len == 0) return RuntimeError.OutOfBounds; if (arr.values.len == 0) return RuntimeError.OutOfBounds;
return .{ .result = result, .array_element = 0 }; return .{ .result = result };
}, },
.Matrix => return .{ .result = result, .matrix_column = 0 }, .Matrix => return .{ .result = result, .matrix_column = 0 },
else => return .{ .result = result }, else => return .{ .result = result },
} }
} else |err| switch (err) {
RuntimeError.NotFound => {},
else => return err,
} }
for (self.results, 0..) |*result, id| { for (self.results, 0..) |*result, id| {
@@ -736,11 +1011,10 @@ fn resolveInputLocationTarget(self: *const Self, location: SpvWord) RuntimeError
while (base_location > 0) { while (base_location > 0) {
base_location -= 1; base_location -= 1;
const result = if (base_location < self.mod.input_locations.len) const result = self.getResultByLocationComponent(base_location, 0, .input) catch |err| switch (err) {
self.mod.input_locations[base_location][0] RuntimeError.NotFound => continue,
else else => return err,
0; };
if (result == 0) continue;
const location_offset: usize = @intCast(location - base_location); const location_offset: usize = @intCast(location - base_location);
const value = try self.results[result].getConstValue(); const value = try self.results[result].getConstValue();
@@ -830,7 +1104,7 @@ pub fn readOutput(self: *const Self, output: []u8, result: SpvWord) RuntimeError
} }
pub fn readBuiltIn(self: *const Self, output: []u8, builtin: spv.SpvBuiltIn) RuntimeError!void { pub fn readBuiltIn(self: *const Self, output: []u8, builtin: spv.SpvBuiltIn) RuntimeError!void {
if (self.mod.builtins.get(builtin)) |result| { if (self.getBuiltinResult(builtin)) |result| {
try self.readResultValue(output, result); try self.readResultValue(output, result);
} else { } else {
return RuntimeError.NotFound; return RuntimeError.NotFound;
@@ -865,13 +1139,34 @@ pub fn writeInputLocation(self: *const Self, input: []const u8, location: SpvWor
} }
pub fn writeBuiltIn(self: *const Self, allocator: std.mem.Allocator, input: []const u8, builtin: spv.SpvBuiltIn) RuntimeError!void { pub fn writeBuiltIn(self: *const Self, allocator: std.mem.Allocator, input: []const u8, builtin: spv.SpvBuiltIn) RuntimeError!void {
if (self.mod.builtins.get(builtin)) |result| { if (self.getBuiltinResult(builtin)) |result| {
try self.writeResultValue(allocator, input, result); try self.writeResultValue(allocator, input, result);
} else { } else {
return RuntimeError.NotFound; return RuntimeError.NotFound;
} }
} }
fn getBuiltinResult(self: *const Self, builtin: spv.SpvBuiltIn) ?SpvWord {
if (self.mod.builtins.get(builtin)) |result| {
if (self.resultIsInActiveInterface(result))
return result;
}
const entry_point_index = self.active_entry_point orelse return null;
const entry_point = self.mod.entry_points.items[entry_point_index];
for (entry_point.globals) |global| {
if (global >= self.results.len)
continue;
for (self.results[global].decorations.items) |decoration| {
if (decoration.rtype == .BuiltIn and decoration.literal_1 == @intFromEnum(builtin))
return global;
}
}
return null;
}
pub fn flushDescriptorSets(self: *const Self, allocator: std.mem.Allocator) RuntimeError!void { pub fn flushDescriptorSets(self: *const Self, allocator: std.mem.Allocator) RuntimeError!void {
for (self.results) |*result| { for (self.results) |*result| {
try result.flushPtr(allocator); try result.flushPtr(allocator);
@@ -902,12 +1197,48 @@ pub fn hasResultDecoration(self: *const Self, result: SpvWord, decoration: spv.S
return false; return false;
} }
pub fn hasResultOrMemberDecoration(self: *const Self, result: SpvWord, decoration: spv.SpvDecoration) bool {
if (self.hasResultDecoration(result, decoration))
return true;
if (result >= self.results.len)
return false;
const type_word = switch ((self.results[result].variant orelse return false)) {
.Variable => |variable| variable.type_word,
else => return false,
};
const target_type_word = switch ((self.results[type_word].variant orelse return false)) {
.Type => |t| switch (t) {
.Pointer => |ptr| ptr.target,
else => type_word,
},
else => return false,
};
const target_type = self.results[target_type_word].variant orelse return false;
switch (target_type) {
.Type => |t| switch (t) {
.Structure => {
for (self.results[target_type_word].decorations.items) |member_decoration| {
if (member_decoration.rtype == decoration)
return true;
}
},
else => {},
},
else => {},
}
return false;
}
pub fn resetInvocation(self: *Self, allocator: std.mem.Allocator) void { pub fn resetInvocation(self: *Self, allocator: std.mem.Allocator) void {
var derivatives = self.derivatives.iterator(); var derivatives = self.derivatives.iterator();
while (derivatives.next()) |entry| { while (derivatives.next()) |entry| {
entry.value_ptr.deinit(allocator); entry.value_ptr.deinit(allocator);
} }
self.derivatives.clearRetainingCapacity(); self.derivatives.clearRetainingCapacity();
self.clearPhiValues(allocator);
for (self.results) |*result| { for (self.results) |*result| {
if (result.variant) |*variant| { if (result.variant) |*variant| {
+90 -33
View File
@@ -264,19 +264,6 @@ pub const Value = union(Type) {
break :blk self; break :blk self;
}, },
.Array => |a| blk: { .Array => |a| blk: {
// If an array is in externally visible storage we treat it as a runtime array
if (is_externally_visible) {
break :blk .{
.RuntimeArray = .{
.type_word = a.components_type_word,
.stride = a.stride,
.data = &.{},
.matrix_stride = null,
.row_major = false,
},
};
}
const self: Self = .{ const self: Self = .{
.Array = .{ .Array = .{
.stride = a.stride, .stride = a.stride,
@@ -389,6 +376,17 @@ pub const Value = union(Type) {
}; };
}, },
}, },
.Pointer => |p| .{
.Pointer = .{
.ptr = p.ptr,
.image_texel = p.image_texel,
.uniform_slice_window = p.uniform_slice_window,
.uniform_backing_value = p.uniform_backing_value,
.owns_uniform_backing_value = false,
.matrix_stride = p.matrix_stride,
.matrix_row_major = p.matrix_row_major,
},
},
else => self.*, else => self.*,
}; };
} }
@@ -479,7 +477,7 @@ pub const Value = union(Type) {
var offset: usize = 0; var offset: usize = 0;
for (arr.values) |v| { for (arr.values) |v| {
_ = try v.read(output[offset..]); _ = try v.read(output[offset..]);
offset += arr.stride; offset += if (arr.stride != 0) arr.stride else try v.getPlainMemorySize();
} }
return offset; return offset;
}, },
@@ -488,10 +486,15 @@ pub const Value = union(Type) {
for (s.values, 0..) |v, i| { for (s.values, 0..) |v, i| {
const member_offset: usize = @intCast(s.offsets[i] orelse end_offset); const member_offset: usize = @intCast(s.offsets[i] orelse end_offset);
if (member_offset > output.len) return RuntimeError.OutOfBounds; if (member_offset > output.len) return RuntimeError.OutOfBounds;
const member_output = if (v == .RuntimeArray and i + 1 < s.values.len and s.offsets[i + 1] != null) blk: {
const next_offset: usize = @intCast(s.offsets[i + 1].?);
if (next_offset < member_offset or next_offset > output.len) return RuntimeError.OutOfBounds;
break :blk output[member_offset..next_offset];
} else output[member_offset..];
const read_size = if (s.matrix_strides[i]) |matrix_stride| const read_size = if (s.matrix_strides[i]) |matrix_stride|
try v.readWithMatrixLayout(output[member_offset..], matrix_stride, s.row_major[i]) try v.readWithMatrixLayout(member_output, matrix_stride, s.row_major[i])
else else
try v.read(output[member_offset..]); try v.read(member_output);
end_offset = @max(end_offset, member_offset + read_size); end_offset = @max(end_offset, member_offset + read_size);
} }
return end_offset; return end_offset;
@@ -624,7 +627,7 @@ pub const Value = union(Type) {
var offset: usize = 0; var offset: usize = 0;
for (arr.values) |*value| { for (arr.values) |*value| {
_ = try value.readWithMatrixLayout(output[offset..], matrix_stride, row_major); _ = try value.readWithMatrixLayout(output[offset..], matrix_stride, row_major);
offset += arr.stride; offset += if (arr.stride != 0) arr.stride else try value.getPlainMemorySize();
} }
break :blk offset; break :blk offset;
}, },
@@ -671,7 +674,7 @@ pub const Value = union(Type) {
var offset: usize = 0; var offset: usize = 0;
for (arr.values) |*value| { for (arr.values) |*value| {
_ = try value.writeWithMatrixLayout(input[offset..], matrix_stride, row_major); _ = try value.writeWithMatrixLayout(input[offset..], matrix_stride, row_major);
offset += arr.stride; offset += if (arr.stride != 0) arr.stride else try value.getPlainMemorySize();
} }
break :blk offset; break :blk offset;
}, },
@@ -771,7 +774,7 @@ pub const Value = union(Type) {
var offset: usize = 0; var offset: usize = 0;
for (arr.values) |*v| { for (arr.values) |*v| {
_ = try v.write(input[offset..]); _ = try v.write(input[offset..]);
offset += arr.stride; offset += if (arr.stride != 0) arr.stride else try v.getPlainMemorySize();
} }
return offset; return offset;
}, },
@@ -780,17 +783,25 @@ pub const Value = union(Type) {
for (s.values, 0..) |*v, i| { for (s.values, 0..) |*v, i| {
const member_offset: usize = @intCast(s.offsets[i] orelse end_offset); const member_offset: usize = @intCast(s.offsets[i] orelse end_offset);
if (member_offset > input.len) return RuntimeError.OutOfBounds; if (member_offset > input.len) return RuntimeError.OutOfBounds;
const member_input = if (v.* == .RuntimeArray and i + 1 < s.values.len and s.offsets[i + 1] != null) blk: {
const next_offset: usize = @intCast(s.offsets[i + 1].?);
if (next_offset < member_offset or next_offset > input.len) return RuntimeError.OutOfBounds;
break :blk input[member_offset..next_offset];
} else input[member_offset..];
const write_size = if (s.matrix_strides[i]) |matrix_stride| const write_size = if (s.matrix_strides[i]) |matrix_stride|
try v.writeWithMatrixLayout(input[member_offset..], matrix_stride, s.row_major[i]) try v.writeWithMatrixLayout(member_input, matrix_stride, s.row_major[i])
else else
try v.write(input[member_offset..]); try v.write(member_input);
end_offset = @max(end_offset, member_offset + write_size); end_offset = @max(end_offset, member_offset + write_size);
} }
if (end_offset > input.len) return RuntimeError.OutOfBounds; if (end_offset > input.len) return RuntimeError.OutOfBounds;
s.external_data = @constCast(input[0..end_offset]); s.external_data = @constCast(input[0..end_offset]);
return end_offset; return end_offset;
}, },
.RuntimeArray => |*arr| arr.data = @constCast(input[0..]), .RuntimeArray => |*arr| {
arr.data = @constCast(input[0..]);
return input.len;
},
.Image => |*img| { .Image => |*img| {
if (input.len < @sizeOf(usize)) return RuntimeError.OutOfBounds; if (input.len < @sizeOf(usize)) return RuntimeError.OutOfBounds;
img.driver_image = @ptrFromInt(std.mem.bytesToValue(usize, input[0..@sizeOf(usize)])); img.driver_image = @ptrFromInt(std.mem.bytesToValue(usize, input[0..@sizeOf(usize)]));
@@ -840,7 +851,13 @@ pub const Value = union(Type) {
} }
break :blk size; break :blk size;
}, },
.Array => |arr| arr.stride * arr.values.len, .Array => |arr| blk: {
var size: usize = 0;
for (arr.values) |v| {
size += if (arr.stride != 0) arr.stride else try v.getPlainMemorySize();
}
break :blk size;
},
.Structure => |s| blk: { .Structure => |s| blk: {
var size: usize = 0; var size: usize = 0;
for (s.values, 0..) |v, i| { for (s.values, 0..) |v, i| {
@@ -959,14 +976,54 @@ pub const Value = union(Type) {
} }
} }
fn readScalarLane(comptime T: PrimitiveType, comptime bits: u32, v: *const Value) RuntimeError!getPrimitiveFieldType(T, bits) {
const TT = getPrimitiveFieldType(T, bits);
return switch (v.*) {
.Bool => |b| blk: {
if (T != .Bool or bits != 8) return RuntimeError.InvalidSpirV;
break :blk @as(TT, b);
},
.Int => |i| blk: {
if (i.bit_count != bits) return RuntimeError.InvalidSpirV;
break :blk switch (T) {
.SInt => switch (bits) {
8 => @as(TT, if (i.is_signed) i.value.sint8 else @bitCast(i.value.uint8)),
16 => @as(TT, if (i.is_signed) i.value.sint16 else @bitCast(i.value.uint16)),
32 => @as(TT, if (i.is_signed) i.value.sint32 else @bitCast(i.value.uint32)),
64 => @as(TT, if (i.is_signed) i.value.sint64 else @bitCast(i.value.uint64)),
else => return RuntimeError.InvalidSpirV,
},
.UInt => switch (bits) {
8 => @as(TT, if (i.is_signed) @bitCast(i.value.sint8) else i.value.uint8),
16 => @as(TT, if (i.is_signed) @bitCast(i.value.sint16) else i.value.uint16),
32 => @as(TT, if (i.is_signed) @bitCast(i.value.sint32) else i.value.uint32),
64 => @as(TT, if (i.is_signed) @bitCast(i.value.sint64) else i.value.uint64),
else => return RuntimeError.InvalidSpirV,
},
else => return RuntimeError.InvalidSpirV,
};
},
.Float => |f| blk: {
if (T != .Float or f.bit_count != bits) return RuntimeError.InvalidSpirV;
break :blk switch (bits) {
16 => @as(TT, f.value.float16),
32 => @as(TT, f.value.float32),
64 => @as(TT, f.value.float64),
else => return RuntimeError.InvalidSpirV,
};
},
else => return RuntimeError.InvalidSpirV,
};
}
pub inline fn readLane(comptime T: PrimitiveType, comptime bits: u32, v: *const Value, lane_index: usize) RuntimeError!getPrimitiveFieldType(T, bits) { pub inline fn readLane(comptime T: PrimitiveType, comptime bits: u32, v: *const Value, lane_index: usize) RuntimeError!getPrimitiveFieldType(T, bits) {
const TT = getPrimitiveFieldType(T, bits); const TT = getPrimitiveFieldType(T, bits);
return switch (v.*) { return switch (v.*) {
.Int => (try getPrimitiveField(T, bits, @constCast(v))).*, .Bool, .Int, .Float => try readScalarLane(T, bits, v),
.Float => (try getPrimitiveField(T, bits, @constCast(v))).*,
.Vector => |lanes| (try getPrimitiveField(T, bits, &lanes[lane_index])).*, .Vector => |lanes| try readScalarLane(T, bits, &lanes[lane_index]),
.Vector2f32 => |*vec| switch (lane_index) { .Vector2f32 => |*vec| switch (lane_index) {
inline 0...1 => |i| blk: { inline 0...1 => |i| blk: {
@@ -1001,7 +1058,7 @@ pub const Value = union(Type) {
.Vector2i32 => |*vec| switch (lane_index) { .Vector2i32 => |*vec| switch (lane_index) {
inline 0...1 => |i| blk: { inline 0...1 => |i| blk: {
if (bits == 32) { if (comptime (T == .SInt or T == .UInt) and bits == 32) {
break :blk @as(TT, @bitCast(vec[i])); break :blk @as(TT, @bitCast(vec[i]));
} else { } else {
return RuntimeError.InvalidSpirV; return RuntimeError.InvalidSpirV;
@@ -1011,7 +1068,7 @@ pub const Value = union(Type) {
}, },
.Vector3i32 => |*vec| switch (lane_index) { .Vector3i32 => |*vec| switch (lane_index) {
inline 0...2 => |i| blk: { inline 0...2 => |i| blk: {
if (bits == 32) { if (comptime (T == .SInt or T == .UInt) and bits == 32) {
break :blk @as(TT, @bitCast(vec[i])); break :blk @as(TT, @bitCast(vec[i]));
} else { } else {
return RuntimeError.InvalidSpirV; return RuntimeError.InvalidSpirV;
@@ -1021,7 +1078,7 @@ pub const Value = union(Type) {
}, },
.Vector4i32 => |*vec| switch (lane_index) { .Vector4i32 => |*vec| switch (lane_index) {
inline 0...3 => |i| blk: { inline 0...3 => |i| blk: {
if (bits == 32) { if (comptime (T == .SInt or T == .UInt) and bits == 32) {
break :blk @as(TT, @bitCast(vec[i])); break :blk @as(TT, @bitCast(vec[i]));
} else { } else {
return RuntimeError.InvalidSpirV; return RuntimeError.InvalidSpirV;
@@ -1032,7 +1089,7 @@ pub const Value = union(Type) {
.Vector2u32 => |*vec| switch (lane_index) { .Vector2u32 => |*vec| switch (lane_index) {
inline 0...1 => |i| blk: { inline 0...1 => |i| blk: {
if (bits == 32) { if (comptime (T == .SInt or T == .UInt) and bits == 32) {
break :blk @as(TT, @bitCast(vec[i])); break :blk @as(TT, @bitCast(vec[i]));
} else { } else {
return RuntimeError.InvalidSpirV; return RuntimeError.InvalidSpirV;
@@ -1042,7 +1099,7 @@ pub const Value = union(Type) {
}, },
.Vector3u32 => |*vec| switch (lane_index) { .Vector3u32 => |*vec| switch (lane_index) {
inline 0...2 => |i| blk: { inline 0...2 => |i| blk: {
if (bits == 32) { if (comptime (T == .SInt or T == .UInt) and bits == 32) {
break :blk @as(TT, @bitCast(vec[i])); break :blk @as(TT, @bitCast(vec[i]));
} else { } else {
return RuntimeError.InvalidSpirV; return RuntimeError.InvalidSpirV;
@@ -1052,7 +1109,7 @@ pub const Value = union(Type) {
}, },
.Vector4u32 => |*vec| switch (lane_index) { .Vector4u32 => |*vec| switch (lane_index) {
inline 0...3 => |i| blk: { inline 0...3 => |i| blk: {
if (bits == 32) { if (comptime (T == .SInt or T == .UInt) and bits == 32) {
break :blk @as(TT, @bitCast(vec[i])); break :blk @as(TT, @bitCast(vec[i]));
} else { } else {
return RuntimeError.InvalidSpirV; return RuntimeError.InvalidSpirV;
@@ -1185,7 +1242,7 @@ pub const Value = union(Type) {
else => unreachable, else => unreachable,
}, },
} }, } },
else => return RuntimeError.InvalidSpirV, .Bool => .{ .Bool = v },
}; };
}, },
else => return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV,
+372 -134
View File
@@ -114,21 +114,22 @@ pub const SetupDispatcher = block: {
.AccessChain = setupAccessChain, .AccessChain = setupAccessChain,
.All = autoSetupConstant, .All = autoSetupConstant,
.Any = autoSetupConstant, .Any = autoSetupConstant,
.AtomicAnd = autoSetupConstant, .ArrayLength = autoSetupConstant,
.AtomicCompareExchange = autoSetupConstant, .AtomicAnd = setupAtomic,
.AtomicExchange = autoSetupConstant, .AtomicCompareExchange = setupAtomic,
.AtomicIAdd = autoSetupConstant, .AtomicExchange = setupAtomic,
.AtomicIDecrement = autoSetupConstant, .AtomicIAdd = setupAtomic,
.AtomicIIncrement = autoSetupConstant, .AtomicIDecrement = setupAtomic,
.AtomicISub = autoSetupConstant, .AtomicIIncrement = setupAtomic,
.AtomicLoad = autoSetupConstant, .AtomicISub = setupAtomic,
.AtomicOr = autoSetupConstant, .AtomicLoad = setupAtomic,
.AtomicSMax = autoSetupConstant, .AtomicOr = setupAtomic,
.AtomicSMin = autoSetupConstant, .AtomicSMax = setupAtomic,
.AtomicStore = autoSetupConstant, .AtomicSMin = setupAtomic,
.AtomicUMax = autoSetupConstant, .AtomicStore = setupAtomicStore,
.AtomicUMin = autoSetupConstant, .AtomicUMax = setupAtomic,
.AtomicXor = autoSetupConstant, .AtomicUMin = setupAtomic,
.AtomicXor = setupAtomic,
.BitCount = autoSetupConstant, .BitCount = autoSetupConstant,
.BitFieldInsert = autoSetupConstant, .BitFieldInsert = autoSetupConstant,
.BitFieldSExtract = autoSetupConstant, .BitFieldSExtract = autoSetupConstant,
@@ -144,14 +145,16 @@ pub const SetupDispatcher = block: {
.Constant = opConstant, .Constant = opConstant,
.ConstantComposite = opConstantComposite, .ConstantComposite = opConstantComposite,
.ConstantFalse = opConstantFalse, .ConstantFalse = opConstantFalse,
.ConstantNull = opConstantNull,
.ConstantTrue = opConstantTrue, .ConstantTrue = opConstantTrue,
.ControlBarrier = opControlBarrier, .ControlBarrier = opControlBarrierSetup,
.ConvertFToS = autoSetupConstant, .ConvertFToS = autoSetupConstant,
.ConvertFToU = autoSetupConstant, .ConvertFToU = autoSetupConstant,
.ConvertPtrToU = autoSetupConstant, .ConvertPtrToU = autoSetupConstant,
.ConvertSToF = autoSetupConstant, .ConvertSToF = autoSetupConstant,
.ConvertUToF = autoSetupConstant, .ConvertUToF = autoSetupConstant,
.ConvertUToPtr = autoSetupConstant, .ConvertUToPtr = autoSetupConstant,
.CopyObject = autoSetupConstant,
.DPdx = opDerivativeSetup, .DPdx = opDerivativeSetup,
.DPdxCoarse = opDerivativeSetup, .DPdxCoarse = opDerivativeSetup,
.DPdxFine = opDerivativeSetup, .DPdxFine = opDerivativeSetup,
@@ -321,6 +324,7 @@ pub fn initRuntimeDispatcher() void {
runtime_dispatcher[@intFromEnum(spv.SpvOp.AtomicUMax)] = AtomicEngine(.MaxUnsigned).op; runtime_dispatcher[@intFromEnum(spv.SpvOp.AtomicUMax)] = AtomicEngine(.MaxUnsigned).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.AtomicUMin)] = AtomicEngine(.MinUnsigned).op; runtime_dispatcher[@intFromEnum(spv.SpvOp.AtomicUMin)] = AtomicEngine(.MinUnsigned).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.AtomicXor)] = AtomicEngine(.Xor).op; runtime_dispatcher[@intFromEnum(spv.SpvOp.AtomicXor)] = AtomicEngine(.Xor).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.ArrayLength)] = opArrayLength;
runtime_dispatcher[@intFromEnum(spv.SpvOp.BitCount)] = BitEngine(.UInt, .BitCount).op; 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.BitFieldInsert)] = BitEngine(.UInt, .BitFieldInsert).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.BitFieldSExtract)] = BitEngine(.SInt, .BitFieldSExtract).op; runtime_dispatcher[@intFromEnum(spv.SpvOp.BitFieldSExtract)] = BitEngine(.SInt, .BitFieldSExtract).op;
@@ -335,11 +339,13 @@ pub fn initRuntimeDispatcher() void {
runtime_dispatcher[@intFromEnum(spv.SpvOp.CompositeConstruct)] = opCompositeConstruct; runtime_dispatcher[@intFromEnum(spv.SpvOp.CompositeConstruct)] = opCompositeConstruct;
runtime_dispatcher[@intFromEnum(spv.SpvOp.CompositeExtract)] = opCompositeExtract; runtime_dispatcher[@intFromEnum(spv.SpvOp.CompositeExtract)] = opCompositeExtract;
runtime_dispatcher[@intFromEnum(spv.SpvOp.CompositeInsert)] = opCompositeInsert; runtime_dispatcher[@intFromEnum(spv.SpvOp.CompositeInsert)] = opCompositeInsert;
runtime_dispatcher[@intFromEnum(spv.SpvOp.ConstantNull)] = opConstantNull;
runtime_dispatcher[@intFromEnum(spv.SpvOp.ControlBarrier)] = opControlBarrier; runtime_dispatcher[@intFromEnum(spv.SpvOp.ControlBarrier)] = opControlBarrier;
runtime_dispatcher[@intFromEnum(spv.SpvOp.ConvertFToS)] = ConversionEngine(.Float, .SInt).op; 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.ConvertFToU)] = ConversionEngine(.Float, .UInt).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.ConvertSToF)] = ConversionEngine(.SInt, .Float).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.ConvertUToF)] = ConversionEngine(.UInt, .Float).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.CopyObject)] = opCopyObject;
runtime_dispatcher[@intFromEnum(spv.SpvOp.CopyMemory)] = opCopyMemory; runtime_dispatcher[@intFromEnum(spv.SpvOp.CopyMemory)] = opCopyMemory;
runtime_dispatcher[@intFromEnum(spv.SpvOp.DPdx)] = DerivativeEngine(.x).op; runtime_dispatcher[@intFromEnum(spv.SpvOp.DPdx)] = DerivativeEngine(.x).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.DPdxCoarse)] = DerivativeEngine(.x).op; runtime_dispatcher[@intFromEnum(spv.SpvOp.DPdxCoarse)] = DerivativeEngine(.x).op;
@@ -580,15 +586,17 @@ fn BitEngine(comptime T: PrimitiveType, comptime Op: BitOp) type {
const bits: u32 = info.int.bits; const bits: u32 = info.int.bits;
const U = std.meta.Int(.unsigned, bits); const U = std.meta.Int(.unsigned, bits);
if (count == 0) return base; if (count == 0 or offset >= bits) return base;
const actual_count: u64 = @min(count, @as(u64, bits) - offset);
const base_u: U = @bitCast(base); const base_u: U = @bitCast(base);
const insert_u: U = @bitCast(insert); const insert_u: U = @bitCast(insert);
const field_mask: U = if (count == bits) const field_mask: U = if (actual_count == bits)
~@as(U, 0) ~@as(U, 0)
else else
(@as(U, 1) << @intCast(count)) - 1; (@as(U, 1) << @intCast(actual_count)) - 1;
const shift: std.math.Log2Int(U) = @truncate(offset); const shift: std.math.Log2Int(U) = @truncate(offset);
@@ -604,23 +612,26 @@ fn BitEngine(comptime T: PrimitiveType, comptime Op: BitOp) type {
const bits: u32 = info.int.bits; const bits: u32 = info.int.bits;
if (count == 0) return @as(TT, 0); if (count == 0 or offset >= bits) return @as(TT, 0);
const actual_count: u64 = @min(count, @as(u64, bits) - offset);
const U = std.meta.Int(.unsigned, bits); const U = std.meta.Int(.unsigned, bits);
const base_u: U = @bitCast(base); const base_u: U = @bitCast(base);
const shift: std.math.Log2Int(U) = @truncate(offset);
const field: U = if (count == bits) const field: U = if (actual_count == bits)
base_u base_u
else else
(base_u >> @intCast(offset)) & (base_u >> shift) &
((@as(U, 1) << @intCast(count)) - 1); ((@as(U, 1) << @intCast(actual_count)) - 1);
const result: U = if (!signed_result or count == bits) blk: { const result: U = if (!signed_result or actual_count == bits) blk: {
break :blk field; break :blk field;
} else blk: { } else blk: {
const sign_bit: U = @as(U, 1) << @intCast(count - 1); const sign_bit: U = @as(U, 1) << @intCast(actual_count - 1);
if ((field & sign_bit) != 0) { if ((field & sign_bit) != 0) {
break :blk field | (~@as(U, 0) << @intCast(count)); break :blk field | (~@as(U, 0) << @intCast(actual_count));
} }
break :blk field; break :blk field;
}; };
@@ -647,34 +658,57 @@ fn BitEngine(comptime T: PrimitiveType, comptime Op: BitOp) type {
.BitwiseAnd => op1 & op2, .BitwiseAnd => op1 & op2,
.BitwiseOr => op1 | op2, .BitwiseOr => op1 | op2,
.BitwiseXor => op1 ^ op2, .BitwiseXor => op1 ^ op2,
.ShiftLeft => op1 << @intCast(op2),
.ShiftRight, .ShiftRightArithmetic => op1 >> @intCast(op2),
else => RuntimeError.InvalidSpirV, else => RuntimeError.InvalidSpirV,
}; };
} }
inline fn operationTernary(comptime TT: type, op1: TT, op2: TT, op3: *const Value) RuntimeError!TT { inline fn operationShift(comptime TT: type, op1: TT, amount: u64) RuntimeError!TT {
if (amount >= @bitSizeOf(TT)) return @as(TT, 0);
const shift: std.math.Log2Int(TT) = @intCast(amount);
return switch (Op) {
.ShiftLeft => op1 << shift,
.ShiftRight, .ShiftRightArithmetic => op1 >> shift,
else => RuntimeError.InvalidSpirV,
};
}
inline fn operationTernary(comptime TT: type, op1: TT, op2: u64, op3: u64) RuntimeError!TT {
return switch (Op) { return switch (Op) {
.BitFieldSExtract => blk: { .BitFieldSExtract => blk: {
if (T != .SInt) return RuntimeError.InvalidSpirV; if (T != .SInt) return RuntimeError.InvalidSpirV;
break :blk bitExtract(TT, true, op1, @intCast(op2), op3.Int.value.uint64); break :blk bitExtract(TT, true, op1, op2, op3);
}, },
.BitFieldUExtract => blk: { .BitFieldUExtract => blk: {
if (T != .UInt) return RuntimeError.InvalidSpirV; if (T != .UInt) return RuntimeError.InvalidSpirV;
break :blk bitExtract(TT, false, op1, @intCast(op2), op3.Int.value.uint64); break :blk bitExtract(TT, false, op1, op2, op3);
}, },
else => RuntimeError.InvalidSpirV, else => RuntimeError.InvalidSpirV,
}; };
} }
inline fn operationQuaternary(comptime TT: type, op1: TT, op2: TT, op3: *const Value, op4: *const Value) RuntimeError!TT { inline fn operationQuaternary(comptime TT: type, op1: TT, op2: TT, op3: u64, op4: u64) RuntimeError!TT {
return switch (Op) { return switch (Op) {
.BitFieldInsert => bitInsert(TT, op1, op2, op3.Int.value.uint64, op4.Int.value.uint64), .BitFieldInsert => bitInsert(TT, op1, op2, op3, op4),
else => RuntimeError.InvalidSpirV, else => RuntimeError.InvalidSpirV,
}; };
} }
fn readIntegerLaneAsU64(value: *const Value, lane_index: usize) RuntimeError!u64 {
const lane_bits = try value.resolveLaneBitWidth();
const sign = try value.resolveSign();
return switch (lane_bits) {
inline 8, 16, 32, 64 => |bits| blk: {
if (sign == .signed) {
const lane = try Value.readLane(.SInt, bits, value, lane_index);
break :blk std.math.cast(u64, lane) orelse return RuntimeError.OutOfBounds;
}
break :blk @intCast(try Value.readLane(.UInt, bits, value, lane_index));
},
else => return RuntimeError.InvalidSpirV,
};
}
fn applyScalarBits(bit_count: SpvWord, dst: *Value, ops: [max_operator_count]?*const Value) RuntimeError!void { fn applyScalarBits(bit_count: SpvWord, dst: *Value, ops: [max_operator_count]?*const Value) RuntimeError!void {
switch (bit_count) { switch (bit_count) {
inline 8, 16, 32, 64 => |bits| { inline 8, 16, 32, 64 => |bits| {
@@ -685,11 +719,25 @@ fn BitEngine(comptime T: PrimitiveType, comptime Op: BitOp) type {
if (comptime isUnaryOp()) break :blk try operationUnary(TT, a); if (comptime isUnaryOp()) break :blk try operationUnary(TT, a);
if (comptime isBinaryOp()) {
if (comptime Op == .ShiftLeft or Op == .ShiftRight or Op == .ShiftRightArithmetic) {
const amount = try readIntegerLaneAsU64(ops[1].?, 0);
break :blk try operationShift(TT, a, amount);
}
const b = try Value.readLane(T, bits, ops[1].?, 0); const b = try Value.readLane(T, bits, ops[1].?, 0);
break :blk try operationBinary(TT, a, b);
if (comptime isBinaryOp()) break :blk try operationBinary(TT, a, b); }
if (comptime isTernaryOp()) break :blk try operationTernary(TT, a, b, ops[2].?); if (comptime isTernaryOp()) {
if (comptime isQuaternaryOp()) break :blk try operationQuaternary(TT, a, b, ops[2].?, ops[3].?); const offset = try readIntegerLaneAsU64(ops[1].?, 0);
const count = try readIntegerLaneAsU64(ops[2].?, 0);
break :blk try operationTernary(TT, a, offset, count);
}
if (comptime isQuaternaryOp()) {
const b = try Value.readLane(T, bits, ops[1].?, 0);
const offset = try readIntegerLaneAsU64(ops[2].?, 0);
const count = try readIntegerLaneAsU64(ops[3].?, 0);
break :blk try operationQuaternary(TT, a, b, offset, count);
}
}; };
try Value.writeLane(T, bits, dst, 0, out); try Value.writeLane(T, bits, dst, 0, out);
@@ -711,11 +759,25 @@ fn BitEngine(comptime T: PrimitiveType, comptime Op: BitOp) type {
if (comptime isUnaryOp()) break :blk try operationUnary(TT, a); if (comptime isUnaryOp()) break :blk try operationUnary(TT, a);
if (comptime isBinaryOp()) {
if (comptime Op == .ShiftLeft or Op == .ShiftRight or Op == .ShiftRightArithmetic) {
const amount = try readIntegerLaneAsU64(ops[1].?, if (ops[1].?.isVector()) i else 0);
break :blk try operationShift(TT, a, amount);
}
const b = try Value.readLane(T, bits, ops[1].?, if (ops[1].?.isVector()) i else 0); const b = try Value.readLane(T, bits, ops[1].?, if (ops[1].?.isVector()) i else 0);
break :blk try operationBinary(TT, a, b);
if (comptime isBinaryOp()) break :blk try operationBinary(TT, a, b); }
if (comptime isTernaryOp()) break :blk try operationTernary(TT, a, b, ops[2].?); if (comptime isTernaryOp()) {
if (comptime isQuaternaryOp()) break :blk try operationQuaternary(TT, a, b, ops[2].?, ops[3].?); const offset = try readIntegerLaneAsU64(ops[1].?, if (ops[1].?.isVector()) i else 0);
const count = try readIntegerLaneAsU64(ops[2].?, if (ops[2].?.isVector()) i else 0);
break :blk try operationTernary(TT, a, offset, count);
}
if (comptime isQuaternaryOp()) {
const b = try Value.readLane(T, bits, ops[1].?, if (ops[1].?.isVector()) i else 0);
const offset = try readIntegerLaneAsU64(ops[2].?, if (ops[2].?.isVector()) i else 0);
const count = try readIntegerLaneAsU64(ops[3].?, if (ops[3].?.isVector()) i else 0);
break :blk try operationQuaternary(TT, a, b, offset, count);
}
}; };
try Value.writeLane(T, bits, dst, i, out); try Value.writeLane(T, bits, dst, i, out);
@@ -809,18 +871,36 @@ fn CondEngine(comptime T: PrimitiveType, comptime Op: CondOp) type {
} }
fn applyScalarBits(bit_count: SpvWord, dst_bool: *Value, a_v: *const Value, b_v: ?*const Value) RuntimeError!void { fn applyScalarBits(bit_count: SpvWord, dst_bool: *Value, a_v: *const Value, b_v: ?*const Value) RuntimeError!void {
if (comptime T == .Bool) {
const a = switch (a_v.*) {
.Bool => |value| value,
else => return RuntimeError.InvalidSpirV,
};
if (comptime isUnaryOp()) {
dst_bool.Bool = try operationUnary(bool, a);
} else {
const b_ptr = b_v orelse return RuntimeError.InvalidSpirV;
const b = switch (b_ptr.*) {
.Bool => |value| value,
else => return RuntimeError.InvalidSpirV,
};
dst_bool.Bool = try operationBinary(bool, a, b);
}
return;
}
switch (bit_count) { switch (bit_count) {
inline 8, 16, 32, 64 => |bits| { inline 8, 16, 32, 64 => |bits| {
if (bits == 8 and T == .Float) return RuntimeError.InvalidSpirV; if (bits == 8 and T == .Float) return RuntimeError.InvalidSpirV;
const TT = Value.getPrimitiveFieldType(T, bits); const TT = Value.getPrimitiveFieldType(T, bits);
const a = (try Value.getPrimitiveField(T, bits, @constCast(a_v))).*; const a = try Value.readLane(T, bits, a_v, 0);
if (comptime isUnaryOp()) { if (comptime isUnaryOp()) {
dst_bool.Bool = try operationUnary(TT, a); dst_bool.Bool = try operationUnary(TT, a);
} else { } else {
const b_ptr = b_v orelse return RuntimeError.InvalidSpirV; const b_ptr = b_v orelse return RuntimeError.InvalidSpirV;
const b = (try Value.getPrimitiveField(T, bits, @constCast(b_ptr))).*; const b = try Value.readLane(T, bits, b_ptr, 0);
dst_bool.Bool = try operationBinary(TT, a, b); dst_bool.Bool = try operationBinary(TT, a, b);
} }
}, },
@@ -912,57 +992,20 @@ fn CondEngine(comptime T: PrimitiveType, comptime Op: CondOp) type {
.Bool => try applyScalarBits(lane_bits, dst, op1_value, op2_value), .Bool => try applyScalarBits(lane_bits, dst, op1_value, op2_value),
.Vector => |dst_vec| { .Vector => |dst_vec| {
switch (op1_value.*) { switch (lane_bits) {
.Vector => |op1_vec| for (dst_vec, op1_vec, 0..) |*d_lane, a_lane, i| { inline 8, 16, 32, 64 => |bits| {
const b_ptr = laneRhsPtr(op2_value, i); if (bits == 8 and T == .Float) return RuntimeError.InvalidSpirV;
try applyScalarBits(lane_bits, d_lane, &a_lane, b_ptr); const TT = Value.getPrimitiveFieldType(T, bits);
for (dst_vec, 0..) |*d_lane, i| {
const a = try Value.readLane(T, bits, op1_value, i);
d_lane.Bool = if (comptime isUnaryOp()) blk: {
break :blk try operationUnary(TT, a);
} else blk: {
const b = try Value.readLane(T, bits, op2_value.?, i);
break :blk try operationBinary(TT, a, b);
};
}
}, },
.Vector4f32 => |*op1_vec| if (comptime isUnaryOp())
try applyFixedVectorUnary(f32, 4, dst_vec, op1_vec)
else
try applyFixedVectorBinary(f32, 4, dst_vec, op1_vec, op2_value.?),
.Vector3f32 => |*op1_vec| if (comptime isUnaryOp())
try applyFixedVectorUnary(f32, 3, dst_vec, op1_vec)
else
try applyFixedVectorBinary(f32, 3, dst_vec, op1_vec, op2_value.?),
.Vector2f32 => |*op1_vec| if (comptime isUnaryOp())
try applyFixedVectorUnary(f32, 2, dst_vec, op1_vec)
else
try applyFixedVectorBinary(f32, 2, dst_vec, op1_vec, op2_value.?),
.Vector4i32 => |*op1_vec| if (comptime isUnaryOp())
try applyFixedVectorUnary(i32, 4, dst_vec, op1_vec)
else
try applyFixedVectorBinary(i32, 4, dst_vec, op1_vec, op2_value.?),
.Vector3i32 => |*op1_vec| if (comptime isUnaryOp())
try applyFixedVectorUnary(i32, 3, dst_vec, op1_vec)
else
try applyFixedVectorBinary(i32, 3, dst_vec, op1_vec, op2_value.?),
.Vector2i32 => |*op1_vec| if (comptime isUnaryOp())
try applyFixedVectorUnary(i32, 2, dst_vec, op1_vec)
else
try applyFixedVectorBinary(i32, 2, dst_vec, op1_vec, op2_value.?),
.Vector4u32 => |*op1_vec| if (comptime isUnaryOp())
try applyFixedVectorUnary(u32, 4, dst_vec, op1_vec)
else
try applyFixedVectorBinary(u32, 4, dst_vec, op1_vec, op2_value.?),
.Vector3u32 => |*op1_vec| if (comptime isUnaryOp())
try applyFixedVectorUnary(u32, 3, dst_vec, op1_vec)
else
try applyFixedVectorBinary(u32, 3, dst_vec, op1_vec, op2_value.?),
.Vector2u32 => |*op1_vec| if (comptime isUnaryOp())
try applyFixedVectorUnary(u32, 2, dst_vec, op1_vec)
else
try applyFixedVectorBinary(u32, 2, dst_vec, op1_vec, op2_value.?),
else => return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV,
} }
}, },
@@ -980,6 +1023,20 @@ fn ConversionEngine(comptime from_kind: PrimitiveType, comptime to_kind: Primiti
inline 8, 16, 32, 64 => |bits| blk: { inline 8, 16, 32, 64 => |bits| blk: {
if (bits == 8 and from_kind == .Float) return RuntimeError.InvalidSpirV; // No f8 if (bits == 8 and from_kind == .Float) return RuntimeError.InvalidSpirV; // No f8
const v = try Value.readLane(from_kind, bits, from, lane_index); const v = try Value.readLane(from_kind, bits, from, lane_index);
if (comptime from_kind != .Float and to_kind != .Float) {
const to_bits = @bitSizeOf(ToT);
const FromUInt = std.meta.Int(.unsigned, bits);
const ToUInt = std.meta.Int(.unsigned, to_bits);
const src_bits: FromUInt = @bitCast(v);
const dst_bits: ToUInt = if (to_bits < bits)
@truncate(src_bits)
else if (from_kind == .SInt)
@bitCast(@as(std.meta.Int(.signed, to_bits), @intCast(v)))
else
@intCast(src_bits);
break :blk @bitCast(dst_bits);
}
break :blk std.math.lossyCast(ToT, v); break :blk std.math.lossyCast(ToT, v);
}, },
else => return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV,
@@ -1001,11 +1058,9 @@ fn ConversionEngine(comptime from_kind: PrimitiveType, comptime to_kind: Primiti
const target_type = (try rt.results[try rt.it.next()].getVariant()).Type; const target_type = (try rt.results[try rt.it.next()].getVariant()).Type;
const dst_value = try rt.results[try rt.it.next()].getValue(); const dst_value = try rt.results[try rt.it.next()].getValue();
const src_result = &rt.results[try rt.it.next()]; const src_value = try rt.results[try rt.it.next()].getValue();
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 from_bits = try src_value.resolveLaneBitWidth();
const to_bits = try Result.resolveLaneBitWidth(target_type, rt); const to_bits = try Result.resolveLaneBitWidth(target_type, rt);
const dst_lane_count = try dst_value.resolveLaneCount(); const dst_lane_count = try dst_value.resolveLaneCount();
@@ -1730,6 +1785,10 @@ fn ImageEngine(comptime Op: ImageOp) type {
defer dy.deinit(allocator); defer dy.deinit(allocator);
const result_type = (try rt.results[result_type_word].getVariant()).Type; const result_type = (try rt.results[result_type_word].getVariant()).Type;
if (!try typeHasFloatLanes(rt, result_type)) {
rt.clearDerivative(allocator, result_id);
return;
}
const lane_bits = try Result.resolveLaneBitWidth(result_type, rt); const lane_bits = try Result.resolveLaneBitWidth(result_type, rt);
const lane_count = try Result.resolveLaneCount(result_type); const lane_count = try Result.resolveLaneCount(result_type);
@@ -1749,6 +1808,19 @@ fn ImageEngine(comptime Op: ImageOp) type {
try rt.setDerivative(allocator, result_id, &dx, &dy); try rt.setDerivative(allocator, result_id, &dx, &dy);
} }
fn typeHasFloatLanes(rt: *Runtime, target_type: Result.TypeData) RuntimeError!bool {
return switch (target_type) {
.Float,
.Vector2f32,
.Vector3f32,
.Vector4f32,
=> true,
.Vector => |v| typeHasFloatLanes(rt, (try rt.results[v.components_type_word].getVariant()).Type),
.Matrix => |m| typeHasFloatLanes(rt, (try rt.results[m.column_type_word].getVariant()).Type),
else => false,
};
}
fn sampleImageExplicitLod(rt: *Runtime, dst: *Value, driver_image: *anyopaque, driver_sampler: *anyopaque, dim: spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32, offset: Runtime.ImageOffset) RuntimeError!void { fn sampleImageExplicitLod(rt: *Runtime, dst: *Value, driver_image: *anyopaque, driver_sampler: *anyopaque, dim: spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32, offset: Runtime.ImageOffset) RuntimeError!void {
switch (dst.*) { switch (dst.*) {
.Vector4f32, .Vector4f32,
@@ -3078,6 +3150,44 @@ fn autoSetupConstant(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) Run
_ = try setupConstant(allocator, rt); _ = try setupConstant(allocator, rt);
} }
fn setupAtomic(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void {
rt.mod.reflection_infos.has_atomics = true;
try autoSetupConstant(allocator, word_count, rt);
}
fn setupAtomicStore(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
rt.mod.reflection_infos.has_atomics = true;
}
fn opArrayLength(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
_ = try rt.it.next(); // result type
const id = try rt.it.next();
const structure = try rt.results[try rt.it.next()].getValue();
const member_index = try rt.it.next();
const structure_value = switch (structure.*) {
.Pointer => |p| switch (p.ptr) {
.common => |value| value,
else => return RuntimeError.InvalidValueType,
},
else => structure,
};
const length: u32 = switch (structure_value.*) {
.Structure => |s| blk: {
if (member_index >= s.values.len) return RuntimeError.OutOfBounds;
break :blk switch (s.values[member_index]) {
.RuntimeArray => |arr| @as(u32, @intCast(arr.getLen())),
else => return RuntimeError.InvalidValueType,
};
},
.RuntimeArray => |arr| @intCast(arr.getLen()),
else => return RuntimeError.InvalidValueType,
};
try Value.writeLane(.UInt, 32, try rt.results[id].getValue(), 0, length);
}
fn setupAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { fn setupAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void {
const var_type = try rt.it.next(); const var_type = try rt.it.next();
const id = try rt.it.next(); const id = try rt.it.next();
@@ -3264,26 +3374,53 @@ fn copyValue(dst: *Value, src: *const Value) RuntimeError!void {
switch (src.*) { switch (src.*) {
.Vector, .Matrix => |src_slice| { .Vector, .Matrix => |src_slice| {
const dst_slice = helpers.getDstSlice(dst); if (dst.* == .RuntimeArray) {
try helpers.copySlice(dst_slice.?, src_slice); const size = try src.getPlainMemorySize();
if (size > dst.RuntimeArray.data.len) return RuntimeError.OutOfBounds;
_ = try src.read(dst.RuntimeArray.data[0..size]);
return;
}
const dst_slice = helpers.getDstSlice(dst) orelse return RuntimeError.InvalidSpirV;
try helpers.copySlice(dst_slice, src_slice);
}, },
.Array => |a| { .Array => |a| {
const dst_slice = helpers.getDstSlice(dst); if (dst.* == .RuntimeArray) {
try helpers.copySlice(dst_slice.?, a.values); const size = try src.getPlainMemorySize();
if (size > dst.RuntimeArray.data.len) return RuntimeError.OutOfBounds;
_ = try src.read(dst.RuntimeArray.data[0..size]);
return;
}
const dst_slice = helpers.getDstSlice(dst) orelse return RuntimeError.InvalidSpirV;
try helpers.copySlice(dst_slice, a.values);
}, },
.Structure => |s| { .Structure => |s| {
if (s.external_data) |src_data| {
if (dst.* == .Structure) {
if (dst.Structure.external_data) |dst_data| {
if (src_data.len > dst_data.len) return RuntimeError.OutOfBounds;
@memcpy(dst_data[0..src_data.len], src_data);
_ = try dst.write(dst_data[0..src_data.len]);
return;
}
}
}
if (dst.* == .RuntimeArray) {
const size = try src.getPlainMemorySize();
if (size > dst.RuntimeArray.data.len) return RuntimeError.OutOfBounds;
_ = try src.read(dst.RuntimeArray.data[0..size]);
return;
}
if (dst.* == .Structure) { if (dst.* == .Structure) {
@memcpy(@constCast(dst.Structure.offsets), s.offsets); @memcpy(@constCast(dst.Structure.offsets), s.offsets);
@memcpy(@constCast(dst.Structure.matrix_strides), s.matrix_strides); @memcpy(@constCast(dst.Structure.matrix_strides), s.matrix_strides);
@memcpy(@constCast(dst.Structure.row_major), s.row_major); @memcpy(@constCast(dst.Structure.row_major), s.row_major);
} }
const dst_slice = helpers.getDstSlice(dst); const dst_slice = helpers.getDstSlice(dst) orelse return RuntimeError.InvalidSpirV;
try helpers.copySlice(dst_slice.?, s.values); try helpers.copySlice(dst_slice, s.values);
}, },
.Pointer => |ptr| switch (ptr.ptr) { .Pointer => |ptr| switch (ptr.ptr) {
.common => |src_val_ptr| { .common => |src_val_ptr| {
if (ptr.uniform_slice_window) |window| { if (ptr.uniform_slice_window) |window| {
try copyValue(dst, src_val_ptr);
try helpers.readWindow(dst, window, ptr.matrix_stride, ptr.matrix_row_major); try helpers.readWindow(dst, window, ptr.matrix_stride, ptr.matrix_row_major);
} else { } else {
try copyValue(dst, src_val_ptr); try copyValue(dst, src_val_ptr);
@@ -3312,6 +3449,11 @@ fn copyValue(dst: *Value, src: *const Value) RuntimeError!void {
}, },
}, },
.RuntimeArray => |src_arr| switch (dst.*) { .RuntimeArray => |src_arr| switch (dst.*) {
.Array => {
const size = try dst.getPlainMemorySize();
if (size > src_arr.data.len) return RuntimeError.OutOfBounds;
_ = try dst.write(src_arr.data[0..size]);
},
.RuntimeArray => |dst_arr| @memcpy(dst_arr.data, src_arr.data), .RuntimeArray => |dst_arr| @memcpy(dst_arr.data, src_arr.data),
.Pointer => |dst_ptr| switch (dst_ptr.ptr) { .Pointer => |dst_ptr| switch (dst_ptr.ptr) {
.common => |dst_ptr_ptr| switch (dst_ptr_ptr.*) { .common => |dst_ptr_ptr| switch (dst_ptr_ptr.*) {
@@ -3326,6 +3468,16 @@ fn copyValue(dst: *Value, src: *const Value) RuntimeError!void {
} }
} }
fn intValueToIndex(i: @TypeOf(@as(Value, undefined).Int)) RuntimeError!usize {
return switch (i.bit_count) {
8 => if (i.is_signed) std.math.cast(usize, i.value.sint8) orelse RuntimeError.OutOfBounds else @as(usize, i.value.uint8),
16 => if (i.is_signed) std.math.cast(usize, i.value.sint16) orelse RuntimeError.OutOfBounds else @as(usize, i.value.uint16),
32 => if (i.is_signed) std.math.cast(usize, i.value.sint32) orelse RuntimeError.OutOfBounds else @as(usize, i.value.uint32),
64 => if (i.is_signed) std.math.cast(usize, i.value.sint64) orelse RuntimeError.OutOfBounds else std.math.cast(usize, i.value.uint64) orelse RuntimeError.OutOfBounds,
else => RuntimeError.InvalidSpirV,
};
}
fn opAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { fn opAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void {
const var_type = try rt.it.next(); const var_type = try rt.it.next();
const id = try rt.it.next(); const id = try rt.it.next();
@@ -3433,7 +3585,7 @@ fn opAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime
} }
} }
const component_index: usize = @intCast(i.value.uint32); const component_index = try intValueToIndex(i);
switch (value_ptr.*) { switch (value_ptr.*) {
.Vector, .Matrix => |v| { .Vector, .Matrix => |v| {
@@ -3465,7 +3617,8 @@ fn opAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime
} }
}, },
.Array => |a| { .Array => |a| {
if (component_index >= a.values.len) return RuntimeError.OutOfBounds; if (component_index >= a.values.len)
return RuntimeError.OutOfBounds;
uniform_slice_window = try helpers.advanceWindow(uniform_slice_window, component_index * a.stride); uniform_slice_window = try helpers.advanceWindow(uniform_slice_window, component_index * a.stride);
value_ptr = &a.values[component_index]; value_ptr = &a.values[component_index];
switch (value_ptr.*) { switch (value_ptr.*) {
@@ -3494,6 +3647,15 @@ fn opAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime
value_ptr = &s.values[component_index]; value_ptr = &s.values[component_index];
matrix_stride = s.matrix_strides[component_index]; matrix_stride = s.matrix_strides[component_index];
matrix_row_major = s.row_major[component_index]; matrix_row_major = s.row_major[component_index];
if (uniform_slice_window) |window| {
if (value_ptr.* == .RuntimeArray) {
value_ptr.RuntimeArray.data = window;
if (matrix_stride) |stride| {
value_ptr.RuntimeArray.matrix_stride = stride;
value_ptr.RuntimeArray.row_major = matrix_row_major;
}
}
}
}, },
.RuntimeArray => |*arr| { .RuntimeArray => |*arr| {
if (component_index >= arr.getLen()) return RuntimeError.OutOfBounds; if (component_index >= arr.getLen()) return RuntimeError.OutOfBounds;
@@ -3703,8 +3865,9 @@ fn opBitcast(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeErro
_ = try to_value.write(bytes); _ = try to_value.write(bytes);
} }
fn opBranch(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { fn opBranch(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
const id = try rt.it.next(); const id = try rt.it.next();
try rt.snapshotPhiValues(allocator);
rt.previous_label = rt.current_label; rt.previous_label = rt.current_label;
_ = rt.it.jumpToSourceLocation(switch ((try rt.results[id].getVariant()).*) { _ = rt.it.jumpToSourceLocation(switch ((try rt.results[id].getVariant()).*) {
.Label => |l| l.source_location, .Label => |l| l.source_location,
@@ -3712,7 +3875,7 @@ fn opBranch(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
}); });
} }
fn opBranchConditional(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { fn opBranchConditional(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
const cond_value = try rt.results[try rt.it.next()].getValue(); const cond_value = try rt.results[try rt.it.next()].getValue();
const true_branch = switch ((try rt.results[try rt.it.next()].getVariant()).*) { const true_branch = switch ((try rt.results[try rt.it.next()].getVariant()).*) {
.Label => |l| l.source_location, .Label => |l| l.source_location,
@@ -3722,6 +3885,7 @@ fn opBranchConditional(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeEr
.Label => |l| l.source_location, .Label => |l| l.source_location,
else => return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV,
}; };
try rt.snapshotPhiValues(allocator);
rt.previous_label = rt.current_label; rt.previous_label = rt.current_label;
if (cond_value.Bool) { if (cond_value.Bool) {
_ = rt.it.jumpToSourceLocation(true_branch); _ = rt.it.jumpToSourceLocation(true_branch);
@@ -3872,7 +4036,7 @@ fn opCompositeExtract(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Ru
else => return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV,
}, },
.value = blk: { .value = blk: {
var composite = (try rt.results[composite_id].getVariant()).Constant.value; var composite = (try rt.results[composite_id].getValue()).*;
for (0..index_count) |_| { for (0..index_count) |_| {
const member_id = try rt.it.next(); const member_id = try rt.it.next();
if (composite.getCompositeDataOrNull()) |v| { if (composite.getCompositeDataOrNull()) |v| {
@@ -4049,6 +4213,36 @@ fn opConstantTrue(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) Runtim
} }
} }
fn zeroValue(value: *Value) RuntimeError!void {
switch (value.*) {
.Void => {},
.Bool => |*b| b.* = false,
.Int => |*i| i.value.uint64 = 0,
.Float => |*f| f.value.float64 = 0,
.Vector => |lanes| for (lanes) |*lane| try zeroValue(lane),
.Vector4f32 => |*v| v.* = .{ 0, 0, 0, 0 },
.Vector3f32 => |*v| v.* = .{ 0, 0, 0 },
.Vector2f32 => |*v| v.* = .{ 0, 0 },
.Vector4i32 => |*v| v.* = .{ 0, 0, 0, 0 },
.Vector3i32 => |*v| v.* = .{ 0, 0, 0 },
.Vector2i32 => |*v| v.* = .{ 0, 0 },
.Vector4u32 => |*v| v.* = .{ 0, 0, 0, 0 },
.Vector3u32 => |*v| v.* = .{ 0, 0, 0 },
.Vector2u32 => |*v| v.* = .{ 0, 0 },
.Matrix => |columns| for (columns) |*column| try zeroValue(column),
.Array => |*a| for (a.values) |*element| try zeroValue(element),
.RuntimeArray => |*arr| @memset(arr.data, 0),
.Structure => |*s| for (s.values) |*field| try zeroValue(field),
.Image, .Sampler, .SampledImage, .Pointer => {},
.Function => unreachable,
}
}
fn opConstantNull(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
const target = try setupConstant(allocator, rt);
try zeroValue(&target.variant.?.Constant.value);
}
fn opConstant(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { fn opConstant(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void {
const target = try setupConstant(allocator, rt); const target = try setupConstant(allocator, rt);
switch (target.variant.?.Constant.value) { switch (target.variant.?.Constant.value) {
@@ -4937,6 +5131,20 @@ fn opCopyMemory(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!voi
try copyValue(try rt.results[target].getValue(), try rt.results[source].getValue()); try copyValue(try rt.results[target].getValue(), try rt.results[source].getValue());
} }
fn opCopyObject(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
_ = try rt.it.next(); // result type
const target = try rt.it.next();
const source = try rt.it.next();
const target_value = try rt.results[target].getValue();
const source_value = try rt.results[source].getValue();
if (source_value.* == .Pointer) {
target_value.* = source_value.*;
target_value.Pointer.owns_uniform_backing_value = false;
return;
}
try copyValue(target_value, source_value);
}
fn opDecorate(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { fn opDecorate(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
const target = try rt.it.next(); const target = try rt.it.next();
const decoration_type = try rt.it.nextAs(spv.SpvDecoration); const decoration_type = try rt.it.nextAs(spv.SpvDecoration);
@@ -5118,6 +5326,7 @@ fn opExecutionMode(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!
.OutputLineStrip, .OutputLineStrip,
.OutputTriangleStrip, .OutputTriangleStrip,
=> rt.mod.reflection_infos.geometry_output = @intFromEnum(mode), => rt.mod.reflection_infos.geometry_output = @intFromEnum(mode),
.EarlyFragmentTests => rt.mod.reflection_infos.early_fragment_tests = true,
else => {}, else => {},
} }
} }
@@ -5323,7 +5532,8 @@ fn opPhi(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) Runtim
const parent_label_id = try rt.it.next(); const parent_label_id = try rt.it.next();
if (parent_label_id == predecessor) { if (parent_label_id == predecessor) {
try copyValue(try rt.results[id].getValue(), try rt.results[value_id].getValue()); const value = rt.getPhiValueSnapshot(value_id) orelse try rt.results[value_id].getValue();
try copyValue(try rt.results[id].getValue(), value);
try rt.copyDerivative(allocator, id, value_id); try rt.copyDerivative(allocator, id, value_id);
return; return;
} }
@@ -5332,12 +5542,12 @@ fn opPhi(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) Runtim
} }
fn quantizeF32ToF16(value: f32) f32 { fn quantizeF32ToF16(value: f32) f32 {
const rounded = @as(f32, @floatCast(@as(f16, @floatCast(value)))); const quantized = @as(f32, @floatCast(@as(f16, @floatCast(value))));
if (@abs(rounded) != 0.0 and @abs(rounded) < @as(f32, 0x1p-14)) { if (quantized != 0.0 and @abs(quantized) < 0x1.0p-14) {
const sign = @as(u32, @bitCast(value)) & 0x80000000; const sign = @as(u32, @bitCast(quantized)) & 0x8000_0000;
return @bitCast(sign); return @as(f32, @bitCast(sign));
} }
return rounded; return quantized;
} }
fn quantizeToF16Value(target_type: Result.TypeData, rt: *Runtime, dst: *Value, src: *const Value) RuntimeError!void { fn quantizeToF16Value(target_type: Result.TypeData, rt: *Runtime, dst: *Value, src: *const Value) RuntimeError!void {
@@ -5410,13 +5620,13 @@ fn opSelect(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
const obj2_val = try rt.results[obj2].getValue(); const obj2_val = try rt.results[obj2].getValue();
if (target_val.getCompositeDataOrNull()) |*targets| { if (target_val.getCompositeDataOrNull()) |*targets| {
for ( for (targets.*, 0..) |*t, lane_index| {
targets.*, const condition = try readVectorLaneAsValue(cond_val, lane_index);
cond_val.getCompositeDataOrNull().?, if (condition != .Bool)
obj1_val.getCompositeDataOrNull().?, return RuntimeError.InvalidValueType;
obj2_val.getCompositeDataOrNull().?,
) |*t, c, o1, o2| { const selected = try readVectorLaneAsValue(if (condition.Bool) obj1_val else obj2_val, lane_index);
try copyValue(t, if (c.Bool) &o1 else &o2); try copyValue(t, &selected);
} }
return; return;
} }
@@ -5518,6 +5728,7 @@ fn opTypeArray(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void
var target = &rt.results[id]; var target = &rt.results[id];
const components_type_word = try rt.it.next(); const components_type_word = try rt.it.next();
const components_type_data = &((try rt.results[components_type_word].getVariant()).*).Type; const components_type_data = &((try rt.results[components_type_word].getVariant()).*).Type;
const length_word = try rt.it.next();
target.variant = .{ target.variant = .{
.Type = .{ .Type = .{
.Array = .{ .Array = .{
@@ -5526,16 +5737,7 @@ fn opTypeArray(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void
.Type => |t| @as(Result.Type, t), .Type => |t| @as(Result.Type, t),
else => return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV,
}, },
.member_count = switch ((try rt.results[try rt.it.next()].getValue()).*) { .member_count = try arrayMemberCount(rt, length_word),
.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: { .stride = blk: {
for (target.decorations.items) |decoration| { for (target.decorations.items) |decoration| {
if (decoration.rtype == .ArrayStride) if (decoration.rtype == .ArrayStride)
@@ -5548,6 +5750,31 @@ fn opTypeArray(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void
}; };
} }
fn arrayMemberCount(rt: *Runtime, length_word: SpvWord) RuntimeError!SpvWord {
for (rt.results[length_word].decorations.items) |decoration| {
if (decoration.rtype != .SpecId)
continue;
if (rt.specialization_constants.get(decoration.literal_1)) |data| {
if (data.len < @sizeOf(u32))
return RuntimeError.OutOfBounds;
return @intCast(std.mem.bytesToValue(u32, data[0..@sizeOf(u32)]));
}
}
return switch ((try rt.results[length_word].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.sint16),
32 => @intCast(i.value.sint32),
64 => @intCast(i.value.sint64),
else => return RuntimeError.InvalidSpirV,
},
else => return RuntimeError.InvalidSpirV,
};
}
fn opTypeBool(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { fn opTypeBool(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
const id = try rt.it.next(); const id = try rt.it.next();
rt.results[id].variant = .{ rt.results[id].variant = .{
@@ -5838,20 +6065,31 @@ fn opVariable(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) R
.Uniform, .Uniform,
.StorageBuffer, .StorageBuffer,
.PushConstant, .PushConstant,
.Workgroup,
}; };
const is_externally_visible = std.mem.containsAtLeastScalar(spv.SpvStorageClass, &externally_visible_data_storages, 1, storage_class); const is_externally_visible = std.mem.containsAtLeastScalar(spv.SpvStorageClass, &externally_visible_data_storages, 1, storage_class);
const use_external_storage = is_externally_visible and (storage_class == .Workgroup or resolved_type != .Array);
if (target.variant) |*variant| {
switch (variant.*) {
.Variable => |*variable| variable.value.deinit(allocator),
else => {},
}
}
target.variant = .{ target.variant = .{
.Variable = .{ .Variable = .{
.storage_class = storage_class, .storage_class = storage_class,
.type_word = resolved_word, .type_word = resolved_word,
.type = resolved_type, .type = resolved_type,
.value = try Value.init(allocator, rt.results, resolved_word, is_externally_visible and resolved_type != .Array), .value = try Value.init(allocator, rt.results, resolved_word, use_external_storage),
}, },
}; };
_ = initializer; if (initializer) |initializer_id| {
try copyValue(try target.getValue(), try rt.results[initializer_id].getValue());
}
} }
fn readDynamicVectorIndex(index_value: *const Value) RuntimeError!usize { fn readDynamicVectorIndex(index_value: *const Value) RuntimeError!usize {