authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-04-09 01:27:02+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-04-09 01:51:53+02:00
logefe7fae6afe1ecdfc3838a97651dc617c4c747c2
treedc1ca64c31439427a08fa15dcb15d39f41feb6f8
parent719d47d823a7e27ba0902b2878835297a6001fd0
signaturelock-open Commit is signed but in an unrecognized format.

spirv: temporarily emit test kernels

SPIR-V cannot represent function pointers without extensions that no vendor implements. For the time being, generate a test kernel for each error, so that we can at least run SOME tests. In the future we may be able to emulate function pointers in some way, but that is not today.

2 files changed, 113 insertions(+), 21 deletions(-)

src/codegen/spirv.zig+110-18
...@@ -272,7 +272,7 @@ pub const DeclGen = struct {...@@ -272,7 +272,7 @@ pub const DeclGen = struct {
272272
273 if (!entry.found_existing) {273 if (!entry.found_existing) {
274 if (decl.val.castTag(.function)) |_| {274 if (decl.val.castTag(.function)) |_| {
275 entry.value_ptr.* = .{.func = .{ .result_id = result_id }};275 entry.value_ptr.* = .{ .func = .{ .result_id = result_id } };
276 } else {276 } else {
277 entry.value_ptr.* = .{ .global = try self.spv.allocGlobal() };277 entry.value_ptr.* = .{ .global = try self.spv.allocGlobal() };
278 }278 }
...@@ -418,11 +418,7 @@ pub const DeclGen = struct {...@@ -418,11 +418,7 @@ pub const DeclGen = struct {
418418
419 fn genUndef(self: *DeclGen, ty_ref: SpvType.Ref) Error!IdRef {419 fn genUndef(self: *DeclGen, ty_ref: SpvType.Ref) Error!IdRef {
420 const result_id = self.spv.allocId();420 const result_id = self.spv.allocId();
421 try self.spv.sections.types_globals_constants.emit(421 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpUndef, .{ .id_result_type = self.typeId(ty_ref), .id_result = result_id });
422 self.spv.gpa,
423 .OpUndef,
424 .{ .id_result_type = self.typeId(ty_ref), .id_result = result_id }
425 );
426 return result_id;422 return result_id;
427 }423 }
428424
...@@ -899,11 +895,12 @@ pub const DeclGen = struct {...@@ -899,11 +895,12 @@ pub const DeclGen = struct {
899 .initializer = constant_struct_id,895 .initializer = constant_struct_id,
900 });896 });
901 // TODO: Set alignment of OpVariable.897 // TODO: Set alignment of OpVariable.
902 // TODO: We may be able to eliminate this cast.898 // TODO: We may be able to eliminate these casts.
899 const const_ptr_id = try self.makePointerConstant(section, ptr_constant_struct_ty_ref, var_id);
903 try section.emitSpecConstantOp(self.spv.gpa, .OpBitcast, .{900 try section.emitSpecConstantOp(self.spv.gpa, .OpBitcast, .{
904 .id_result_type = self.typeId(ptr_ty_ref),901 .id_result_type = self.typeId(ptr_ty_ref),
905 .id_result = result_id,902 .id_result = result_id,
906 .operand = var_id,903 .operand = const_ptr_id,
907 });904 });
908 }905 }
909906
...@@ -1267,13 +1264,13 @@ pub const DeclGen = struct {...@@ -1267,13 +1264,13 @@ pub const DeclGen = struct {
1267 // Similar to unions, we're going to put the most aligned member first.1264 // Similar to unions, we're going to put the most aligned member first.
1268 if (error_align > payload_align) {1265 if (error_align > payload_align) {
1269 // Put the error first1266 // Put the error first
1270 members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });1267 members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });
1271 members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });1268 members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });
1272 // TODO: ABI padding?1269 // TODO: ABI padding?
1273 } else {1270 } else {
1274 // Put the payload first.1271 // Put the payload first.
1275 members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });1272 members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });
1276 members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });1273 members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });
1277 // TODO: ABI padding?1274 // TODO: ABI padding?
1278 }1275 }
12791276
...@@ -1302,12 +1299,81 @@ pub const DeclGen = struct {...@@ -1302,12 +1299,81 @@ pub const DeclGen = struct {
1302 };1299 };
1303 }1300 }
13041301
1302 /// The SPIR-V backend is not yet advanced enough to support the std testing infrastructure.
1303 /// In order to be able to run tests, we "temporarily" lower test kernels into separate entry-
1304 /// points. The test executor will then be able to invoke these to run the tests.
1305 /// Note that tests are lowered according to std.builtin.TestFn, which is `fn () anyerror!void`.
1306 /// (anyerror!void has the same layout as anyerror).
1307 /// Each test declaration generates a function like.
1308 /// %anyerror = OpTypeInt 0 16
1309 /// %p_anyerror = OpTypePointer CrossWorkgroup %anyerror
1310 /// %K = OpTypeFunction %void %p_anyerror
1311 ///
1312 /// %test = OpFunction %void %K
1313 /// %p_err = OpFunctionParameter %p_anyerror
1314 /// %lbl = OpLabel
1315 /// %result = OpFunctionCall %anyerror %func
1316 /// OpStore %p_err %result
1317 /// OpFunctionEnd
1318 /// TODO is to also write out the error as a function call parameter, and to somehow fetch
1319 /// the name of an error in the text executor.
1320 fn generateTestEntryPoint(self: *DeclGen, name: []const u8, func: IdResult) !void {
1321 const anyerror_ty_ref = try self.resolveType(Type.anyerror, .direct);
1322 const ptr_anyerror_ty_ref = try self.spv.ptrType(anyerror_ty_ref, .CrossWorkgroup, null);
1323 const void_ty_ref = try self.resolveType(Type.void, .direct);
1324
1325 const kernel_proto_ty_ref = blk: {
1326 const proto_payload = try self.spv.arena.create(SpvType.Payload.Function);
1327 proto_payload.* = .{
1328 .return_type = void_ty_ref,
1329 .parameters = try self.spv.arena.dupe(SpvType.Ref, &.{ptr_anyerror_ty_ref}),
1330 };
1331 break :blk try self.spv.resolveType(SpvType.initPayload(&proto_payload.base));
1332 };
1333
1334 const kernel_id = self.spv.allocId();
1335 const error_id = self.spv.allocId();
1336 const p_error_id = self.spv.allocId();
1337
1338 const section = &self.spv.sections.functions;
1339 try section.emit(self.spv.gpa, .OpFunction, .{
1340 .id_result_type = self.typeId(void_ty_ref),
1341 .id_result = kernel_id,
1342 .function_control = .{},
1343 .function_type = self.typeId(kernel_proto_ty_ref),
1344 });
1345 try section.emit(self.spv.gpa, .OpFunctionParameter, .{
1346 .id_result_type = self.typeId(ptr_anyerror_ty_ref),
1347 .id_result = p_error_id,
1348 });
1349 try section.emit(self.spv.gpa, .OpLabel, .{
1350 .id_result = self.spv.allocId(),
1351 });
1352 try section.emit(self.spv.gpa, .OpFunctionCall, .{
1353 .id_result_type = self.typeId(anyerror_ty_ref),
1354 .id_result = error_id,
1355 .function = func,
1356 });
1357 try section.emit(self.spv.gpa, .OpStore, .{
1358 .pointer = p_error_id,
1359 .object = error_id,
1360 });
1361 try section.emit(self.spv.gpa, .OpReturn, {});
1362 try section.emit(self.spv.gpa, .OpFunctionEnd, {});
1363
1364 try self.spv.sections.entry_points.emit(self.spv.gpa, .OpEntryPoint, .{
1365 .execution_model = .Kernel,
1366 .entry_point = kernel_id,
1367 .name = name,
1368 });
1369 }
1370
1305 fn genDecl(self: *DeclGen) !void {1371 fn genDecl(self: *DeclGen) !void {
1306 const decl = self.module.declPtr(self.decl_index);1372 const decl = self.module.declPtr(self.decl_index);
1307 const link = try self.resolveDecl(self.decl_index);1373 const link = try self.resolveDecl(self.decl_index);
13081374
1309 if (decl.val.castTag(.function)) |_| {1375 if (decl.val.castTag(.function)) |_| {
1310 log.debug("genDecl function {s} = {}", .{decl.name, link.func.result_id.id});1376 log.debug("genDecl function {s} = {}", .{ decl.name, link.func.result_id.id });
13111377
1312 assert(decl.ty.zigTypeTag() == .Fn);1378 assert(decl.ty.zigTypeTag() == .Fn);
1313 const prototype_id = try self.resolveTypeId(decl.ty);1379 const prototype_id = try self.resolveTypeId(decl.ty);
...@@ -1356,6 +1422,10 @@ pub const DeclGen = struct {...@@ -1356,6 +1422,10 @@ pub const DeclGen = struct {
1356 .target = link.func.result_id,1422 .target = link.func.result_id,
1357 .name = fqn,1423 .name = fqn,
1358 });1424 });
1425
1426 if (self.module.test_functions.contains(self.decl_index)) {
1427 try self.generateTestEntryPoint(fqn, link.func.result_id);
1428 }
1359 } else {1429 } else {
1360 const init_val = if (decl.val.castTag(.variable)) |payload|1430 const init_val = if (decl.val.castTag(.variable)) |payload|
1361 payload.data.init1431 payload.data.init
...@@ -1396,6 +1466,7 @@ pub const DeclGen = struct {...@@ -1396,6 +1466,7 @@ pub const DeclGen = struct {
1396 const ty_ref = try self.resolveType(decl.ty, .indirect);1466 const ty_ref = try self.resolveType(decl.ty, .indirect);
1397 const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, decl.@"align");1467 const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, decl.@"align");
1398 // TODO: Can we eliminate this cast?1468 // TODO: Can we eliminate this cast?
1469 // TODO: Const-wash pointer
1399 try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{1470 try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
1400 .id_result_type = self.typeId(ptr_ty_ref),1471 .id_result_type = self.typeId(ptr_ty_ref),
1401 .id_result = global_result_id,1472 .id_result = global_result_id,
...@@ -2036,6 +2107,24 @@ pub const DeclGen = struct {...@@ -2036,6 +2107,24 @@ pub const DeclGen = struct {
2036 return try self.structFieldPtr(result_ptr_ty, struct_ptr_ty, struct_ptr, field_index);2107 return try self.structFieldPtr(result_ptr_ty, struct_ptr_ty, struct_ptr, field_index);
2037 }2108 }
20382109
2110 /// We cannot use an OpVariable directly in an OpSpecConstantOp, but we can
2111 /// after we insert a dummy AccessChain...
2112 /// TODO: Get rid of this
2113 fn makePointerConstant(
2114 self: *DeclGen,
2115 section: *SpvSection,
2116 ptr_ty_ref: SpvType.Ref,
2117 ptr_id: IdRef,
2118 ) !IdRef {
2119 const result_id = self.spv.allocId();
2120 try section.emitSpecConstantOp(self.spv.gpa, .OpInBoundsAccessChain, .{
2121 .id_result_type = self.typeId(ptr_ty_ref),
2122 .id_result = result_id,
2123 .base = ptr_id,
2124 });
2125 return result_id;
2126 }
2127
2039 fn variable(2128 fn variable(
2040 self: *DeclGen,2129 self: *DeclGen,
2041 comptime context: enum { function, global },2130 comptime context: enum { function, global },
...@@ -2088,11 +2177,14 @@ pub const DeclGen = struct {...@@ -2088,11 +2177,14 @@ pub const DeclGen = struct {
2088 .pointer = alloc_result_id,2177 .pointer = alloc_result_id,
2089 }),2178 }),
2090 // TODO: Can we do without this cast or move it to runtime?2179 // TODO: Can we do without this cast or move it to runtime?
2091 else => try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{2180 else => {
2092 .id_result_type = self.typeId(ptr_ty_ref),2181 const const_ptr_id = try self.makePointerConstant(section, actual_ptr_ty_ref, alloc_result_id);
2093 .id_result = result_id,2182 try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
2094 .pointer = alloc_result_id,2183 .id_result_type = self.typeId(ptr_ty_ref),
2095 }),2184 .id_result = result_id,
2185 .pointer = const_ptr_id,
2186 });
2187 },
2096 }2188 }
2097 }2189 }
20982190
src/codegen/spirv/Module.zig+3-3
...@@ -187,8 +187,8 @@ fn orderGlobalsInto(...@@ -187,8 +187,8 @@ fn orderGlobalsInto(
187 seen: *std.DynamicBitSetUnmanaged,187 seen: *std.DynamicBitSetUnmanaged,
188) !void {188) !void {
189 const node = self.globals.nodes.items[@enumToInt(global_index)];189 const node = self.globals.nodes.items[@enumToInt(global_index)];
190 const deps = self.globals.dependencies.items[node.begin_dep .. node.end_dep];190 const deps = self.globals.dependencies.items[node.begin_dep..node.end_dep];
191 const insts = self.globals.section.instructions.items[node.begin_inst .. node.end_inst];191 const insts = self.globals.section.instructions.items[node.begin_inst..node.end_inst];
192192
193 seen.set(@enumToInt(global_index));193 seen.set(@enumToInt(global_index));
194194
...@@ -725,7 +725,7 @@ pub fn allocGlobal(self: *Module) !Global.Index {...@@ -725,7 +725,7 @@ pub fn allocGlobal(self: *Module) !Global.Index {
725 .begin_inst = undefined,725 .begin_inst = undefined,
726 .end_inst = undefined,726 .end_inst = undefined,
727 .begin_dep = undefined,727 .begin_dep = undefined,
728 .end_dep = undefined,728 .end_dep = undefined,
729 });729 });
730 return @intToEnum(Global.Index, @intCast(u32, self.globals.nodes.items.len - 1));730 return @intToEnum(Global.Index, @intCast(u32, self.globals.nodes.items.len - 1));
731}731}