authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-04-10 20:34:15+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-05-11 20:31:50+02:00
log0bae2caaf382dfb168ee404e3ffb717975f8289b
treed3601779e10b0a4a2368498eadf62fcb4558a808
parentdfecf89d06dc2caad41ff54b05240506ea2c47e8
signaturelock-open Commit is signed but in an unrecognized format.

spirv: lower air try

Implements code generation for the try air tag. This commit also adds a utility `errorUnionLayout` function that helps keeping the layout of a spir-v error union consistent.

1 files changed, 105 insertions(+), 21 deletions(-)

src/codegen/spirv.zig+105-21
......@@ -765,21 +765,18 @@ pub const DeclGen = struct {
765765 const is_pl = val.errorUnionIsPayload();
766766 const error_val = if (!is_pl) val else Value.initTag(.zero);
767767
768 if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
768 const eu_layout = dg.errorUnionLayout(payload_ty);
769 if (!eu_layout.payload_has_bits) {
769770 return try self.lower(Type.anyerror, error_val);
770771 }
771772
772 const payload_align = payload_ty.abiAlignment(target);
773 const error_align = Type.anyerror.abiAlignment(target);
774
775773 const payload_size = payload_ty.abiSize(target);
776774 const error_size = Type.anyerror.abiAlignment(target);
777775 const ty_size = ty.abiSize(target);
778776 const padding = ty_size - payload_size - error_size;
779
780777 const payload_val = if (val.castTag(.eu_payload)) |pl| pl.data else Value.initTag(.undef);
781778
782 if (error_align > payload_align) {
779 if (eu_layout.error_first) {
783780 try self.lower(Type.anyerror, error_val);
784781 try self.lower(payload_ty, payload_val);
785782 } else {
......@@ -1277,18 +1274,16 @@ pub const DeclGen = struct {
12771274 .ErrorUnion => {
12781275 const payload_ty = ty.errorUnionPayload();
12791276 const error_ty_ref = try self.resolveType(Type.anyerror, .indirect);
1280 if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
1277
1278 const eu_layout = self.errorUnionLayout(payload_ty);
1279 if (!eu_layout.payload_has_bits) {
12811280 return error_ty_ref;
12821281 }
12831282
12841283 const payload_ty_ref = try self.resolveType(payload_ty, .indirect);
12851284
1286 const payload_align = payload_ty.abiAlignment(target);
1287 const error_align = Type.anyerror.abiAlignment(target);
1288
12891285 var members = std.BoundedArray(SpvType.Payload.Struct.Member, 2){};
1290 // Similar to unions, we're going to put the most aligned member first.
1291 if (error_align > payload_align) {
1286 if (eu_layout.error_first) {
12921287 // Put the error first
12931288 members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });
12941289 members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });
......@@ -1336,6 +1331,34 @@ pub const DeclGen = struct {
13361331 };
13371332 }
13381333
1334 const ErrorUnionLayout = struct {
1335 payload_has_bits: bool,
1336 error_first: bool,
1337
1338 fn errorFieldIndex(self: @This()) u32 {
1339 assert(self.payload_has_bits);
1340 return if (self.error_first) 0 else 1;
1341 }
1342
1343 fn payloadFieldIndex(self: @This()) u32 {
1344 assert(self.payload_has_bits);
1345 return if (self.error_first) 1 else 0;
1346 }
1347 };
1348
1349 fn errorUnionLayout(self: *DeclGen, payload_ty: Type) ErrorUnionLayout {
1350 const target = self.getTarget();
1351
1352 const error_align = Type.anyerror.abiAlignment(target);
1353 const payload_align = payload_ty.abiAlignment(target);
1354
1355 const error_first = error_align > payload_align;
1356 return .{
1357 .payload_has_bits = payload_ty.hasRuntimeBitsIgnoreComptime(),
1358 .error_first = error_first,
1359 };
1360 }
1361
13391362 /// The SPIR-V backend is not yet advanced enough to support the std testing infrastructure.
13401363 /// In order to be able to run tests, we "temporarily" lower test kernels into separate entry-
13411364 /// points. The test executor will then be able to invoke these to run the tests.
......@@ -1585,6 +1608,7 @@ pub const DeclGen = struct {
15851608 .loop => return self.airLoop(inst),
15861609 .ret => return self.airRet(inst),
15871610 .ret_load => return self.airRetLoad(inst),
1611 .@"try" => try self.airTry(inst),
15881612 .switch_br => return self.airSwitchBr(inst),
15891613 .unreach => return self.airUnreach(),
15901614
......@@ -1752,16 +1776,15 @@ pub const DeclGen = struct {
17521776 const operand_ty_id = try self.resolveTypeId(operand_ty);
17531777 const result_type_id = try self.resolveTypeId(result_ty);
17541778
1755 const overflow_member_ty = try self.intType(.unsigned, info.bits);
1756 const overflow_member_ty_id = self.typeId(overflow_member_ty);
1779 const overflow_member_ty_ref = try self.intType(.unsigned, info.bits);
17571780
17581781 const op_result_id = blk: {
17591782 // Construct the SPIR-V result type.
17601783 // It is almost the same as the zig one, except that the fields must be the same type
17611784 // and they must be unsigned.
17621785 const overflow_result_ty_ref = try self.spv.simpleStructType(&.{
1763 .{ .ty = overflow_member_ty, .name = "res" },
1764 .{ .ty = overflow_member_ty, .name = "ov" },
1786 .{ .ty = overflow_member_ty_ref, .name = "res" },
1787 .{ .ty = overflow_member_ty_ref, .name = "ov" },
17651788 });
17661789 const result_id = self.spv.allocId();
17671790 try self.func.body.emit(self.spv.gpa, .OpIAddCarry, .{
......@@ -1775,8 +1798,8 @@ pub const DeclGen = struct {
17751798
17761799 // Now convert the SPIR-V flavor result into a Zig-flavor result.
17771800 // First, extract the two fields.
1778 const unsigned_result = try self.extractField(overflow_member_ty_id, op_result_id, 0);
1779 const overflow = try self.extractField(overflow_member_ty_id, op_result_id, 1);
1801 const unsigned_result = try self.extractField(overflow_member_ty_ref, op_result_id, 0);
1802 const overflow = try self.extractField(overflow_member_ty_ref, op_result_id, 1);
17801803
17811804 // We need to convert the results to the types that Zig expects here.
17821805 // The `result` is the same type except unsigned, so we can just bitcast that.
......@@ -1954,15 +1977,16 @@ pub const DeclGen = struct {
19541977 return result_id;
19551978 }
19561979
1957 fn extractField(self: *DeclGen, result_ty: IdResultType, object: IdRef, field: u32) !IdRef {
1980 fn extractField(self: *DeclGen, result_ty_ref: SpvType.Ref, object: IdRef, field: u32) !IdRef {
19581981 const result_id = self.spv.allocId();
19591982 const indexes = [_]u32{field};
19601983 try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{
1961 .id_result_type = result_ty,
1984 .id_result_type = self.typeId(result_ty_ref),
19621985 .id_result = result_id,
19631986 .composite = object,
19641987 .indexes = &indexes,
19651988 });
1989 // TODO: Convert bools, direct structs should have their field types as indirect values.
19661990 return result_id;
19671991 }
19681992
......@@ -1970,7 +1994,7 @@ pub const DeclGen = struct {
19701994 if (self.liveness.isUnused(inst)) return null;
19711995 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
19721996 return try self.extractField(
1973 try self.resolveTypeId(self.air.typeOfIndex(inst)),
1997 try self.resolveType(self.air.typeOfIndex(inst), .direct),
19741998 try self.resolve(ty_op.operand),
19751999 field,
19762000 );
......@@ -2451,6 +2475,66 @@ pub const DeclGen = struct {
24512475 });
24522476 }
24532477
2478 fn airTry(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
2479 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
2480 const err_union_id = try self.resolve(pl_op.operand);
2481 const extra = self.air.extraData(Air.Try, pl_op.payload);
2482 const body = self.air.extra[extra.end..][0..extra.data.body_len];
2483
2484 const err_union_ty = self.air.typeOf(pl_op.operand);
2485 const payload_ty = self.air.typeOfIndex(inst);
2486
2487 const err_ty_ref = try self.resolveType(Type.anyerror, .direct);
2488 const payload_ty_ref = try self.resolveType(payload_ty, .direct);
2489 const bool_ty_ref = try self.resolveType(Type.bool, .direct);
2490
2491 const eu_layout = self.errorUnionLayout(payload_ty);
2492
2493 if (!err_union_ty.errorUnionSet().errorSetIsEmpty()) {
2494 const err_id = if (eu_layout.payload_has_bits)
2495 try self.extractField(err_ty_ref, err_union_id, eu_layout.errorFieldIndex())
2496 else
2497 err_union_id;
2498
2499 const zero_id = try self.constInt(err_ty_ref, 0);
2500 const is_err_id = self.spv.allocId();
2501 try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{
2502 .id_result_type = self.typeId(bool_ty_ref),
2503 .id_result = is_err_id,
2504 .operand_1 = err_id,
2505 .operand_2 = zero_id,
2506 });
2507
2508 // When there is an error, we must evaluate `body`. Otherwise we must continue
2509 // with the current body.
2510 // Just generate a new block here, then generate a new block inline for the remainder of the body.
2511
2512 const err_block = self.spv.allocId();
2513 const ok_block = self.spv.allocId();
2514
2515 // TODO: Merge block
2516 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{
2517 .condition = is_err_id,
2518 .true_label = err_block,
2519 .false_label = ok_block,
2520 });
2521
2522 try self.beginSpvBlock(err_block);
2523 try self.genBody(body);
2524
2525 try self.beginSpvBlock(ok_block);
2526 // Now just extract the payload, if required.
2527 }
2528 if (self.liveness.isUnused(inst)) {
2529 return null;
2530 }
2531 if (!eu_layout.payload_has_bits) {
2532 return null;
2533 }
2534
2535 return try self.extractField(payload_ty_ref, err_union_id, eu_layout.payloadFieldIndex());
2536 }
2537
24542538 fn airSwitchBr(self: *DeclGen, inst: Air.Inst.Index) !void {
24552539 const target = self.getTarget();
24562540 const pl_op = self.air.instructions.items(.data)[inst].pl_op;