authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-10-15 18:37:09-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-10-15 18:37:09-07:00
log682cdeceaa7f55a14ad88ce01030888a9c18960e
tree1cc955c9090e9084509831491c70b3bcbdef02b3
parent186126c2a4032424e1b1cdb8ac379fb2beab7429

stage2: optional comparison and 0-bit payloads

* Sema: implement peer type resolution for optionals and null. * Rename `Module.optionalType` to `Type.optional`. * LLVM backend: re-use anonymous values. This is especially useful when isByRef()=true because it means re-using the same generated LLVM globals. * LLVM backend: rework the implementation of is_null and is_non_null AIR instructions. Generate slightly better LLVM code, and also fix the behavior for optionals whose payload type is 0-bit. * LLVM backend: improve `cmp` AIR instruction lowering to support pointer-like optionals. * `Value`: implement support for equality-checking optionals.

7 files changed, 171 insertions(+), 96 deletions(-)

src/Module.zig-14
......@@ -4249,20 +4249,6 @@ pub fn errNoteNonLazy(
42494249 };
42504250}
42514251
4252pub fn optionalType(arena: *Allocator, child_type: Type) Allocator.Error!Type {
4253 switch (child_type.tag()) {
4254 .single_const_pointer => return Type.Tag.optional_single_const_pointer.create(
4255 arena,
4256 child_type.elemType(),
4257 ),
4258 .single_mut_pointer => return Type.Tag.optional_single_mut_pointer.create(
4259 arena,
4260 child_type.elemType(),
4261 ),
4262 else => return Type.Tag.optional.create(arena, child_type),
4263 }
4264}
4265
42664252pub fn errorUnionType(
42674253 arena: *Allocator,
42684254 error_set: Type,
src/Sema.zig+52-4
......@@ -4108,7 +4108,7 @@ fn zirOptionalType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro
41084108 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
41094109 const src = inst_data.src();
41104110 const child_type = try sema.resolveType(block, src, inst_data.operand);
4111 const opt_type = try Module.optionalType(sema.arena, child_type);
4111 const opt_type = try Type.optional(sema.arena, child_type);
41124112
41134113 return sema.addType(opt_type);
41144114}
......@@ -9675,7 +9675,7 @@ fn zirCmpxchg(
96759675 return sema.fail(block, failure_order_src, "failure atomic ordering must not be Release or AcqRel", .{});
96769676 }
96779677
9678 const result_ty = try Module.optionalType(sema.arena, elem_ty);
9678 const result_ty = try Type.optional(sema.arena, elem_ty);
96799679
96809680 // special case zero bit types
96819681 if ((try sema.typeHasOnePossibleValue(block, elem_ty_src, elem_ty)) != null) {
......@@ -10517,7 +10517,7 @@ fn panicWithMsg(
1051710517 .@"addrspace" = target_util.defaultAddressSpace(mod.getTarget(), .global_constant), // TODO might need a place that is more dynamic
1051810518 });
1051910519 const null_stack_trace = try sema.addConstant(
10520 try Module.optionalType(arena, ptr_stack_trace_ty),
10520 try Type.optional(arena, ptr_stack_trace_ty),
1052110521 Value.initTag(.null_value),
1052210522 );
1052310523 const args = try arena.create([2]Air.Inst.Ref);
......@@ -12797,6 +12797,7 @@ fn resolvePeerTypes(
1279712797 const target = sema.mod.getTarget();
1279812798
1279912799 var chosen = instructions[0];
12800 var any_are_null = false;
1280012801 var chosen_i: usize = 0;
1280112802 for (instructions[1..]) |candidate, candidate_i| {
1280212803 const candidate_ty = sema.typeOf(candidate);
......@@ -12878,6 +12879,44 @@ fn resolvePeerTypes(
1287812879 continue;
1287912880 }
1288012881
12882 if (chosen_ty_tag == .Null) {
12883 any_are_null = true;
12884 chosen = candidate;
12885 chosen_i = candidate_i + 1;
12886 continue;
12887 }
12888 if (candidate_ty_tag == .Null) {
12889 any_are_null = true;
12890 continue;
12891 }
12892
12893 if (chosen_ty_tag == .Optional) {
12894 var opt_child_buf: Type.Payload.ElemType = undefined;
12895 const opt_child_ty = chosen_ty.optionalChild(&opt_child_buf);
12896 if (coerceInMemoryAllowed(opt_child_ty, candidate_ty, false, target) == .ok) {
12897 continue;
12898 }
12899 if (coerceInMemoryAllowed(candidate_ty, opt_child_ty, false, target) == .ok) {
12900 any_are_null = true;
12901 chosen = candidate;
12902 chosen_i = candidate_i + 1;
12903 continue;
12904 }
12905 }
12906 if (candidate_ty_tag == .Optional) {
12907 var opt_child_buf: Type.Payload.ElemType = undefined;
12908 const opt_child_ty = candidate_ty.optionalChild(&opt_child_buf);
12909 if (coerceInMemoryAllowed(opt_child_ty, chosen_ty, false, target) == .ok) {
12910 chosen = candidate;
12911 chosen_i = candidate_i + 1;
12912 continue;
12913 }
12914 if (coerceInMemoryAllowed(chosen_ty, opt_child_ty, false, target) == .ok) {
12915 any_are_null = true;
12916 continue;
12917 }
12918 }
12919
1288112920 // At this point, we hit a compile error. We need to recover
1288212921 // the source locations.
1288312922 const chosen_src = candidate_srcs.resolve(
......@@ -12906,7 +12945,16 @@ fn resolvePeerTypes(
1290612945 return sema.failWithOwnedErrorMsg(msg);
1290712946 }
1290812947
12909 return sema.typeOf(chosen);
12948 const chosen_ty = sema.typeOf(chosen);
12949
12950 if (any_are_null) {
12951 switch (chosen_ty.zigTypeTag()) {
12952 .Null, .Optional => return chosen_ty,
12953 else => return Type.optional(sema.arena, chosen_ty),
12954 }
12955 }
12956
12957 return chosen_ty;
1291012958}
1291112959
1291212960pub fn resolveTypeLayout(
src/codegen/llvm.zig+68-49
......@@ -1430,7 +1430,7 @@ pub const FuncGen = struct {
14301430
14311431 /// This stores the LLVM values used in a function, such that they can be referred to
14321432 /// in other instructions. This table is cleared before every function is generated.
1433 func_inst_table: std.AutoHashMapUnmanaged(Air.Inst.Index, *const llvm.Value),
1433 func_inst_table: std.AutoHashMapUnmanaged(Air.Inst.Ref, *const llvm.Value),
14341434
14351435 /// If the return type isByRef, this is the result pointer. Otherwise null.
14361436 ret_ptr: ?*const llvm.Value,
......@@ -1472,23 +1472,27 @@ pub const FuncGen = struct {
14721472 }
14731473
14741474 fn resolveInst(self: *FuncGen, inst: Air.Inst.Ref) !*const llvm.Value {
1475 if (self.air.value(inst)) |val| {
1476 const ty = self.air.typeOf(inst);
1477 const llvm_val = try self.dg.genTypedValue(.{ .ty = ty, .val = val });
1478 if (!isByRef(ty)) return llvm_val;
1475 const gop = try self.func_inst_table.getOrPut(self.dg.gpa, inst);
1476 if (gop.found_existing) return gop.value_ptr.*;
14791477
1480 // We have an LLVM value but we need to create a global constant and
1481 // set the value as its initializer, and then return a pointer to the global.
1482 const target = self.dg.module.getTarget();
1483 const global = self.dg.object.llvm_module.addGlobal(llvm_val.typeOf(), "");
1484 global.setInitializer(llvm_val);
1485 global.setLinkage(.Private);
1486 global.setGlobalConstant(.True);
1487 global.setAlignment(ty.abiAlignment(target));
1488 return global;
1478 const val = self.air.value(inst).?;
1479 const ty = self.air.typeOf(inst);
1480 const llvm_val = try self.dg.genTypedValue(.{ .ty = ty, .val = val });
1481 if (!isByRef(ty)) {
1482 gop.value_ptr.* = llvm_val;
1483 return llvm_val;
14891484 }
1490 const inst_index = Air.refToIndex(inst).?;
1491 return self.func_inst_table.get(inst_index).?;
1485
1486 // We have an LLVM value but we need to create a global constant and
1487 // set the value as its initializer, and then return a pointer to the global.
1488 const target = self.dg.module.getTarget();
1489 const global = self.dg.object.llvm_module.addGlobal(llvm_val.typeOf(), "");
1490 global.setInitializer(llvm_val);
1491 global.setLinkage(.Private);
1492 global.setGlobalConstant(.True);
1493 global.setAlignment(ty.abiAlignment(target));
1494 gop.value_ptr.* = global;
1495 return global;
14921496 }
14931497
14941498 fn genBody(self: *FuncGen, body: []const Air.Inst.Index) Error!void {
......@@ -1528,10 +1532,11 @@ pub const FuncGen = struct {
15281532 .cmp_lte => try self.airCmp(inst, .lte),
15291533 .cmp_neq => try self.airCmp(inst, .neq),
15301534
1531 .is_non_null => try self.airIsNonNull(inst, false),
1532 .is_non_null_ptr => try self.airIsNonNull(inst, true),
1533 .is_null => try self.airIsNull(inst, false),
1534 .is_null_ptr => try self.airIsNull(inst, true),
1535 .is_non_null => try self.airIsNonNull(inst, false, false, .NE),
1536 .is_non_null_ptr => try self.airIsNonNull(inst, true , false, .NE),
1537 .is_null => try self.airIsNonNull(inst, false, true , .EQ),
1538 .is_null_ptr => try self.airIsNonNull(inst, true , true , .EQ),
1539
15351540 .is_non_err => try self.airIsErr(inst, .EQ, false),
15361541 .is_non_err_ptr => try self.airIsErr(inst, .EQ, true),
15371542 .is_err => try self.airIsErr(inst, .NE, false),
......@@ -1618,7 +1623,10 @@ pub const FuncGen = struct {
16181623 },
16191624 // zig fmt: on
16201625 };
1621 if (opt_value) |val| try self.func_inst_table.putNoClobber(self.gpa, inst, val);
1626 if (opt_value) |val| {
1627 const ref = Air.indexToRef(inst);
1628 try self.func_inst_table.putNoClobber(self.gpa, ref, val);
1629 }
16221630 }
16231631 }
16241632
......@@ -1722,8 +1730,7 @@ pub const FuncGen = struct {
17221730 }
17231731
17241732 fn airCmp(self: *FuncGen, inst: Air.Inst.Index, op: math.CompareOperator) !?*const llvm.Value {
1725 if (self.liveness.isUnused(inst))
1726 return null;
1733 if (self.liveness.isUnused(inst)) return null;
17271734
17281735 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
17291736 const lhs = try self.resolveInst(bin_op.lhs);
......@@ -1733,7 +1740,7 @@ pub const FuncGen = struct {
17331740
17341741 const int_ty = switch (operand_ty.zigTypeTag()) {
17351742 .Enum => operand_ty.intTagType(&buffer),
1736 .Int, .Bool, .Pointer, .ErrorSet => operand_ty,
1743 .Int, .Bool, .Pointer, .Optional, .ErrorSet => operand_ty,
17371744 .Float => {
17381745 const operation: llvm.RealPredicate = switch (op) {
17391746 .eq => .OEQ,
......@@ -2227,45 +2234,57 @@ pub const FuncGen = struct {
22272234 );
22282235 }
22292236
2230 fn airIsNonNull(self: *FuncGen, inst: Air.Inst.Index, operand_is_ptr: bool) !?*const llvm.Value {
2231 if (self.liveness.isUnused(inst))
2232 return null;
2237 fn airIsNonNull(
2238 self: *FuncGen,
2239 inst: Air.Inst.Index,
2240 operand_is_ptr: bool,
2241 invert: bool,
2242 pred: llvm.IntPredicate,
2243 ) !?*const llvm.Value {
2244 if (self.liveness.isUnused(inst)) return null;
22332245
22342246 const un_op = self.air.instructions.items(.data)[inst].un_op;
22352247 const operand = try self.resolveInst(un_op);
2236
2237 if (operand_is_ptr) {
2238 const operand_ty = self.air.typeOf(un_op).elemType();
2239 if (operand_ty.isPtrLikeOptional()) {
2240 const operand_llvm_ty = try self.dg.llvmType(operand_ty);
2241 const loaded = self.builder.buildLoad(operand, "");
2242 return self.builder.buildICmp(.NE, loaded, operand_llvm_ty.constNull(), "");
2248 const operand_ty = self.air.typeOf(un_op);
2249 const optional_ty = if (operand_is_ptr) operand_ty.childType() else operand_ty;
2250 var buf: Type.Payload.ElemType = undefined;
2251 const payload_ty = optional_ty.optionalChild(&buf);
2252 if (!payload_ty.hasCodeGenBits()) {
2253 if (invert) {
2254 return self.builder.buildNot(operand, "");
2255 } else {
2256 return operand;
22432257 }
2258 }
2259 if (optional_ty.isPtrLikeOptional()) {
2260 const optional_llvm_ty = try self.dg.llvmType(optional_ty);
2261 const loaded = if (operand_is_ptr) self.builder.buildLoad(operand, "") else operand;
2262 return self.builder.buildICmp(pred, loaded, optional_llvm_ty.constNull(), "");
2263 }
22442264
2265 if (operand_is_ptr or isByRef(optional_ty)) {
22452266 const index_type = self.context.intType(32);
22462267
2247 var indices: [2]*const llvm.Value = .{
2268 const indices: [2]*const llvm.Value = .{
22482269 index_type.constNull(),
22492270 index_type.constInt(1, .False),
22502271 };
22512272
2252 return self.builder.buildLoad(self.builder.buildInBoundsGEP(operand, &indices, indices.len, ""), "");
2273 const field_ptr = self.builder.buildInBoundsGEP(operand, &indices, indices.len, "");
2274 const non_null_bit = self.builder.buildLoad(field_ptr, "");
2275 if (invert) {
2276 return self.builder.buildNot(non_null_bit, "");
2277 } else {
2278 return non_null_bit;
2279 }
22532280 }
22542281
2255 const operand_ty = self.air.typeOf(un_op);
2256 if (operand_ty.isPtrLikeOptional()) {
2257 const operand_llvm_ty = try self.dg.llvmType(operand_ty);
2258 return self.builder.buildICmp(.NE, operand, operand_llvm_ty.constNull(), "");
2282 const non_null_bit = self.builder.buildExtractValue(operand, 1, "");
2283 if (invert) {
2284 return self.builder.buildNot(non_null_bit, "");
2285 } else {
2286 return non_null_bit;
22592287 }
2260
2261 return self.builder.buildExtractValue(operand, 1, "");
2262 }
2263
2264 fn airIsNull(self: *FuncGen, inst: Air.Inst.Index, operand_is_ptr: bool) !?*const llvm.Value {
2265 if (self.liveness.isUnused(inst))
2266 return null;
2267
2268 return self.builder.buildNot((try self.airIsNonNull(inst, operand_is_ptr)).?, "");
22692288 }
22702289
22712290 fn airIsErr(
src/type.zig+14
......@@ -4031,6 +4031,20 @@ pub const Type = extern union {
40314031 });
40324032 }
40334033
4034 pub fn optional(arena: *Allocator, child_type: Type) Allocator.Error!Type {
4035 switch (child_type.tag()) {
4036 .single_const_pointer => return Type.Tag.optional_single_const_pointer.create(
4037 arena,
4038 child_type.elemType(),
4039 ),
4040 .single_mut_pointer => return Type.Tag.optional_single_mut_pointer.create(
4041 arena,
4042 child_type.elemType(),
4043 ),
4044 else => return Type.Tag.optional.create(arena, child_type),
4045 }
4046 }
4047
40344048 pub fn smallestUnsignedBits(max: u64) u16 {
40354049 if (max == 0) return 0;
40364050 const base = std.math.log2(max);
src/value.zig+8
......@@ -1365,12 +1365,20 @@ pub const Value = extern union {
13651365 const b_field_index = b.castTag(.enum_field_index).?.data;
13661366 return a_field_index == b_field_index;
13671367 },
1368 .opt_payload => {
1369 const a_payload = a.castTag(.opt_payload).?.data;
1370 const b_payload = b.castTag(.opt_payload).?.data;
1371 var buffer: Type.Payload.ElemType = undefined;
1372 return eql(a_payload, b_payload, ty.optionalChild(&buffer));
1373 },
13681374 .elem_ptr => @panic("TODO: Implement more pointer eql cases"),
13691375 .field_ptr => @panic("TODO: Implement more pointer eql cases"),
13701376 .eu_payload_ptr => @panic("TODO: Implement more pointer eql cases"),
13711377 .opt_payload_ptr => @panic("TODO: Implement more pointer eql cases"),
13721378 else => {},
13731379 }
1380 } else if (a_tag == .null_value or b_tag == .null_value) {
1381 return false;
13741382 }
13751383
13761384 if (a.pointerDecl()) |a_decl| {
test/behavior/optional.zig+29
......@@ -44,3 +44,32 @@ test "optional pointer to size zero struct" {
4444 var o: ?*EmptyStruct = &e;
4545 try expect(o != null);
4646}
47
48test "equality compare optional pointers" {
49 try testNullPtrsEql();
50 comptime try testNullPtrsEql();
51}
52
53fn testNullPtrsEql() !void {
54 var number: i32 = 1234;
55
56 var x: ?*i32 = null;
57 var y: ?*i32 = null;
58 try expect(x == y);
59 y = &number;
60 try expect(x != y);
61 try expect(x != &number);
62 try expect(&number != x);
63 x = &number;
64 try expect(x == y);
65 try expect(x == &number);
66 try expect(&number == x);
67}
68
69test "optional with void type" {
70 const Foo = struct {
71 x: ?void,
72 };
73 var x = Foo{ .x = null };
74 try expect(x.x == null);
75}
test/behavior/optional_stage1.zig-29
......@@ -3,27 +3,6 @@ const testing = std.testing;
33const expect = testing.expect;
44const expectEqual = testing.expectEqual;
55
6test "equality compare nullable pointers" {
7 try testNullPtrsEql();
8 comptime try testNullPtrsEql();
9}
10
11fn testNullPtrsEql() !void {
12 var number: i32 = 1234;
13
14 var x: ?*i32 = null;
15 var y: ?*i32 = null;
16 try expect(x == y);
17 y = &number;
18 try expect(x != y);
19 try expect(x != &number);
20 try expect(&number != x);
21 x = &number;
22 try expect(x == y);
23 try expect(x == &number);
24 try expect(&number == x);
25}
26
276test "address of unwrap optional" {
287 const S = struct {
298 const Foo = struct {
......@@ -143,14 +122,6 @@ test "coerce an anon struct literal to optional struct" {
143122 comptime try S.doTheTest();
144123}
145124
146test "optional with void type" {
147 const Foo = struct {
148 x: ?void,
149 };
150 var x = Foo{ .x = null };
151 try expect(x.x == null);
152}
153
154125test "0-bit child type coerced to optional return ptr result location" {
155126 const S = struct {
156127 fn doTheTest() !void {