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 {...@@ -765,21 +765,18 @@ pub const DeclGen = struct {
765 const is_pl = val.errorUnionIsPayload();765 const is_pl = val.errorUnionIsPayload();
766 const error_val = if (!is_pl) val else Value.initTag(.zero);766 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) {
769 return try self.lower(Type.anyerror, error_val);770 return try self.lower(Type.anyerror, error_val);
770 }771 }
771772
772 const payload_align = payload_ty.abiAlignment(target);
773 const error_align = Type.anyerror.abiAlignment(target);
774
775 const payload_size = payload_ty.abiSize(target);773 const payload_size = payload_ty.abiSize(target);
776 const error_size = Type.anyerror.abiAlignment(target);774 const error_size = Type.anyerror.abiAlignment(target);
777 const ty_size = ty.abiSize(target);775 const ty_size = ty.abiSize(target);
778 const padding = ty_size - payload_size - error_size;776 const padding = ty_size - payload_size - error_size;
779
780 const payload_val = if (val.castTag(.eu_payload)) |pl| pl.data else Value.initTag(.undef);777 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) {
783 try self.lower(Type.anyerror, error_val);780 try self.lower(Type.anyerror, error_val);
784 try self.lower(payload_ty, payload_val);781 try self.lower(payload_ty, payload_val);
785 } else {782 } else {
...@@ -1277,18 +1274,16 @@ pub const DeclGen = struct {...@@ -1277,18 +1274,16 @@ pub const DeclGen = struct {
1277 .ErrorUnion => {1274 .ErrorUnion => {
1278 const payload_ty = ty.errorUnionPayload();1275 const payload_ty = ty.errorUnionPayload();
1279 const error_ty_ref = try self.resolveType(Type.anyerror, .indirect);1276 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) {
1281 return error_ty_ref;1280 return error_ty_ref;
1282 }1281 }
12831282
1284 const payload_ty_ref = try self.resolveType(payload_ty, .indirect);1283 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
1289 var members = std.BoundedArray(SpvType.Payload.Struct.Member, 2){};1285 var members = std.BoundedArray(SpvType.Payload.Struct.Member, 2){};
1290 // Similar to unions, we're going to put the most aligned member first.1286 if (eu_layout.error_first) {
1291 if (error_align > payload_align) {
1292 // Put the error first1287 // Put the error first
1293 members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });1288 members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });
1294 members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });1289 members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });
...@@ -1336,6 +1331,34 @@ pub const DeclGen = struct {...@@ -1336,6 +1331,34 @@ pub const DeclGen = struct {
1336 };1331 };
1337 }1332 }
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
1339 /// The SPIR-V backend is not yet advanced enough to support the std testing infrastructure.1362 /// The SPIR-V backend is not yet advanced enough to support the std testing infrastructure.
1340 /// In order to be able to run tests, we "temporarily" lower test kernels into separate entry-1363 /// In order to be able to run tests, we "temporarily" lower test kernels into separate entry-
1341 /// points. The test executor will then be able to invoke these to run the tests.1364 /// points. The test executor will then be able to invoke these to run the tests.
...@@ -1585,6 +1608,7 @@ pub const DeclGen = struct {...@@ -1585,6 +1608,7 @@ pub const DeclGen = struct {
1585 .loop => return self.airLoop(inst),1608 .loop => return self.airLoop(inst),
1586 .ret => return self.airRet(inst),1609 .ret => return self.airRet(inst),
1587 .ret_load => return self.airRetLoad(inst),1610 .ret_load => return self.airRetLoad(inst),
1611 .@"try" => try self.airTry(inst),
1588 .switch_br => return self.airSwitchBr(inst),1612 .switch_br => return self.airSwitchBr(inst),
1589 .unreach => return self.airUnreach(),1613 .unreach => return self.airUnreach(),
15901614
...@@ -1752,16 +1776,15 @@ pub const DeclGen = struct {...@@ -1752,16 +1776,15 @@ pub const DeclGen = struct {
1752 const operand_ty_id = try self.resolveTypeId(operand_ty);1776 const operand_ty_id = try self.resolveTypeId(operand_ty);
1753 const result_type_id = try self.resolveTypeId(result_ty);1777 const result_type_id = try self.resolveTypeId(result_ty);
17541778
1755 const overflow_member_ty = try self.intType(.unsigned, info.bits);1779 const overflow_member_ty_ref = try self.intType(.unsigned, info.bits);
1756 const overflow_member_ty_id = self.typeId(overflow_member_ty);
17571780
1758 const op_result_id = blk: {1781 const op_result_id = blk: {
1759 // Construct the SPIR-V result type.1782 // Construct the SPIR-V result type.
1760 // It is almost the same as the zig one, except that the fields must be the same type1783 // It is almost the same as the zig one, except that the fields must be the same type
1761 // and they must be unsigned.1784 // and they must be unsigned.
1762 const overflow_result_ty_ref = try self.spv.simpleStructType(&.{1785 const overflow_result_ty_ref = try self.spv.simpleStructType(&.{
1763 .{ .ty = overflow_member_ty, .name = "res" },1786 .{ .ty = overflow_member_ty_ref, .name = "res" },
1764 .{ .ty = overflow_member_ty, .name = "ov" },1787 .{ .ty = overflow_member_ty_ref, .name = "ov" },
1765 });1788 });
1766 const result_id = self.spv.allocId();1789 const result_id = self.spv.allocId();
1767 try self.func.body.emit(self.spv.gpa, .OpIAddCarry, .{1790 try self.func.body.emit(self.spv.gpa, .OpIAddCarry, .{
...@@ -1775,8 +1798,8 @@ pub const DeclGen = struct {...@@ -1775,8 +1798,8 @@ pub const DeclGen = struct {
17751798
1776 // Now convert the SPIR-V flavor result into a Zig-flavor result.1799 // Now convert the SPIR-V flavor result into a Zig-flavor result.
1777 // First, extract the two fields.1800 // First, extract the two fields.
1778 const unsigned_result = try self.extractField(overflow_member_ty_id, op_result_id, 0);1801 const unsigned_result = try self.extractField(overflow_member_ty_ref, op_result_id, 0);
1779 const overflow = try self.extractField(overflow_member_ty_id, op_result_id, 1);1802 const overflow = try self.extractField(overflow_member_ty_ref, op_result_id, 1);
17801803
1781 // We need to convert the results to the types that Zig expects here.1804 // We need to convert the results to the types that Zig expects here.
1782 // The `result` is the same type except unsigned, so we can just bitcast that.1805 // The `result` is the same type except unsigned, so we can just bitcast that.
...@@ -1954,15 +1977,16 @@ pub const DeclGen = struct {...@@ -1954,15 +1977,16 @@ pub const DeclGen = struct {
1954 return result_id;1977 return result_id;
1955 }1978 }
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 {
1958 const result_id = self.spv.allocId();1981 const result_id = self.spv.allocId();
1959 const indexes = [_]u32{field};1982 const indexes = [_]u32{field};
1960 try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{1983 try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{
1961 .id_result_type = result_ty,1984 .id_result_type = self.typeId(result_ty_ref),
1962 .id_result = result_id,1985 .id_result = result_id,
1963 .composite = object,1986 .composite = object,
1964 .indexes = &indexes,1987 .indexes = &indexes,
1965 });1988 });
1989 // TODO: Convert bools, direct structs should have their field types as indirect values.
1966 return result_id;1990 return result_id;
1967 }1991 }
19681992
...@@ -1970,7 +1994,7 @@ pub const DeclGen = struct {...@@ -1970,7 +1994,7 @@ pub const DeclGen = struct {
1970 if (self.liveness.isUnused(inst)) return null;1994 if (self.liveness.isUnused(inst)) return null;
1971 const ty_op = self.air.instructions.items(.data)[inst].ty_op;1995 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1972 return try self.extractField(1996 return try self.extractField(
1973 try self.resolveTypeId(self.air.typeOfIndex(inst)),1997 try self.resolveType(self.air.typeOfIndex(inst), .direct),
1974 try self.resolve(ty_op.operand),1998 try self.resolve(ty_op.operand),
1975 field,1999 field,
1976 );2000 );
...@@ -2451,6 +2475,66 @@ pub const DeclGen = struct {...@@ -2451,6 +2475,66 @@ pub const DeclGen = struct {
2451 });2475 });
2452 }2476 }
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
2454 fn airSwitchBr(self: *DeclGen, inst: Air.Inst.Index) !void {2538 fn airSwitchBr(self: *DeclGen, inst: Air.Inst.Index) !void {
2455 const target = self.getTarget();2539 const target = self.getTarget();
2456 const pl_op = self.air.instructions.items(.data)[inst].pl_op;2540 const pl_op = self.air.instructions.items(.data)[inst].pl_op;