authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-11-09 22:58:27-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-11-09 23:01:35-07:00
logcb785b9c6ba705fcb507fdfa470e27b5e82ce1c5
tree59bd1aa803f9669ba434aa7e7890c8c5cd283055
parent008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d

Sema: implement coerce_result_ptr for optionals

New AIR instruction: `optional_payload_ptr_set` It's like `optional_payload_ptr` except it sets the non-null bit. When storing to the payload via a result location that is an optional, `optional_payload_ptr_set` is now emitted. There is a new algorithm in `zirCoerceResultPtr` which stores a dummy value through the result pointer into a temporary block, and then pops off the AIR instructions from the temporary block in order to determine how to transform the result location pointer in case any in-between coercions need to happen. Fixes a couple of behavior tests regarding optionals.

13 files changed, 231 insertions(+), 39 deletions(-)

src/Air.zig+4
......@@ -336,6 +336,9 @@ pub const Inst = struct {
336336 /// *?T => *T. If the value is null, undefined behavior.
337337 /// Uses the `ty_op` field.
338338 optional_payload_ptr,
339 /// *?T => *T. Sets the value to non-null with an undefined payload value.
340 /// Uses the `ty_op` field.
341 optional_payload_ptr_set,
339342 /// Given a payload value, wraps it in an optional type.
340343 /// Uses the `ty_op` field.
341344 wrap_optional,
......@@ -728,6 +731,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
728731 .trunc,
729732 .optional_payload,
730733 .optional_payload_ptr,
734 .optional_payload_ptr_set,
731735 .wrap_optional,
732736 .unwrap_errunion_payload,
733737 .unwrap_errunion_err,
src/Liveness.zig+1
......@@ -292,6 +292,7 @@ fn analyzeInst(
292292 .trunc,
293293 .optional_payload,
294294 .optional_payload_ptr,
295 .optional_payload_ptr_set,
295296 .wrap_optional,
296297 .unwrap_errunion_payload,
297298 .unwrap_errunion_err,
src/Sema.zig+88-5
......@@ -1414,9 +1414,10 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
14141414 const pointee_ty = try sema.resolveType(block, src, bin_inst.lhs);
14151415 const ptr = sema.resolveInst(bin_inst.rhs);
14161416
1417 const addr_space = target_util.defaultAddressSpace(sema.mod.getTarget(), .local);
14171418 const ptr_ty = try Type.ptr(sema.arena, .{
14181419 .pointee_type = pointee_ty,
1419 .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
1420 .@"addrspace" = addr_space,
14201421 });
14211422
14221423 if (Air.refToIndex(ptr)) |ptr_inst| {
......@@ -1430,8 +1431,15 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
14301431 // for the inferred allocation.
14311432 // This instruction will not make it to codegen; it is only to participate
14321433 // in the `stored_inst_list` of the `inferred_alloc`.
1433 const operand = try block.addBitCast(pointee_ty, .void_value);
1434 var trash_block = block.makeSubBlock();
1435 defer trash_block.instructions.deinit(sema.gpa);
1436 const operand = try trash_block.addBitCast(pointee_ty, .void_value);
1437
14341438 try inferred_alloc.stored_inst_list.append(sema.arena, operand);
1439
1440 try sema.requireRuntimeBlock(block, src);
1441 const bitcasted_ptr = try block.addBitCast(ptr_ty, ptr);
1442 return bitcasted_ptr;
14351443 },
14361444 .inferred_alloc_comptime => {
14371445 const iac = ptr_val.castTag(.inferred_alloc_comptime).?;
......@@ -1456,9 +1464,78 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
14561464 }
14571465 }
14581466 }
1467
14591468 try sema.requireRuntimeBlock(block, src);
1460 const bitcasted_ptr = try block.addBitCast(ptr_ty, ptr);
1461 return bitcasted_ptr;
1469
1470 // Make a dummy store through the pointer to test the coercion.
1471 // We will then use the generated instructions to decide what
1472 // kind of transformations to make on the result pointer.
1473 var trash_block = block.makeSubBlock();
1474 defer trash_block.instructions.deinit(sema.gpa);
1475
1476 const dummy_operand = try trash_block.addBitCast(pointee_ty, .void_value);
1477 try sema.storePtr(&trash_block, src, ptr, dummy_operand);
1478
1479 {
1480 const air_tags = sema.air_instructions.items(.tag);
1481
1482 //std.debug.print("dummy storePtr instructions:\n", .{});
1483 //for (trash_block.instructions.items) |item| {
1484 // std.debug.print(" {s}\n", .{@tagName(air_tags[item])});
1485 //}
1486
1487 // The last one is always `store`.
1488 const trash_inst = trash_block.instructions.pop();
1489 assert(air_tags[trash_inst] == .store);
1490 assert(trash_inst == sema.air_instructions.len - 1);
1491 sema.air_instructions.len -= 1;
1492 }
1493
1494 var new_ptr = ptr;
1495
1496 while (true) {
1497 const air_tags = sema.air_instructions.items(.tag);
1498 const air_datas = sema.air_instructions.items(.data);
1499 const trash_inst = trash_block.instructions.pop();
1500 switch (air_tags[trash_inst]) {
1501 .bitcast => {
1502 if (Air.indexToRef(trash_inst) == dummy_operand) {
1503 return block.addBitCast(ptr_ty, new_ptr);
1504 }
1505 const ty_op = air_datas[trash_inst].ty_op;
1506 const operand_ty = sema.getTmpAir().typeOf(ty_op.operand);
1507 const ptr_operand_ty = try Type.ptr(sema.arena, .{
1508 .pointee_type = operand_ty,
1509 .@"addrspace" = addr_space,
1510 });
1511 new_ptr = try block.addBitCast(ptr_operand_ty, new_ptr);
1512 },
1513 .wrap_optional => {
1514 const ty_op = air_datas[trash_inst].ty_op;
1515 const payload_ty = sema.getTmpAir().typeOf(ty_op.operand);
1516 const ptr_payload_ty = try Type.ptr(sema.arena, .{
1517 .pointee_type = payload_ty,
1518 .@"addrspace" = addr_space,
1519 });
1520 new_ptr = try block.addTyOp(.optional_payload_ptr_set, ptr_payload_ty, new_ptr);
1521 },
1522 .wrap_errunion_err => {
1523 return sema.fail(block, src, "TODO coerce_result_ptr wrap_errunion_err", .{});
1524 },
1525 .wrap_errunion_payload => {
1526 return sema.fail(block, src, "TODO coerce_result_ptr wrap_errunion_payload", .{});
1527 },
1528 else => {
1529 if (std.debug.runtime_safety) {
1530 std.debug.panic("unexpected AIR tag for coerce_result_ptr: {s}", .{
1531 air_tags[trash_inst],
1532 });
1533 } else {
1534 unreachable;
1535 }
1536 },
1537 }
1538 } else unreachable; // TODO should not need else unreachable
14621539}
14631540
14641541pub fn analyzeStructDecl(
......@@ -2365,7 +2442,13 @@ fn validateUnionInit(
23652442 // Otherwise, the bitcast should be preserved and a store instruction should be
23662443 // emitted to store the constant union value through the bitcast.
23672444 },
2368 else => unreachable,
2445 else => |t| {
2446 if (std.debug.runtime_safety) {
2447 std.debug.panic("unexpected AIR tag for union pointer: {s}", .{@tagName(t)});
2448 } else {
2449 unreachable;
2450 }
2451 },
23692452 }
23702453
23712454 // Otherwise, we set the new union tag now.
src/arch/aarch64/CodeGen.zig+7
......@@ -592,6 +592,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
592592
593593 .optional_payload => try self.airOptionalPayload(inst),
594594 .optional_payload_ptr => try self.airOptionalPayloadPtr(inst),
595 .optional_payload_ptr_set => try self.airOptionalPayloadPtrSet(inst),
595596 .unwrap_errunion_err => try self.airUnwrapErrErr(inst),
596597 .unwrap_errunion_payload => try self.airUnwrapErrPayload(inst),
597598 .unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst),
......@@ -1010,6 +1011,12 @@ fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) !void {
10101011 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
10111012}
10121013
1014fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void {
1015 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1016 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload_ptr_set for {}", .{self.target.cpu.arch});
1017 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1018}
1019
10131020fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void {
10141021 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
10151022 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union error for {}", .{self.target.cpu.arch});
src/arch/arm/CodeGen.zig+7
......@@ -510,6 +510,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
510510
511511 .optional_payload => try self.airOptionalPayload(inst),
512512 .optional_payload_ptr => try self.airOptionalPayloadPtr(inst),
513 .optional_payload_ptr_set => try self.airOptionalPayloadPtrSet(inst),
513514 .unwrap_errunion_err => try self.airUnwrapErrErr(inst),
514515 .unwrap_errunion_payload => try self.airUnwrapErrPayload(inst),
515516 .unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst),
......@@ -1008,6 +1009,12 @@ fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) !void {
10081009 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
10091010}
10101011
1012fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void {
1013 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1014 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload_ptr_set for {}", .{self.target.cpu.arch});
1015 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1016}
1017
10111018fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void {
10121019 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
10131020 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union error for {}", .{self.target.cpu.arch});
src/arch/riscv64/CodeGen.zig+7
......@@ -505,6 +505,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
505505
506506 .optional_payload => try self.airOptionalPayload(inst),
507507 .optional_payload_ptr => try self.airOptionalPayloadPtr(inst),
508 .optional_payload_ptr_set => try self.airOptionalPayloadPtrSet(inst),
508509 .unwrap_errunion_err => try self.airUnwrapErrErr(inst),
509510 .unwrap_errunion_payload => try self.airUnwrapErrPayload(inst),
510511 .unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst),
......@@ -926,6 +927,12 @@ fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) !void {
926927 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
927928}
928929
930fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void {
931 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
932 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload_ptr_set for {}", .{self.target.cpu.arch});
933 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
934}
935
929936fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void {
930937 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
931938 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union error for {}", .{self.target.cpu.arch});
src/arch/x86_64/CodeGen.zig+10
......@@ -578,6 +578,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
578578
579579 .optional_payload => try self.airOptionalPayload(inst),
580580 .optional_payload_ptr => try self.airOptionalPayloadPtr(inst),
581 .optional_payload_ptr_set => try self.airOptionalPayloadPtrSet(inst),
581582 .unwrap_errunion_err => try self.airUnwrapErrErr(inst),
582583 .unwrap_errunion_payload => try self.airUnwrapErrPayload(inst),
583584 .unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst),
......@@ -1043,6 +1044,15 @@ fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) !void {
10431044 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
10441045}
10451046
1047fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void {
1048 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1049 const result: MCValue = if (self.liveness.isUnused(inst))
1050 .dead
1051 else
1052 return self.fail("TODO implement .optional_payload_ptr_set for {}", .{self.target.cpu.arch});
1053 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1054}
1055
10461056fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void {
10471057 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
10481058 const result: MCValue = if (self.liveness.isUnused(inst))
src/codegen/c.zig+30-2
......@@ -1125,8 +1125,9 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
11251125 .shl, .shl_exact => try airBinOp(f, inst, " << "),
11261126 .not => try airNot (f, inst),
11271127
1128 .optional_payload => try airOptionalPayload(f, inst),
1129 .optional_payload_ptr => try airOptionalPayload(f, inst),
1128 .optional_payload => try airOptionalPayload(f, inst),
1129 .optional_payload_ptr => try airOptionalPayload(f, inst),
1130 .optional_payload_ptr_set => try airOptionalPayloadPtrSet(f, inst),
11301131
11311132 .is_err => try airIsErr(f, inst, "", ".", "!="),
11321133 .is_non_err => try airIsErr(f, inst, "", ".", "=="),
......@@ -2218,6 +2219,33 @@ fn airOptionalPayload(f: *Function, inst: Air.Inst.Index) !CValue {
22182219 return local;
22192220}
22202221
2222fn airOptionalPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue {
2223 const ty_op = f.air.instructions.items(.data)[inst].ty_op;
2224 const writer = f.object.writer();
2225 const operand = try f.resolveInst(ty_op.operand);
2226 const operand_ty = f.air.typeOf(ty_op.operand);
2227
2228 const opt_ty = operand_ty.elemType();
2229
2230 if (opt_ty.isPtrLikeOptional()) {
2231 // The payload and the optional are the same value.
2232 // Setting to non-null will be done when the payload is set.
2233 return operand;
2234 }
2235
2236 try writer.writeAll("(");
2237 try f.writeCValue(writer, operand);
2238 try writer.writeAll(")->is_null = false;\n");
2239
2240 const inst_ty = f.air.typeOfIndex(inst);
2241 const local = try f.allocLocal(inst_ty, .Const);
2242 try writer.writeAll(" = &(");
2243 try f.writeCValue(writer, operand);
2244
2245 try writer.writeAll(")->payload;\n");
2246 return local;
2247}
2248
22212249fn airStructFieldPtr(f: *Function, inst: Air.Inst.Index) !CValue {
22222250 if (f.liveness.isUnused(inst))
22232251 // TODO this @as is needed because of a stage1 bug
src/codegen/llvm.zig+38-2
......@@ -1796,8 +1796,9 @@ pub const FuncGen = struct {
17961796 .ptr_elem_val => try self.airPtrElemVal(inst),
17971797 .ptr_elem_ptr => try self.airPtrElemPtr(inst),
17981798
1799 .optional_payload => try self.airOptionalPayload(inst),
1800 .optional_payload_ptr => try self.airOptionalPayloadPtr(inst),
1799 .optional_payload => try self.airOptionalPayload(inst),
1800 .optional_payload_ptr => try self.airOptionalPayloadPtr(inst),
1801 .optional_payload_ptr_set => try self.airOptionalPayloadPtrSet(inst),
18011802
18021803 .unwrap_errunion_payload => try self.airErrUnionPayload(inst, false),
18031804 .unwrap_errunion_payload_ptr => try self.airErrUnionPayload(inst, true),
......@@ -2572,6 +2573,41 @@ pub const FuncGen = struct {
25722573 return self.builder.buildInBoundsGEP(operand, &indices, indices.len, "");
25732574 }
25742575
2576 fn airOptionalPayloadPtrSet(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2577 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2578 const operand = try self.resolveInst(ty_op.operand);
2579 const optional_ty = self.air.typeOf(ty_op.operand).childType();
2580 var buf: Type.Payload.ElemType = undefined;
2581 const payload_ty = optional_ty.optionalChild(&buf);
2582 const non_null_bit = self.context.intType(1).constAllOnes();
2583 if (!payload_ty.hasCodeGenBits()) {
2584 // We have a pointer to a i1. We need to set it to 1 and then return the same pointer.
2585 _ = self.builder.buildStore(non_null_bit, operand);
2586 return operand;
2587 }
2588 if (optional_ty.isPtrLikeOptional()) {
2589 // The payload and the optional are the same value.
2590 // Setting to non-null will be done when the payload is set.
2591 return operand;
2592 }
2593 const index_type = self.context.intType(32);
2594 {
2595 // First set the non-null bit.
2596 const indices: [2]*const llvm.Value = .{
2597 index_type.constNull(), // dereference the pointer
2598 index_type.constInt(1, .False), // second field is the payload
2599 };
2600 const non_null_ptr = self.builder.buildInBoundsGEP(operand, &indices, indices.len, "");
2601 _ = self.builder.buildStore(non_null_bit, non_null_ptr);
2602 }
2603 // Then return the payload pointer.
2604 const indices: [2]*const llvm.Value = .{
2605 index_type.constNull(), // dereference the pointer
2606 index_type.constNull(), // first field is the payload
2607 };
2608 return self.builder.buildInBoundsGEP(operand, &indices, indices.len, "");
2609 }
2610
25752611 fn airOptionalPayload(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
25762612 if (self.liveness.isUnused(inst)) return null;
25772613
src/codegen/wasm.zig+8
......@@ -881,6 +881,7 @@ pub const Context = struct {
881881
882882 .optional_payload => self.airOptionalPayload(inst),
883883 .optional_payload_ptr => self.airOptionalPayload(inst),
884 .optional_payload_ptr_set => self.airOptionalPayloadPtrSet(inst),
884885 else => |tag| self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
885886 };
886887 }
......@@ -1702,6 +1703,13 @@ pub const Context = struct {
17021703 return WValue{ .local = operand.multi_value.index + 1 };
17031704 }
17041705
1706 fn airOptionalPayloadPtrSet(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1707 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1708 const operand = self.resolveInst(ty_op.operand);
1709 _ = operand;
1710 return self.fail("TODO - wasm codegen for optional_payload_ptr_set", .{});
1711 }
1712
17051713 fn airWrapOptional(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
17061714 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
17071715 return self.resolveInst(ty_op.operand);
src/print_air.zig+1
......@@ -175,6 +175,7 @@ const Writer = struct {
175175 .trunc,
176176 .optional_payload,
177177 .optional_payload_ptr,
178 .optional_payload_ptr_set,
178179 .wrap_optional,
179180 .unwrap_errunion_payload,
180181 .unwrap_errunion_err,
test/behavior/optional.zig+30
......@@ -73,3 +73,33 @@ test "optional with void type" {
7373 var x = Foo{ .x = null };
7474 try expect(x.x == null);
7575}
76
77test "address of unwrap optional" {
78 const S = struct {
79 const Foo = struct {
80 a: i32,
81 };
82
83 var global: ?Foo = null;
84
85 pub fn getFoo() anyerror!*Foo {
86 return &global.?;
87 }
88 };
89 S.global = S.Foo{ .a = 1234 };
90 const foo = S.getFoo() catch unreachable;
91 try expect(foo.a == 1234);
92}
93
94test "nested optional field in struct" {
95 const S2 = struct {
96 y: u8,
97 };
98 const S1 = struct {
99 x: ?S2,
100 };
101 var s = S1{
102 .x = S2{ .y = 127 },
103 };
104 try expect(s.x.?.y == 127);
105}
test/behavior/optional_stage1.zig-30
......@@ -3,23 +3,6 @@ const testing = std.testing;
33const expect = testing.expect;
44const expectEqual = testing.expectEqual;
55
6test "address of unwrap optional" {
7 const S = struct {
8 const Foo = struct {
9 a: i32,
10 };
11
12 var global: ?Foo = null;
13
14 pub fn getFoo() anyerror!*Foo {
15 return &global.?;
16 }
17 };
18 S.global = S.Foo{ .a = 1234 };
19 const foo = S.getFoo() catch unreachable;
20 try expect(foo.a == 1234);
21}
22
236test "equality compare optional with non-optional" {
247 try test_cmp_optional_non_optional();
258 comptime try test_cmp_optional_non_optional();
......@@ -198,16 +181,3 @@ test "array of optional unaligned types" {
198181 i += 1;
199182 try expectEqual(Enum.three, values[i].?.Num);
200183}
201
202test "nested optional field in struct" {
203 const S2 = struct {
204 y: u8,
205 };
206 const S1 = struct {
207 x: ?S2,
208 };
209 var s = S1{
210 .x = S2{ .y = 127 },
211 };
212 try expect(s.x.?.y == 127);
213}