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 {
272272
273273 if (!entry.found_existing) {
274274 if (decl.val.castTag(.function)) |_| {
275 entry.value_ptr.* = .{.func = .{ .result_id = result_id }};
275 entry.value_ptr.* = .{ .func = .{ .result_id = result_id } };
276276 } else {
277277 entry.value_ptr.* = .{ .global = try self.spv.allocGlobal() };
278278 }
......@@ -418,11 +418,7 @@ pub const DeclGen = struct {
418418
419419 fn genUndef(self: *DeclGen, ty_ref: SpvType.Ref) Error!IdRef {
420420 const result_id = self.spv.allocId();
421 try self.spv.sections.types_globals_constants.emit(
422 self.spv.gpa,
423 .OpUndef,
424 .{ .id_result_type = self.typeId(ty_ref), .id_result = result_id }
425 );
421 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpUndef, .{ .id_result_type = self.typeId(ty_ref), .id_result = result_id });
426422 return result_id;
427423 }
428424
......@@ -899,11 +895,12 @@ pub const DeclGen = struct {
899895 .initializer = constant_struct_id,
900896 });
901897 // 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);
903900 try section.emitSpecConstantOp(self.spv.gpa, .OpBitcast, .{
904901 .id_result_type = self.typeId(ptr_ty_ref),
905902 .id_result = result_id,
906 .operand = var_id,
903 .operand = const_ptr_id,
907904 });
908905 }
909906
......@@ -1267,13 +1264,13 @@ pub const DeclGen = struct {
12671264 // Similar to unions, we're going to put the most aligned member first.
12681265 if (error_align > payload_align) {
12691266 // Put the error first
1270 members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });
1271 members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });
1267 members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });
1268 members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });
12721269 // TODO: ABI padding?
12731270 } else {
12741271 // Put the payload first.
1275 members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });
1276 members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });
1272 members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });
1273 members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });
12771274 // TODO: ABI padding?
12781275 }
12791276
......@@ -1302,12 +1299,81 @@ pub const DeclGen = struct {
13021299 };
13031300 }
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
13051371 fn genDecl(self: *DeclGen) !void {
13061372 const decl = self.module.declPtr(self.decl_index);
13071373 const link = try self.resolveDecl(self.decl_index);
13081374
13091375 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
13121378 assert(decl.ty.zigTypeTag() == .Fn);
13131379 const prototype_id = try self.resolveTypeId(decl.ty);
......@@ -1356,6 +1422,10 @@ pub const DeclGen = struct {
13561422 .target = link.func.result_id,
13571423 .name = fqn,
13581424 });
1425
1426 if (self.module.test_functions.contains(self.decl_index)) {
1427 try self.generateTestEntryPoint(fqn, link.func.result_id);
1428 }
13591429 } else {
13601430 const init_val = if (decl.val.castTag(.variable)) |payload|
13611431 payload.data.init
......@@ -1396,6 +1466,7 @@ pub const DeclGen = struct {
13961466 const ty_ref = try self.resolveType(decl.ty, .indirect);
13971467 const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, decl.@"align");
13981468 // TODO: Can we eliminate this cast?
1469 // TODO: Const-wash pointer
13991470 try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
14001471 .id_result_type = self.typeId(ptr_ty_ref),
14011472 .id_result = global_result_id,
......@@ -2036,6 +2107,24 @@ pub const DeclGen = struct {
20362107 return try self.structFieldPtr(result_ptr_ty, struct_ptr_ty, struct_ptr, field_index);
20372108 }
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
20392128 fn variable(
20402129 self: *DeclGen,
20412130 comptime context: enum { function, global },
......@@ -2088,11 +2177,14 @@ pub const DeclGen = struct {
20882177 .pointer = alloc_result_id,
20892178 }),
20902179 // TODO: Can we do without this cast or move it to runtime?
2091 else => try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
2092 .id_result_type = self.typeId(ptr_ty_ref),
2093 .id_result = result_id,
2094 .pointer = alloc_result_id,
2095 }),
2180 else => {
2181 const const_ptr_id = try self.makePointerConstant(section, actual_ptr_ty_ref, alloc_result_id);
2182 try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
2183 .id_result_type = self.typeId(ptr_ty_ref),
2184 .id_result = result_id,
2185 .pointer = const_ptr_id,
2186 });
2187 },
20962188 }
20972189 }
20982190
src/codegen/spirv/Module.zig+3-3
......@@ -187,8 +187,8 @@ fn orderGlobalsInto(
187187 seen: *std.DynamicBitSetUnmanaged,
188188) !void {
189189 const node = self.globals.nodes.items[@enumToInt(global_index)];
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];
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];
192192
193193 seen.set(@enumToInt(global_index));
194194
......@@ -725,7 +725,7 @@ pub fn allocGlobal(self: *Module) !Global.Index {
725725 .begin_inst = undefined,
726726 .end_inst = undefined,
727727 .begin_dep = undefined,
728 .end_dep = undefined,
728 .end_dep = undefined,
729729 });
730730 return @intToEnum(Global.Index, @intCast(u32, self.globals.nodes.items.len - 1));
731731}