adding missing opcodes, fixing runtime array pointer windows
This commit is contained in:
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user