improving derivatives support, adding matrix stride management, adding matrix times scalar
Build / build (push) Successful in 41s
Test / build (push) Successful in 1m12s

This commit is contained in:
2026-06-11 22:50:57 +02:00
parent 75099f7e1d
commit 089a33981c
5 changed files with 464 additions and 43 deletions
+8 -3
View File
@@ -303,8 +303,10 @@ fn applyDecorations(self: *Self, allocator: std.mem.Allocator) ModuleError!void
.Type => |t| { .Type => |t| {
switch (t) { switch (t) {
.Structure => |*s| { .Structure => |*s| {
if (decoration.rtype == .Offset) { switch (decoration.rtype) {
s.members_offsets[decoration.index] = decoration.literal_1; .Offset => s.members_offsets[decoration.index] = decoration.literal_1,
.MatrixStride => s.members_matrix_strides[decoration.index] = decoration.literal_1,
else => {},
} }
}, },
else => {}, else => {},
@@ -324,7 +326,10 @@ fn applyDecorations(self: *Self, allocator: std.mem.Allocator) ModuleError!void
=> if (v.value == .Structure) { => if (v.value == .Structure) {
if (self.results[v.type_word].variant) |type_variant| switch (type_variant) { if (self.results[v.type_word].variant) |type_variant| switch (type_variant) {
.Type => |type_data| switch (type_data) { .Type => |type_data| switch (type_data) {
.Structure => |s| @memcpy(@constCast(v.value.Structure.offsets), s.members_offsets), .Structure => |s| {
@memcpy(@constCast(v.value.Structure.offsets), s.members_offsets);
@memcpy(@constCast(v.value.Structure.matrix_strides), s.members_matrix_strides);
},
else => {}, else => {},
}, },
else => {}, else => {},
+5 -2
View File
@@ -122,6 +122,7 @@ pub const TypeData = union(Type) {
Structure: struct { Structure: struct {
members_type_word: []const SpvWord, members_type_word: []const SpvWord,
members_offsets: []?SpvWord, members_offsets: []?SpvWord,
members_matrix_strides: []?SpvWord,
member_names: std.ArrayList([]const u8), member_names: std.ArrayList([]const u8),
}, },
Function: struct { Function: struct {
@@ -152,9 +153,9 @@ pub const TypeData = union(Type) {
.Bool => 1, .Bool => 1,
.Int => |i| @divExact(i.bit_length, 8), .Int => |i| @divExact(i.bit_length, 8),
.Float => |f| @divExact(f.bit_length, 8), .Float => |f| @divExact(f.bit_length, 8),
.Vector => |v| results[v.components_type_word].variant.?.Type.getSize(results), .Vector => |v| results[v.components_type_word].variant.?.Type.getSize(results) * v.member_count,
.Array => |a| a.stride, .Array => |a| a.stride,
.Matrix => |m| results[m.column_type_word].variant.?.Type.getSize(results), .Matrix => |m| results[m.column_type_word].variant.?.Type.getSize(results) * m.member_count,
.RuntimeArray => |a| a.stride, .RuntimeArray => |a| a.stride,
.Structure => |s| blk: { .Structure => |s| blk: {
var total: usize = 0; var total: usize = 0;
@@ -245,6 +246,7 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
allocator.free(name); allocator.free(name);
} }
allocator.free(data.members_offsets); allocator.free(data.members_offsets);
allocator.free(data.members_matrix_strides);
data.member_names.deinit(allocator); data.member_names.deinit(allocator);
}, },
else => {}, else => {},
@@ -328,6 +330,7 @@ pub fn dupe(self: *const Self, allocator: std.mem.Allocator) RuntimeError!Self {
.Structure = .{ .Structure = .{
.members_type_word = allocator.dupe(SpvWord, s.members_type_word) catch return RuntimeError.OutOfMemory, .members_type_word = allocator.dupe(SpvWord, s.members_type_word) catch return RuntimeError.OutOfMemory,
.members_offsets = allocator.dupe(?SpvWord, s.members_offsets) catch return RuntimeError.OutOfMemory, .members_offsets = allocator.dupe(?SpvWord, s.members_offsets) catch return RuntimeError.OutOfMemory,
.members_matrix_strides = allocator.dupe(?SpvWord, s.members_matrix_strides) catch return RuntimeError.OutOfMemory,
.member_names = blk2: { .member_names = blk2: {
const member_names = s.member_names.clone(allocator) catch return RuntimeError.OutOfMemory; const member_names = s.member_names.clone(allocator) catch return RuntimeError.OutOfMemory;
for (member_names.items, s.member_names.items) |*new_name, name| { for (member_names.items, s.member_names.items) |*new_name, name| {
+4
View File
@@ -68,6 +68,8 @@ pub const Function = struct {
source_location: usize, source_location: usize,
result: *Result, result: *Result,
ret: *Result, ret: *Result,
current_label: ?SpvWord,
previous_label: ?SpvWord,
}; };
pub fn Vec4(comptime T: type) type { pub fn Vec4(comptime T: type) type {
@@ -335,6 +337,8 @@ pub fn beginEntryPoint(self: *Self, allocator: std.mem.Allocator, entry_point_in
.source_location = f.source_location, .source_location = f.source_location,
.result = entry_point_result, .result = entry_point_result,
.ret = &self.results[f.return_type], .ret = &self.results[f.return_type],
.current_label = null,
.previous_label = null,
}) catch return RuntimeError.OutOfMemory; }) catch return RuntimeError.OutOfMemory;
}, },
else => return RuntimeError.InvalidEntryPoint, else => return RuntimeError.InvalidEntryPoint,
+60 -17
View File
@@ -118,6 +118,7 @@ pub const Value = union(Type) {
Structure: struct { Structure: struct {
external_data: ?[]u8, external_data: ?[]u8,
offsets: []const ?SpvWord, offsets: []const ?SpvWord,
matrix_strides: []const ?SpvWord,
values: []Self, values: []Self,
}, },
Function: noreturn, Function: noreturn,
@@ -158,6 +159,7 @@ pub const Value = union(Type) {
/// to a child/member of that materialized value. /// to a child/member of that materialized value.
uniform_backing_value: ?*Self = null, uniform_backing_value: ?*Self = null,
owns_uniform_backing_value: bool = false, owns_uniform_backing_value: bool = false,
matrix_stride: ?SpvWord = null,
}, },
pub inline fn getCompositeDataOrNull(self: *const Self) ?[]Self { pub inline fn getCompositeDataOrNull(self: *const Self) ?[]Self {
@@ -281,6 +283,7 @@ pub const Value = union(Type) {
.Structure = .{ .Structure = .{
.external_data = null, .external_data = null,
.offsets = allocator.dupe(?SpvWord, s.members_offsets) catch return RuntimeError.OutOfMemory, .offsets = allocator.dupe(?SpvWord, s.members_offsets) catch return RuntimeError.OutOfMemory,
.matrix_strides = allocator.dupe(?SpvWord, s.members_matrix_strides) catch return RuntimeError.OutOfMemory,
.values = allocator.alloc(Self, member_count) catch return RuntimeError.OutOfMemory, .values = allocator.alloc(Self, member_count) catch return RuntimeError.OutOfMemory,
}, },
}; };
@@ -365,6 +368,7 @@ pub const Value = union(Type) {
break :blk .{ break :blk .{
.external_data = s.external_data, .external_data = s.external_data,
.offsets = allocator.dupe(?SpvWord, s.offsets) catch return RuntimeError.OutOfMemory, .offsets = allocator.dupe(?SpvWord, s.offsets) catch return RuntimeError.OutOfMemory,
.matrix_strides = allocator.dupe(?SpvWord, s.matrix_strides) catch return RuntimeError.OutOfMemory,
.values = values, .values = values,
}; };
}, },
@@ -376,9 +380,15 @@ pub const Value = union(Type) {
pub fn read(self: *const Self, output: []u8) RuntimeError!usize { pub fn read(self: *const Self, output: []u8) RuntimeError!usize {
const vecRoutine = struct { const vecRoutine = struct {
inline fn routine(comptime T: type, vec: T, out: []u8) RuntimeError!usize { inline fn routine(comptime T: type, vec: T, out: []u8) RuntimeError!usize {
const size = @typeInfo(T).vector.len * 4; const info = @typeInfo(T).vector;
const Lane = info.child;
const size = info.len * @sizeOf(Lane);
if (out.len < size) return RuntimeError.OutOfBounds; if (out.len < size) return RuntimeError.OutOfBounds;
std.mem.bytesAsValue(T, out[0..size]).* = vec; inline for (0..info.len) |i| {
const offset = i * @sizeOf(Lane);
var lane = vec[i];
@memcpy(out[offset..][0..@sizeOf(Lane)], std.mem.asBytes(&lane));
}
return size; return size;
} }
}.routine; }.routine;
@@ -473,12 +483,35 @@ pub const Value = union(Type) {
return 0; return 0;
} }
pub fn readMatrixWithStride(self: *const Self, output: []u8, matrix_stride: SpvWord) RuntimeError!usize {
const columns = switch (self.*) {
.Matrix => |columns| columns,
else => return RuntimeError.InvalidValueType,
};
if (columns.len == 0) return 0;
const matrix_stride_usize: usize = @intCast(matrix_stride);
const column_size = try columns[0].getPlainMemorySize();
const matrix_size = (columns.len - 1) * matrix_stride_usize + column_size;
if (output.len < matrix_size) return RuntimeError.OutOfBounds;
for (columns, 0..) |column, column_index| {
_ = try column.read(output[column_index * matrix_stride_usize ..]);
}
return matrix_size;
}
pub fn write(self: *Self, input: []const u8) RuntimeError!usize { pub fn write(self: *Self, input: []const u8) RuntimeError!usize {
const vecRoutine = struct { const vecRoutine = struct {
inline fn routine(comptime T: type, vec: *T, in: []const u8) RuntimeError!usize { inline fn routine(comptime T: type, vec: *T, in: []const u8) RuntimeError!usize {
const size = @typeInfo(T).vector.len * 4; const info = @typeInfo(T).vector;
const Lane = info.child;
const size = info.len * @sizeOf(Lane);
if (in.len < size) return RuntimeError.OutOfBounds; if (in.len < size) return RuntimeError.OutOfBounds;
vec.* = std.mem.bytesToValue(T, in[0..size]); inline for (0..info.len) |i| {
const offset = i * @sizeOf(Lane);
vec[i] = std.mem.bytesToValue(Lane, in[offset..][0..@sizeOf(Lane)]);
}
return size; return size;
} }
}.routine; }.routine;
@@ -550,17 +583,6 @@ pub const Value = union(Type) {
return offset; return offset;
}, },
.Matrix => |*values| { .Matrix => |*values| {
const matrix_stride = 16;
if (values.len != 0) {
const column_size = try values.*[0].getPlainMemorySize();
if (column_size < matrix_stride and input.len >= (values.len - 1) * matrix_stride + column_size) {
for (values.*, 0..) |*v, column| {
_ = try v.write(input[column * matrix_stride ..]);
}
return (values.len - 1) * matrix_stride + column_size;
}
}
var offset: usize = 0; var offset: usize = 0;
for (values.*) |*v| { for (values.*) |*v| {
offset += try v.write(input[offset..]); offset += try v.write(input[offset..]);
@@ -580,7 +602,24 @@ 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 write_size = try v.write(input[member_offset..]); const write_size = switch (v.*) {
.Matrix => |*columns| blk: {
const matrix_stride = s.matrix_strides[i] orelse break :blk try v.write(input[member_offset..]);
if (columns.len == 0) break :blk @as(usize, 0);
const matrix_stride_usize: usize = @intCast(matrix_stride);
const column_size = try columns.*[0].getPlainMemorySize();
const matrix_size = (columns.len - 1) * matrix_stride_usize + column_size;
if (input.len - member_offset < matrix_size)
return RuntimeError.OutOfBounds;
for (columns.*, 0..) |*column, column_index| {
_ = try column.write(input[member_offset + (column_index * matrix_stride_usize) ..]);
}
break :blk matrix_size;
},
else => try v.write(input[member_offset..]),
};
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;
@@ -693,7 +732,10 @@ pub const Value = union(Type) {
if (p.uniform_slice_window) |window| { if (p.uniform_slice_window) |window| {
switch (p.ptr) { switch (p.ptr) {
.common => |ptr| { .common => |ptr| {
_ = try ptr.read(window); _ = if (p.matrix_stride) |matrix_stride|
try ptr.readMatrixWithStride(window, matrix_stride)
else
try ptr.read(window);
}, },
.f32_ptr => |ptr| { .f32_ptr => |ptr| {
if (window.len < @sizeOf(f32)) return RuntimeError.OutOfBounds; if (window.len < @sizeOf(f32)) return RuntimeError.OutOfBounds;
@@ -730,6 +772,7 @@ pub const Value = union(Type) {
for (s.values) |*value| value.deinit(allocator); for (s.values) |*value| value.deinit(allocator);
allocator.free(s.values); allocator.free(s.values);
allocator.free(s.offsets); allocator.free(s.offsets);
allocator.free(s.matrix_strides);
}, },
.Pointer => |*p| { .Pointer => |*p| {
if (p.owns_uniform_backing_value) { if (p.owns_uniform_backing_value) {
+385 -19
View File
@@ -148,9 +148,9 @@ pub const SetupDispatcher = block: {
.DPdy = opDerivativeSetup, .DPdy = opDerivativeSetup,
.DPdyCoarse = opDerivativeSetup, .DPdyCoarse = opDerivativeSetup,
.DPdyFine = opDerivativeSetup, .DPdyFine = opDerivativeSetup,
.Fwidth = autoSetupConstant, .Fwidth = opDerivativeSetup,
.FwidthCoarse = autoSetupConstant, .FwidthCoarse = opDerivativeSetup,
.FwidthFine = autoSetupConstant, .FwidthFine = opDerivativeSetup,
.Decorate = opDecorate, .Decorate = opDecorate,
.DecorationGroup = opDecorationGroup, .DecorationGroup = opDecorationGroup,
.Dot = autoSetupConstant, .Dot = autoSetupConstant,
@@ -219,6 +219,7 @@ pub const SetupDispatcher = block: {
.MemoryModel = opMemoryModel, .MemoryModel = opMemoryModel,
.Name = opName, .Name = opName,
.Not = autoSetupConstant, .Not = autoSetupConstant,
.OuterProduct = autoSetupConstant,
.Phi = autoSetupConstant, .Phi = autoSetupConstant,
.QuantizeToF16 = autoSetupConstant, .QuantizeToF16 = autoSetupConstant,
.SConvert = autoSetupConstant, .SConvert = autoSetupConstant,
@@ -244,6 +245,7 @@ pub const SetupDispatcher = block: {
.SpecConstantFalse = opSpecConstantFalse, .SpecConstantFalse = opSpecConstantFalse,
.SpecConstantOp = opSpecConstantOp, .SpecConstantOp = opSpecConstantOp,
.SpecConstantTrue = opSpecConstantTrue, .SpecConstantTrue = opSpecConstantTrue,
.Transpose = autoSetupConstant,
.TypeArray = opTypeArray, .TypeArray = opTypeArray,
.TypeBool = opTypeBool, .TypeBool = opTypeBool,
.TypeFloat = opTypeFloat, .TypeFloat = opTypeFloat,
@@ -384,6 +386,7 @@ pub fn initRuntimeDispatcher() void {
runtime_dispatcher[@intFromEnum(spv.SpvOp.MatrixTimesVector)] = MathEngine(.Float, .MatrixTimesVector, false).op; runtime_dispatcher[@intFromEnum(spv.SpvOp.MatrixTimesVector)] = MathEngine(.Float, .MatrixTimesVector, false).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.MemoryBarrier)] = opMemoryBarrier; runtime_dispatcher[@intFromEnum(spv.SpvOp.MemoryBarrier)] = opMemoryBarrier;
runtime_dispatcher[@intFromEnum(spv.SpvOp.Not)] = BitEngine(.UInt, .Not).op; runtime_dispatcher[@intFromEnum(spv.SpvOp.Not)] = BitEngine(.UInt, .Not).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.OuterProduct)] = opOuterProduct;
runtime_dispatcher[@intFromEnum(spv.SpvOp.Phi)] = opPhi; runtime_dispatcher[@intFromEnum(spv.SpvOp.Phi)] = opPhi;
runtime_dispatcher[@intFromEnum(spv.SpvOp.Return)] = opReturn; runtime_dispatcher[@intFromEnum(spv.SpvOp.Return)] = opReturn;
runtime_dispatcher[@intFromEnum(spv.SpvOp.ReturnValue)] = opReturnValue; runtime_dispatcher[@intFromEnum(spv.SpvOp.ReturnValue)] = opReturnValue;
@@ -410,6 +413,7 @@ pub fn initRuntimeDispatcher() void {
runtime_dispatcher[@intFromEnum(spv.SpvOp.Store)] = opStore; runtime_dispatcher[@intFromEnum(spv.SpvOp.Store)] = opStore;
runtime_dispatcher[@intFromEnum(spv.SpvOp.Switch)] = opSwitch; runtime_dispatcher[@intFromEnum(spv.SpvOp.Switch)] = opSwitch;
runtime_dispatcher[@intFromEnum(spv.SpvOp.TerminateInvocation)] = opKill; runtime_dispatcher[@intFromEnum(spv.SpvOp.TerminateInvocation)] = opKill;
runtime_dispatcher[@intFromEnum(spv.SpvOp.Transpose)] = opTranspose;
runtime_dispatcher[@intFromEnum(spv.SpvOp.UConvert)] = ConversionEngine(.UInt, .UInt).op; runtime_dispatcher[@intFromEnum(spv.SpvOp.UConvert)] = ConversionEngine(.UInt, .UInt).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.UDiv)] = MathEngine(.UInt, .Div, false).op; runtime_dispatcher[@intFromEnum(spv.SpvOp.UDiv)] = MathEngine(.UInt, .Div, false).op;
runtime_dispatcher[@intFromEnum(spv.SpvOp.UGreaterThan)] = CondEngine(.UInt, .Greater).op; runtime_dispatcher[@intFromEnum(spv.SpvOp.UGreaterThan)] = CondEngine(.UInt, .Greater).op;
@@ -1428,6 +1432,65 @@ fn ImageEngine(comptime Op: ImageOp) type {
} }
} }
fn setImplicitSampleDerivative(
allocator: std.mem.Allocator,
rt: *Runtime,
result_type_word: SpvWord,
result_id: SpvWord,
coordinate_id: SpvWord,
dst: *const Value,
driver_image: *anyopaque,
driver_sampler: *anyopaque,
dim: spv.SpvDim,
x: f32,
y: f32,
z: f32,
) RuntimeError!void {
const coord_derivative = rt.derivatives.get(coordinate_id) orelse {
rt.clearDerivative(allocator, result_id);
return;
};
var dx_sample = try Value.init(allocator, rt.results, result_type_word, false);
defer dx_sample.deinit(allocator);
var dy_sample = try Value.init(allocator, rt.results, result_type_word, false);
defer dy_sample.deinit(allocator);
const coord_dx_x = try readSampleCoordLane(&coord_derivative.dx, 0);
const coord_dx_y = readSampleCoordLane(&coord_derivative.dx, 1) catch 0.0;
const coord_dx_z = readSampleCoordLane(&coord_derivative.dx, 2) catch 0.0;
const coord_dy_x = try readSampleCoordLane(&coord_derivative.dy, 0);
const coord_dy_y = readSampleCoordLane(&coord_derivative.dy, 1) catch 0.0;
const coord_dy_z = readSampleCoordLane(&coord_derivative.dy, 2) catch 0.0;
try sampleImageImplicitLod(rt, &dx_sample, driver_image, driver_sampler, dim, x + coord_dx_x, y + coord_dx_y, z + coord_dx_z);
try sampleImageImplicitLod(rt, &dy_sample, driver_image, driver_sampler, dim, x + coord_dy_x, y + coord_dy_y, z + coord_dy_z);
var dx = try Value.init(allocator, rt.results, result_type_word, false);
defer dx.deinit(allocator);
var dy = try Value.init(allocator, rt.results, result_type_word, false);
defer dy.deinit(allocator);
const result_type = (try rt.results[result_type_word].getVariant()).Type;
const lane_bits = try Result.resolveLaneBitWidth(result_type, rt);
const lane_count = try Result.resolveLaneCount(result_type);
switch (lane_bits) {
inline 16, 32, 64 => |bits| {
for (0..lane_count) |lane_index| {
const center = try Value.readLane(.Float, bits, dst, lane_index);
const dx_lane = try Value.readLane(.Float, bits, &dx_sample, lane_index);
const dy_lane = try Value.readLane(.Float, bits, &dy_sample, lane_index);
try Value.writeLane(.Float, bits, &dx, lane_index, dx_lane - center);
try Value.writeLane(.Float, bits, &dy, lane_index, dy_lane - center);
}
},
else => return RuntimeError.UnsupportedSpirV,
}
try rt.setDerivative(allocator, result_id, &dx, &dy);
}
fn sampleImageExplicitLod(rt: *Runtime, dst: *Value, driver_image: *anyopaque, driver_sampler: *anyopaque, dim: spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32) 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) RuntimeError!void {
switch (dst.*) { switch (dst.*) {
.Vector4f32, .Vector4f32,
@@ -1509,7 +1572,7 @@ fn ImageEngine(comptime Op: ImageOp) type {
} }
} }
fn op(_: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { fn op(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void {
if (comptime Op == .Resolve) { if (comptime Op == .Resolve) {
_ = try rt.it.next(); // result type _ = try rt.it.next(); // result type
const dst = try rt.results[try rt.it.next()].getValue(); const dst = try rt.results[try rt.it.next()].getValue();
@@ -1538,7 +1601,7 @@ fn ImageEngine(comptime Op: ImageOp) type {
return try writeImage(rt, texel, image_operand.driver_image, image_operand.dim, x, y, z); return try writeImage(rt, texel, image_operand.driver_image, image_operand.dim, x, y, z);
} }
_ = try rt.it.next(); // result type const result_type_word = try rt.it.next();
const result_id = try rt.it.next(); const result_id = try rt.it.next();
const image = &rt.results[try rt.it.next()]; const image = &rt.results[try rt.it.next()];
if (comptime Op == .QuerySize or Op == .QuerySizeLod) { if (comptime Op == .QuerySize or Op == .QuerySizeLod) {
@@ -1550,7 +1613,8 @@ fn ImageEngine(comptime Op: ImageOp) type {
return try queryImageSize(rt, dst, image_operand); return try queryImageSize(rt, dst, image_operand);
} }
const coordinate = try rt.results[try rt.it.next()].getValue(); const coordinate_id = try rt.it.next();
const coordinate = try rt.results[coordinate_id].getValue();
const dst = try rt.results[result_id].getValue(); const dst = try rt.results[result_id].getValue();
switch (Op) { switch (Op) {
@@ -1581,6 +1645,20 @@ fn ImageEngine(comptime Op: ImageOp) type {
y, y,
z, z,
); );
try setImplicitSampleDerivative(
allocator,
rt,
result_type_word,
result_id,
coordinate_id,
dst,
sampled_image_operand.driver_image,
sampled_image_operand.driver_sampler,
sampled_image_operand.dim,
x,
y,
z,
);
}, },
.SampleExplicitLod => { .SampleExplicitLod => {
@@ -1841,6 +1919,79 @@ fn AtomicEngine(comptime Op: AtomicOp) type {
}; };
} }
fn readMatrixLane(comptime bits: u32, matrix: *const Value, column_index: usize, row_index: usize) RuntimeError!Value.getPrimitiveFieldType(.Float, bits) {
const columns = switch (matrix.*) {
.Matrix => |columns| columns,
else => return RuntimeError.InvalidSpirV,
};
if (column_index >= columns.len) return RuntimeError.OutOfBounds;
return Value.readLane(.Float, bits, &columns[column_index], row_index);
}
fn opOuterProduct(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
const target_type = (try rt.results[try rt.it.next()].getVariant()).Type;
const dst = try rt.results[try rt.it.next()].getValue();
const lhs = try rt.results[try rt.it.next()].getValue();
const rhs = try rt.results[try rt.it.next()].getValue();
const dst_columns = switch (dst.*) {
.Matrix => |columns| columns,
else => return RuntimeError.InvalidSpirV,
};
const lhs_lanes = try lhs.resolveLaneCount();
const rhs_lanes = try rhs.resolveLaneCount();
if (dst_columns.len != rhs_lanes) return RuntimeError.InvalidSpirV;
const lane_bits = try Result.resolveLaneBitWidth(target_type, rt);
switch (lane_bits) {
inline 16, 32, 64 => |bits| {
for (dst_columns, 0..) |*dst_column, column_index| {
if (try dst_column.resolveLaneCount() != lhs_lanes) return RuntimeError.InvalidSpirV;
const rhs_lane = try Value.readLane(.Float, bits, rhs, column_index);
for (0..lhs_lanes) |row_index| {
const lhs_lane = try Value.readLane(.Float, bits, lhs, row_index);
try Value.writeLane(.Float, bits, dst_column, row_index, lhs_lane * rhs_lane);
}
}
},
else => return RuntimeError.UnsupportedSpirV,
}
}
fn opTranspose(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
const target_type = (try rt.results[try rt.it.next()].getVariant()).Type;
const dst = try rt.results[try rt.it.next()].getValue();
const src = try rt.results[try rt.it.next()].getValue();
const dst_columns = switch (dst.*) {
.Matrix => |columns| columns,
else => return RuntimeError.InvalidSpirV,
};
const src_columns = switch (src.*) {
.Matrix => |columns| columns,
else => return RuntimeError.InvalidSpirV,
};
if (dst_columns.len == 0 or src_columns.len == 0) return RuntimeError.InvalidSpirV;
const dst_rows = try dst_columns[0].resolveLaneCount();
if (dst_rows != src_columns.len) return RuntimeError.InvalidSpirV;
const lane_bits = try Result.resolveLaneBitWidth(target_type, rt);
switch (lane_bits) {
inline 16, 32, 64 => |bits| {
for (dst_columns, 0..) |*dst_column, dst_column_index| {
const src_row_index = dst_column_index;
for (0..dst_rows) |dst_row_index| {
const src_column_index = dst_row_index;
const value = try readMatrixLane(bits, src, src_column_index, src_row_index);
try Value.writeLane(.Float, bits, dst_column, dst_row_index, value);
}
}
},
else => return RuntimeError.UnsupportedSpirV,
}
}
fn MathEngine(comptime T: PrimitiveType, comptime Op: MathOp, comptime IsAtomic: bool) type { fn MathEngine(comptime T: PrimitiveType, comptime Op: MathOp, comptime IsAtomic: bool) type {
return struct { return struct {
fn operation(comptime TT: type, op1: TT, op2: TT) RuntimeError!TT { fn operation(comptime TT: type, op1: TT, op2: TT) RuntimeError!TT {
@@ -1913,7 +2064,9 @@ fn MathEngine(comptime T: PrimitiveType, comptime Op: MathOp, comptime IsAtomic:
fn applyDirectSIMDVectorf32(comptime N: usize, d: *@Vector(N, f32), l: @Vector(N, f32), r: *const Value) RuntimeError!void { fn applyDirectSIMDVectorf32(comptime N: usize, d: *@Vector(N, f32), l: @Vector(N, f32), r: *const Value) RuntimeError!void {
switch (Op) { switch (Op) {
.VectorTimesScalar => d.* = l * @as(@Vector(N, f32), @splat(r.Float.value.float32)), .VectorTimesScalar,
.MatrixTimesScalar,
=> d.* = l * @as(@Vector(N, f32), @splat(r.Float.value.float32)),
else => try applySIMDVector(f32, N, d, l, r.getVectorSpecialization(N, f32)), else => try applySIMDVector(f32, N, d, l, r.getVectorSpecialization(N, f32)),
} }
} }
@@ -1945,10 +2098,101 @@ fn MathEngine(comptime T: PrimitiveType, comptime Op: MathOp, comptime IsAtomic:
} }
} }
fn propagateDerivative(
allocator: std.mem.Allocator,
rt: *Runtime,
target_type_word: SpvWord,
target_type: Result.TypeData,
dst_id: SpvWord,
lhs_id: SpvWord,
rhs_id: SpvWord,
lhs: *const Value,
rhs: *const Value,
) RuntimeError!void {
if (comptime T != .Float) {
rt.clearDerivative(allocator, dst_id);
return;
}
switch (comptime Op) {
.Add, .Sub, .Mul, .Div, .VectorTimesScalar => {},
else => {
rt.clearDerivative(allocator, dst_id);
return;
},
}
const lhs_derivative = rt.derivatives.get(lhs_id);
const rhs_derivative = rt.derivatives.get(rhs_id);
if (lhs_derivative == null and rhs_derivative == null) {
rt.clearDerivative(allocator, dst_id);
return;
}
var dx = try Value.init(allocator, rt.results, target_type_word, false);
defer dx.deinit(allocator);
var dy = try Value.init(allocator, rt.results, target_type_word, false);
defer dy.deinit(allocator);
const lane_bits = try Result.resolveLaneBitWidth(target_type, rt);
const lane_count = try Result.resolveLaneCount(target_type);
switch (lane_bits) {
inline 16, 32, 64 => |bits| {
const FloatT = Value.getPrimitiveFieldType(.Float, bits);
for (0..lane_count) |lane_index| {
const l = try Value.readLane(.Float, bits, lhs, lane_index);
const r = try Value.readLane(.Float, bits, rhs, lane_index);
const ldx: FloatT = if (lhs_derivative) |derivative|
try Value.readLane(.Float, bits, &derivative.dx, lane_index)
else
@as(FloatT, 0);
const ldy: FloatT = if (lhs_derivative) |derivative|
try Value.readLane(.Float, bits, &derivative.dy, lane_index)
else
@as(FloatT, 0);
const rdx: FloatT = if (rhs_derivative) |derivative|
try Value.readLane(.Float, bits, &derivative.dx, lane_index)
else
@as(FloatT, 0);
const rdy: FloatT = if (rhs_derivative) |derivative|
try Value.readLane(.Float, bits, &derivative.dy, lane_index)
else
@as(FloatT, 0);
const dx_lane = switch (comptime Op) {
.Add => ldx + rdx,
.Sub => ldx - rdx,
.Mul, .VectorTimesScalar => (ldx * r) + (l * rdx),
.Div => ((ldx * r) - (l * rdx)) / (r * r),
else => unreachable,
};
const dy_lane = switch (comptime Op) {
.Add => ldy + rdy,
.Sub => ldy - rdy,
.Mul, .VectorTimesScalar => (ldy * r) + (l * rdy),
.Div => ((ldy * r) - (l * rdy)) / (r * r),
else => unreachable,
};
try Value.writeLane(.Float, bits, &dx, lane_index, dx_lane);
try Value.writeLane(.Float, bits, &dy, lane_index, dy_lane);
}
},
else => return RuntimeError.UnsupportedSpirV,
}
try rt.setDerivative(allocator, dst_id, &dx, &dy);
}
fn op(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { fn op(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
const target_type = (try rt.results[try rt.it.next()].getVariant()).Type; const target_type_word = try rt.it.next();
const dst = try rt.results[try rt.it.next()].getValue(); const target_type = (try rt.results[target_type_word].getVariant()).Type;
const lhs = try rt.results[try rt.it.next()].getValue(); const dst_id = try rt.it.next();
const dst = try rt.results[dst_id].getValue();
const lhs_id = try rt.it.next();
const lhs = try rt.results[lhs_id].getValue();
var arena = std.heap.ArenaAllocator.init(allocator); var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit(); defer arena.deinit();
@@ -1961,7 +2205,8 @@ fn MathEngine(comptime T: PrimitiveType, comptime Op: MathOp, comptime IsAtomic:
lhs_save = try lhs.dupe(arena.allocator()); lhs_save = try lhs.dupe(arena.allocator());
} }
const rhs = try rt.results[try rt.it.next()].getValue(); const rhs_id = try rt.it.next();
const rhs = try rt.results[rhs_id].getValue();
const lane_bits = try Result.resolveLaneBitWidth(target_type, rt); const lane_bits = try Result.resolveLaneBitWidth(target_type, rt);
@@ -2019,7 +2264,11 @@ fn MathEngine(comptime T: PrimitiveType, comptime Op: MathOp, comptime IsAtomic:
try vectorRoutines(dst_vec, lhs_vec, rhs_vec, lane_bits); try vectorRoutines(dst_vec, lhs_vec, rhs_vec, lane_bits);
} }
}, },
// TODO : matrix times scalar .MatrixTimesScalar => {
for (dst_m, lhs.Matrix) |*dst_vec, *lhs_vec| {
try vectorRoutines(dst_vec, lhs_vec, rhs, lane_bits);
}
},
else => return RuntimeError.ToDo, else => return RuntimeError.ToDo,
}, },
@@ -2031,6 +2280,8 @@ fn MathEngine(comptime T: PrimitiveType, comptime Op: MathOp, comptime IsAtomic:
try copyValue(dst, &lhs_save.?); try copyValue(dst, &lhs_save.?);
try lhs.flushPtr(allocator); try lhs.flushPtr(allocator);
} }
try propagateDerivative(allocator, rt, target_type_word, target_type, dst_id, lhs_id, rhs_id, lhs, rhs);
} }
fn opSingle(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { fn opSingle(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
@@ -2257,7 +2508,10 @@ fn copyValue(dst: *Value, src: *const Value) RuntimeError!void {
else => try copyValue(dst_val_ptr, src), else => try copyValue(dst_val_ptr, src),
} }
if (dst_ptr.uniform_slice_window) |window| { if (dst_ptr.uniform_slice_window) |window| {
_ = try dst_val_ptr.read(window); _ = if (dst_ptr.matrix_stride) |matrix_stride|
try dst_val_ptr.readMatrixWithStride(window, matrix_stride)
else
try dst_val_ptr.read(window);
} }
return; return;
}, },
@@ -2376,12 +2630,14 @@ fn opAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime
var uniform_slice_window: ?[]u8 = null; var uniform_slice_window: ?[]u8 = null;
var uniform_backing_value: ?*Value = null; var uniform_backing_value: ?*Value = null;
var owns_uniform_backing_value = false; var owns_uniform_backing_value = false;
var matrix_stride: ?SpvWord = null;
if (std.meta.activeTag(value_ptr.*) == .Pointer) { if (std.meta.activeTag(value_ptr.*) == .Pointer) {
const ptr = value_ptr.Pointer; const ptr = value_ptr.Pointer;
uniform_slice_window = ptr.uniform_slice_window; uniform_slice_window = ptr.uniform_slice_window;
uniform_backing_value = ptr.uniform_backing_value; uniform_backing_value = ptr.uniform_backing_value;
owns_uniform_backing_value = false; owns_uniform_backing_value = false;
matrix_stride = ptr.matrix_stride;
switch (ptr.ptr) { switch (ptr.ptr) {
.common => |common| value_ptr = common, .common => |common| value_ptr = common,
else => return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV,
@@ -2405,6 +2661,7 @@ fn opAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime
uniform_slice_window = ptr.uniform_slice_window; uniform_slice_window = ptr.uniform_slice_window;
uniform_backing_value = ptr.uniform_backing_value; uniform_backing_value = ptr.uniform_backing_value;
owns_uniform_backing_value = false; owns_uniform_backing_value = false;
matrix_stride = ptr.matrix_stride;
switch (ptr.ptr) { switch (ptr.ptr) {
.common => |common| value_ptr = common, .common => |common| value_ptr = common,
else => return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV,
@@ -2416,12 +2673,18 @@ fn opAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime
switch (value_ptr.*) { switch (value_ptr.*) {
.Vector, .Matrix => |v| { .Vector, .Matrix => |v| {
if (component_index >= v.len) return RuntimeError.OutOfBounds; if (component_index >= v.len) return RuntimeError.OutOfBounds;
var offset: usize = 0; const offset = if (std.meta.activeTag(value_ptr.*) == .Matrix and matrix_stride != null)
component_index * @as(usize, @intCast(matrix_stride.?))
else plain_offset_blk: {
var plain_offset: usize = 0;
for (v[0..component_index]) |*element| { for (v[0..component_index]) |*element| {
offset += try element.getPlainMemorySize(); plain_offset += try element.getPlainMemorySize();
} }
break :plain_offset_blk plain_offset;
};
uniform_slice_window = try helpers.advanceWindow(uniform_slice_window, offset); uniform_slice_window = try helpers.advanceWindow(uniform_slice_window, offset);
value_ptr = &v[component_index]; value_ptr = &v[component_index];
if (std.meta.activeTag(value_ptr.*) != .Matrix) matrix_stride = null;
}, },
.Array => |a| { .Array => |a| {
if (component_index >= a.values.len) return RuntimeError.OutOfBounds; if (component_index >= a.values.len) return RuntimeError.OutOfBounds;
@@ -2444,6 +2707,7 @@ 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];
}, },
.RuntimeArray => |*arr| { .RuntimeArray => |*arr| {
if (component_index >= arr.getLen()) return RuntimeError.OutOfBounds; if (component_index >= arr.getLen()) return RuntimeError.OutOfBounds;
@@ -2474,6 +2738,7 @@ fn opAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime
.uniform_slice_window = try helpers.advanceWindowSized(uniform_slice_window, idx * @sizeOf(f32), @sizeOf(f32)), .uniform_slice_window = try helpers.advanceWindowSized(uniform_slice_window, idx * @sizeOf(f32), @sizeOf(f32)),
.uniform_backing_value = uniform_backing_value, .uniform_backing_value = uniform_backing_value,
.owns_uniform_backing_value = owns_uniform_backing_value, .owns_uniform_backing_value = owns_uniform_backing_value,
.matrix_stride = null,
} }, } },
else => return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV,
}, },
@@ -2483,6 +2748,7 @@ fn opAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime
.uniform_slice_window = try helpers.advanceWindowSized(uniform_slice_window, idx * @sizeOf(f32), @sizeOf(f32)), .uniform_slice_window = try helpers.advanceWindowSized(uniform_slice_window, idx * @sizeOf(f32), @sizeOf(f32)),
.uniform_backing_value = uniform_backing_value, .uniform_backing_value = uniform_backing_value,
.owns_uniform_backing_value = owns_uniform_backing_value, .owns_uniform_backing_value = owns_uniform_backing_value,
.matrix_stride = null,
} }, } },
else => return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV,
}, },
@@ -2492,6 +2758,7 @@ fn opAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime
.uniform_slice_window = try helpers.advanceWindowSized(uniform_slice_window, idx * @sizeOf(f32), @sizeOf(f32)), .uniform_slice_window = try helpers.advanceWindowSized(uniform_slice_window, idx * @sizeOf(f32), @sizeOf(f32)),
.uniform_backing_value = uniform_backing_value, .uniform_backing_value = uniform_backing_value,
.owns_uniform_backing_value = owns_uniform_backing_value, .owns_uniform_backing_value = owns_uniform_backing_value,
.matrix_stride = null,
} }, } },
else => return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV,
}, },
@@ -2501,6 +2768,7 @@ fn opAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime
.uniform_slice_window = try helpers.advanceWindowSized(uniform_slice_window, idx * @sizeOf(i32), @sizeOf(i32)), .uniform_slice_window = try helpers.advanceWindowSized(uniform_slice_window, idx * @sizeOf(i32), @sizeOf(i32)),
.uniform_backing_value = uniform_backing_value, .uniform_backing_value = uniform_backing_value,
.owns_uniform_backing_value = owns_uniform_backing_value, .owns_uniform_backing_value = owns_uniform_backing_value,
.matrix_stride = null,
} }, } },
else => return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV,
}, },
@@ -2510,6 +2778,7 @@ fn opAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime
.uniform_slice_window = try helpers.advanceWindowSized(uniform_slice_window, idx * @sizeOf(i32), @sizeOf(i32)), .uniform_slice_window = try helpers.advanceWindowSized(uniform_slice_window, idx * @sizeOf(i32), @sizeOf(i32)),
.uniform_backing_value = uniform_backing_value, .uniform_backing_value = uniform_backing_value,
.owns_uniform_backing_value = owns_uniform_backing_value, .owns_uniform_backing_value = owns_uniform_backing_value,
.matrix_stride = null,
} }, } },
else => return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV,
}, },
@@ -2519,6 +2788,7 @@ fn opAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime
.uniform_slice_window = try helpers.advanceWindowSized(uniform_slice_window, idx * @sizeOf(i32), @sizeOf(i32)), .uniform_slice_window = try helpers.advanceWindowSized(uniform_slice_window, idx * @sizeOf(i32), @sizeOf(i32)),
.uniform_backing_value = uniform_backing_value, .uniform_backing_value = uniform_backing_value,
.owns_uniform_backing_value = owns_uniform_backing_value, .owns_uniform_backing_value = owns_uniform_backing_value,
.matrix_stride = null,
} }, } },
else => return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV,
}, },
@@ -2528,6 +2798,7 @@ fn opAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime
.uniform_slice_window = try helpers.advanceWindowSized(uniform_slice_window, idx * @sizeOf(u32), @sizeOf(u32)), .uniform_slice_window = try helpers.advanceWindowSized(uniform_slice_window, idx * @sizeOf(u32), @sizeOf(u32)),
.uniform_backing_value = uniform_backing_value, .uniform_backing_value = uniform_backing_value,
.owns_uniform_backing_value = owns_uniform_backing_value, .owns_uniform_backing_value = owns_uniform_backing_value,
.matrix_stride = null,
} }, } },
else => return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV,
}, },
@@ -2537,6 +2808,7 @@ fn opAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime
.uniform_slice_window = try helpers.advanceWindowSized(uniform_slice_window, idx * @sizeOf(u32), @sizeOf(u32)), .uniform_slice_window = try helpers.advanceWindowSized(uniform_slice_window, idx * @sizeOf(u32), @sizeOf(u32)),
.uniform_backing_value = uniform_backing_value, .uniform_backing_value = uniform_backing_value,
.owns_uniform_backing_value = owns_uniform_backing_value, .owns_uniform_backing_value = owns_uniform_backing_value,
.matrix_stride = null,
} }, } },
else => return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV,
}, },
@@ -2546,6 +2818,7 @@ fn opAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime
.uniform_slice_window = try helpers.advanceWindowSized(uniform_slice_window, idx * @sizeOf(u32), @sizeOf(u32)), .uniform_slice_window = try helpers.advanceWindowSized(uniform_slice_window, idx * @sizeOf(u32), @sizeOf(u32)),
.uniform_backing_value = uniform_backing_value, .uniform_backing_value = uniform_backing_value,
.owns_uniform_backing_value = owns_uniform_backing_value, .owns_uniform_backing_value = owns_uniform_backing_value,
.matrix_stride = null,
} }, } },
else => return RuntimeError.InvalidSpirV, else => return RuntimeError.InvalidSpirV,
}, },
@@ -2561,11 +2834,53 @@ fn opAccessChain(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime
.uniform_slice_window = uniform_slice_window, .uniform_slice_window = uniform_slice_window,
.uniform_backing_value = uniform_backing_value, .uniform_backing_value = uniform_backing_value,
.owns_uniform_backing_value = owns_uniform_backing_value, .owns_uniform_backing_value = owns_uniform_backing_value,
.matrix_stride = matrix_stride,
}, },
}; };
}, },
}, },
}; };
if (index_count == 1) {
const base_derivative = rt.derivatives.get(base_id) orelse {
rt.clearDerivative(allocator, id);
return;
};
const index_value = switch ((try rt.results[indexes[0]].getVariant()).*) {
.Constant => |c| c.value,
else => return RuntimeError.InvalidSpirV,
};
const lane_index: usize = switch (index_value) {
.Int => |int| @intCast(int.value.uint32),
else => return RuntimeError.InvalidSpirV,
};
const target_type_word = switch ((try rt.results[var_type].getVariant()).*) {
.Type => |t| switch (t) {
.Pointer => |p| p.target,
else => return RuntimeError.InvalidSpirV,
},
else => return RuntimeError.InvalidSpirV,
};
const target_type = (try rt.results[target_type_word].getVariant()).Type;
const lane_bits = try Result.resolveLaneBitWidth(target_type, rt);
var dx = try Value.init(allocator, rt.results, target_type_word, false);
defer dx.deinit(allocator);
var dy = try Value.init(allocator, rt.results, target_type_word, false);
defer dy.deinit(allocator);
switch (lane_bits) {
inline 16, 32, 64 => |bits| {
try Value.writeLane(.Float, bits, &dx, 0, try Value.readLane(.Float, bits, &base_derivative.dx, lane_index));
try Value.writeLane(.Float, bits, &dy, 0, try Value.readLane(.Float, bits, &base_derivative.dy, lane_index));
},
else => return RuntimeError.UnsupportedSpirV,
}
try rt.setDerivative(allocator, id, &dx, &dy);
} else {
rt.clearDerivative(allocator, id);
}
} }
fn opAtomicStore(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { fn opAtomicStore(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
const ptr_id = try rt.it.next(); const ptr_id = try rt.it.next();
@@ -3833,11 +4148,15 @@ fn opFunctionCall(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) Runtim
((try rt.results[param].getVariant()).*).FunctionParameter.value_ptr = try arg.getValue(); ((try rt.results[param].getVariant()).*).FunctionParameter.value_ptr = try arg.getValue();
} }
rt.function_stack.items[rt.function_stack.items.len - 1].source_location = rt.it.emitSourceLocation(); rt.function_stack.items[rt.function_stack.items.len - 1].source_location = rt.it.emitSourceLocation();
rt.function_stack.items[rt.function_stack.items.len - 1].current_label = rt.current_label;
rt.function_stack.items[rt.function_stack.items.len - 1].previous_label = rt.previous_label;
const source_location = (try func.getVariant()).Function.source_location; const source_location = (try func.getVariant()).Function.source_location;
rt.function_stack.append(allocator, .{ rt.function_stack.append(allocator, .{
.source_location = source_location, .source_location = source_location,
.result = func, .result = func,
.ret = ret, .ret = ret,
.current_label = null,
.previous_label = null,
}) catch return RuntimeError.OutOfMemory; }) catch return RuntimeError.OutOfMemory;
if (!rt.it.jumpToSourceLocation(source_location)) return RuntimeError.InvalidSpirV; if (!rt.it.jumpToSourceLocation(source_location)) return RuntimeError.InvalidSpirV;
rt.current_parameter_index = 0; rt.current_parameter_index = 0;
@@ -3871,6 +4190,9 @@ fn opFunctionParameter(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeEr
fn opLabel(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { fn opLabel(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
const id = try rt.it.next(); const id = try rt.it.next();
rt.current_label = id; rt.current_label = id;
if (rt.function_stack.items.len != 0) {
rt.function_stack.items[rt.function_stack.items.len - 1].current_label = id;
}
if (rt.results[id].variant == null) { if (rt.results[id].variant == null) {
rt.results[id].variant = .{ rt.results[id].variant = .{
.Label = .{ .Label = .{
@@ -3904,6 +4226,7 @@ fn opMemberName(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime)
.Structure = .{ .Structure = .{
.members_type_word = undefined, .members_type_word = undefined,
.members_offsets = undefined, .members_offsets = undefined,
.members_matrix_strides = undefined,
.member_names = .empty, .member_names = .empty,
}, },
}, },
@@ -3941,7 +4264,7 @@ fn opName(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) Runti
result.name = try readStringN(allocator, &rt.it, word_count - 1); result.name = try readStringN(allocator, &rt.it, word_count - 1);
} }
fn opPhi(_: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { fn opPhi(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void {
_ = try rt.it.next(); // result type _ = try rt.it.next(); // result type
const id = try rt.it.next(); const id = try rt.it.next();
@@ -3954,6 +4277,7 @@ fn opPhi(_: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!v
if (parent_label_id == predecessor) { if (parent_label_id == predecessor) {
try copyValue(try rt.results[id].getValue(), try rt.results[value_id].getValue()); try copyValue(try rt.results[id].getValue(), try rt.results[value_id].getValue());
try rt.copyDerivative(allocator, id, value_id);
return; return;
} }
} }
@@ -3965,8 +4289,12 @@ fn opReturn(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
if (rt.function_stack.getLastOrNull()) |function| { if (rt.function_stack.getLastOrNull()) |function| {
_ = rt.it.jumpToSourceLocation(function.source_location); _ = rt.it.jumpToSourceLocation(function.source_location);
rt.current_function = function.result; rt.current_function = function.result;
rt.current_label = function.current_label;
rt.previous_label = function.previous_label;
} else { } else {
rt.current_function = null; rt.current_function = null;
rt.current_label = null;
rt.previous_label = null;
rt.it.skipToEnd(); rt.it.skipToEnd();
} }
} }
@@ -3983,8 +4311,12 @@ fn opReturnValue(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!vo
if (rt.function_stack.getLastOrNull()) |function| { if (rt.function_stack.getLastOrNull()) |function| {
_ = rt.it.jumpToSourceLocation(function.source_location); _ = rt.it.jumpToSourceLocation(function.source_location);
rt.current_function = function.result; rt.current_function = function.result;
rt.current_label = function.current_label;
rt.previous_label = function.previous_label;
} else { } else {
rt.current_function = null; rt.current_function = null;
rt.current_label = null;
rt.previous_label = null;
rt.it.skipToEnd(); rt.it.skipToEnd();
} }
} }
@@ -4098,10 +4430,11 @@ fn opSourceExtension(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Run
rt.mod.extensions.append(allocator, try readStringN(allocator, &rt.it, word_count)) catch return RuntimeError.OutOfMemory; rt.mod.extensions.append(allocator, try readStringN(allocator, &rt.it, word_count)) catch return RuntimeError.OutOfMemory;
} }
fn opStore(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { fn opStore(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
const ptr_id = try rt.it.next(); const ptr_id = try rt.it.next();
const val_id = try rt.it.next(); const val_id = try rt.it.next();
try copyValue(try rt.results[ptr_id].getValue(), try rt.results[val_id].getValue()); try copyValue(try rt.results[ptr_id].getValue(), try rt.results[val_id].getValue());
try rt.copyDerivative(allocator, ptr_id, val_id);
} }
fn opTypeArray(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { fn opTypeArray(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
@@ -4317,6 +4650,8 @@ fn opTypeStruct(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime)
}; };
const members_offsets = allocator.alloc(?SpvWord, word_count - 1) catch return RuntimeError.OutOfMemory; const members_offsets = allocator.alloc(?SpvWord, word_count - 1) catch return RuntimeError.OutOfMemory;
@memset(members_offsets, null); @memset(members_offsets, null);
const members_matrix_strides = allocator.alloc(?SpvWord, word_count - 1) catch return RuntimeError.OutOfMemory;
@memset(members_matrix_strides, null);
if (rt.results[id].variant) |*variant| { if (rt.results[id].variant) |*variant| {
switch (variant.*) { switch (variant.*) {
@@ -4324,6 +4659,7 @@ fn opTypeStruct(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime)
.Structure => |*s| { .Structure => |*s| {
s.members_type_word = members_type_word; s.members_type_word = members_type_word;
s.members_offsets = members_offsets; s.members_offsets = members_offsets;
s.members_matrix_strides = members_matrix_strides;
}, },
else => unreachable, else => unreachable,
}, },
@@ -4335,6 +4671,7 @@ fn opTypeStruct(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime)
.Structure = .{ .Structure = .{
.members_type_word = members_type_word, .members_type_word = members_type_word,
.members_offsets = members_offsets, .members_offsets = members_offsets,
.members_matrix_strides = members_matrix_strides,
.member_names = .empty, .member_names = .empty,
}, },
}, },
@@ -4519,8 +4856,8 @@ fn opVectorExtractDynamic(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime
} }
} }
fn opVectorShuffle(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { fn opVectorShuffle(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
_ = try rt.it.next(); const result_type_word = try rt.it.next();
const result_id = try rt.it.next(); const result_id = try rt.it.next();
const vector_1_id = try rt.it.next(); const vector_1_id = try rt.it.next();
@@ -4722,6 +5059,14 @@ fn opVectorShuffle(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!
const dst_lane_count = try dst.resolveLaneCount(); const dst_lane_count = try dst.resolveLaneCount();
const vector_1_lane_count = try vector_1.resolveLaneCount(); const vector_1_lane_count = try vector_1.resolveLaneCount();
const vector_2_lane_count = try vector_2.resolveLaneCount(); const vector_2_lane_count = try vector_2.resolveLaneCount();
const vector_1_derivative = rt.derivatives.get(vector_1_id);
const vector_2_derivative = rt.derivatives.get(vector_2_id);
var has_derivative = false;
var dx = try Value.init(allocator, rt.results, result_type_word, false);
defer dx.deinit(allocator);
var dy = try Value.init(allocator, rt.results, result_type_word, false);
defer dy.deinit(allocator);
for (0..dst_lane_count) |lane_index| { for (0..dst_lane_count) |lane_index| {
const selector = try rt.it.next(); const selector = try rt.it.next();
@@ -4739,6 +5084,27 @@ fn opVectorShuffle(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!
}; };
try Impl.writeLane(dst, lane_index, lane_value); try Impl.writeLane(dst, lane_index, lane_value);
const derivative = if (selector < vector_1_lane_count)
vector_1_derivative
else
vector_2_derivative;
if (derivative) |d| {
const src_lane_index = if (selector < vector_1_lane_count)
selector
else
selector - vector_1_lane_count;
try Impl.writeLane(&dx, lane_index, try Impl.readLane(&d.dx, src_lane_index));
try Impl.writeLane(&dy, lane_index, try Impl.readLane(&d.dy, src_lane_index));
has_derivative = true;
}
}
if (has_derivative) {
try rt.setDerivative(allocator, result_id, &dx, &dy);
} else {
rt.clearDerivative(allocator, result_id);
} }
} }