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(...@@ -4249,20 +4249,6 @@ pub fn errNoteNonLazy(
4249 };4249 };
4250}4250}
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
4266pub fn errorUnionType(4252pub fn errorUnionType(
4267 arena: *Allocator,4253 arena: *Allocator,
4268 error_set: Type,4254 error_set: Type,
src/Sema.zig+52-4
...@@ -4108,7 +4108,7 @@ fn zirOptionalType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro...@@ -4108,7 +4108,7 @@ fn zirOptionalType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro
4108 const inst_data = sema.code.instructions.items(.data)[inst].un_node;4108 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
4109 const src = inst_data.src();4109 const src = inst_data.src();
4110 const child_type = try sema.resolveType(block, src, inst_data.operand);4110 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
4113 return sema.addType(opt_type);4113 return sema.addType(opt_type);
4114}4114}
...@@ -9675,7 +9675,7 @@ fn zirCmpxchg(...@@ -9675,7 +9675,7 @@ fn zirCmpxchg(
9675 return sema.fail(block, failure_order_src, "failure atomic ordering must not be Release or AcqRel", .{});9675 return sema.fail(block, failure_order_src, "failure atomic ordering must not be Release or AcqRel", .{});
9676 }9676 }
96779677
9678 const result_ty = try Module.optionalType(sema.arena, elem_ty);9678 const result_ty = try Type.optional(sema.arena, elem_ty);
96799679
9680 // special case zero bit types9680 // special case zero bit types
9681 if ((try sema.typeHasOnePossibleValue(block, elem_ty_src, elem_ty)) != null) {9681 if ((try sema.typeHasOnePossibleValue(block, elem_ty_src, elem_ty)) != null) {
...@@ -10517,7 +10517,7 @@ fn panicWithMsg(...@@ -10517,7 +10517,7 @@ fn panicWithMsg(
10517 .@"addrspace" = target_util.defaultAddressSpace(mod.getTarget(), .global_constant), // TODO might need a place that is more dynamic10517 .@"addrspace" = target_util.defaultAddressSpace(mod.getTarget(), .global_constant), // TODO might need a place that is more dynamic
10518 });10518 });
10519 const null_stack_trace = try sema.addConstant(10519 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),
10521 Value.initTag(.null_value),10521 Value.initTag(.null_value),
10522 );10522 );
10523 const args = try arena.create([2]Air.Inst.Ref);10523 const args = try arena.create([2]Air.Inst.Ref);
...@@ -12797,6 +12797,7 @@ fn resolvePeerTypes(...@@ -12797,6 +12797,7 @@ fn resolvePeerTypes(
12797 const target = sema.mod.getTarget();12797 const target = sema.mod.getTarget();
1279812798
12799 var chosen = instructions[0];12799 var chosen = instructions[0];
12800 var any_are_null = false;
12800 var chosen_i: usize = 0;12801 var chosen_i: usize = 0;
12801 for (instructions[1..]) |candidate, candidate_i| {12802 for (instructions[1..]) |candidate, candidate_i| {
12802 const candidate_ty = sema.typeOf(candidate);12803 const candidate_ty = sema.typeOf(candidate);
...@@ -12878,6 +12879,44 @@ fn resolvePeerTypes(...@@ -12878,6 +12879,44 @@ fn resolvePeerTypes(
12878 continue;12879 continue;
12879 }12880 }
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
12881 // At this point, we hit a compile error. We need to recover12920 // At this point, we hit a compile error. We need to recover
12882 // the source locations.12921 // the source locations.
12883 const chosen_src = candidate_srcs.resolve(12922 const chosen_src = candidate_srcs.resolve(
...@@ -12906,7 +12945,16 @@ fn resolvePeerTypes(...@@ -12906,7 +12945,16 @@ fn resolvePeerTypes(
12906 return sema.failWithOwnedErrorMsg(msg);12945 return sema.failWithOwnedErrorMsg(msg);
12907 }12946 }
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;
12910}12958}
1291112959
12912pub fn resolveTypeLayout(12960pub fn resolveTypeLayout(
src/codegen/llvm.zig+68-49
...@@ -1430,7 +1430,7 @@ pub const FuncGen = struct {...@@ -1430,7 +1430,7 @@ pub const FuncGen = struct {
14301430
1431 /// This stores the LLVM values used in a function, such that they can be referred to1431 /// This stores the LLVM values used in a function, such that they can be referred to
1432 /// in other instructions. This table is cleared before every function is generated.1432 /// 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
1435 /// If the return type isByRef, this is the result pointer. Otherwise null.1435 /// If the return type isByRef, this is the result pointer. Otherwise null.
1436 ret_ptr: ?*const llvm.Value,1436 ret_ptr: ?*const llvm.Value,
...@@ -1472,23 +1472,27 @@ pub const FuncGen = struct {...@@ -1472,23 +1472,27 @@ pub const FuncGen = struct {
1472 }1472 }
14731473
1474 fn resolveInst(self: *FuncGen, inst: Air.Inst.Ref) !*const llvm.Value {1474 fn resolveInst(self: *FuncGen, inst: Air.Inst.Ref) !*const llvm.Value {
1475 if (self.air.value(inst)) |val| {1475 const gop = try self.func_inst_table.getOrPut(self.dg.gpa, inst);
1476 const ty = self.air.typeOf(inst);1476 if (gop.found_existing) return gop.value_ptr.*;
1477 const llvm_val = try self.dg.genTypedValue(.{ .ty = ty, .val = val });
1478 if (!isByRef(ty)) return llvm_val;
14791477
1480 // We have an LLVM value but we need to create a global constant and1478 const val = self.air.value(inst).?;
1481 // set the value as its initializer, and then return a pointer to the global.1479 const ty = self.air.typeOf(inst);
1482 const target = self.dg.module.getTarget();1480 const llvm_val = try self.dg.genTypedValue(.{ .ty = ty, .val = val });
1483 const global = self.dg.object.llvm_module.addGlobal(llvm_val.typeOf(), "");1481 if (!isByRef(ty)) {
1484 global.setInitializer(llvm_val);1482 gop.value_ptr.* = llvm_val;
1485 global.setLinkage(.Private);1483 return llvm_val;
1486 global.setGlobalConstant(.True);
1487 global.setAlignment(ty.abiAlignment(target));
1488 return global;
1489 }1484 }
1490 const inst_index = Air.refToIndex(inst).?;1485
1491 return self.func_inst_table.get(inst_index).?;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;
1492 }1496 }
14931497
1494 fn genBody(self: *FuncGen, body: []const Air.Inst.Index) Error!void {1498 fn genBody(self: *FuncGen, body: []const Air.Inst.Index) Error!void {
...@@ -1528,10 +1532,11 @@ pub const FuncGen = struct {...@@ -1528,10 +1532,11 @@ pub const FuncGen = struct {
1528 .cmp_lte => try self.airCmp(inst, .lte),1532 .cmp_lte => try self.airCmp(inst, .lte),
1529 .cmp_neq => try self.airCmp(inst, .neq),1533 .cmp_neq => try self.airCmp(inst, .neq),
15301534
1531 .is_non_null => try self.airIsNonNull(inst, false),1535 .is_non_null => try self.airIsNonNull(inst, false, false, .NE),
1532 .is_non_null_ptr => try self.airIsNonNull(inst, true),1536 .is_non_null_ptr => try self.airIsNonNull(inst, true , false, .NE),
1533 .is_null => try self.airIsNull(inst, false),1537 .is_null => try self.airIsNonNull(inst, false, true , .EQ),
1534 .is_null_ptr => try self.airIsNull(inst, true),1538 .is_null_ptr => try self.airIsNonNull(inst, true , true , .EQ),
1539
1535 .is_non_err => try self.airIsErr(inst, .EQ, false),1540 .is_non_err => try self.airIsErr(inst, .EQ, false),
1536 .is_non_err_ptr => try self.airIsErr(inst, .EQ, true),1541 .is_non_err_ptr => try self.airIsErr(inst, .EQ, true),
1537 .is_err => try self.airIsErr(inst, .NE, false),1542 .is_err => try self.airIsErr(inst, .NE, false),
...@@ -1618,7 +1623,10 @@ pub const FuncGen = struct {...@@ -1618,7 +1623,10 @@ pub const FuncGen = struct {
1618 },1623 },
1619 // zig fmt: on1624 // zig fmt: on
1620 };1625 };
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 }
1622 }1630 }
1623 }1631 }
16241632
...@@ -1722,8 +1730,7 @@ pub const FuncGen = struct {...@@ -1722,8 +1730,7 @@ pub const FuncGen = struct {
1722 }1730 }
17231731
1724 fn airCmp(self: *FuncGen, inst: Air.Inst.Index, op: math.CompareOperator) !?*const llvm.Value {1732 fn airCmp(self: *FuncGen, inst: Air.Inst.Index, op: math.CompareOperator) !?*const llvm.Value {
1725 if (self.liveness.isUnused(inst))1733 if (self.liveness.isUnused(inst)) return null;
1726 return null;
17271734
1728 const bin_op = self.air.instructions.items(.data)[inst].bin_op;1735 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
1729 const lhs = try self.resolveInst(bin_op.lhs);1736 const lhs = try self.resolveInst(bin_op.lhs);
...@@ -1733,7 +1740,7 @@ pub const FuncGen = struct {...@@ -1733,7 +1740,7 @@ pub const FuncGen = struct {
17331740
1734 const int_ty = switch (operand_ty.zigTypeTag()) {1741 const int_ty = switch (operand_ty.zigTypeTag()) {
1735 .Enum => operand_ty.intTagType(&buffer),1742 .Enum => operand_ty.intTagType(&buffer),
1736 .Int, .Bool, .Pointer, .ErrorSet => operand_ty,1743 .Int, .Bool, .Pointer, .Optional, .ErrorSet => operand_ty,
1737 .Float => {1744 .Float => {
1738 const operation: llvm.RealPredicate = switch (op) {1745 const operation: llvm.RealPredicate = switch (op) {
1739 .eq => .OEQ,1746 .eq => .OEQ,
...@@ -2227,45 +2234,57 @@ pub const FuncGen = struct {...@@ -2227,45 +2234,57 @@ pub const FuncGen = struct {
2227 );2234 );
2228 }2235 }
22292236
2230 fn airIsNonNull(self: *FuncGen, inst: Air.Inst.Index, operand_is_ptr: bool) !?*const llvm.Value {2237 fn airIsNonNull(
2231 if (self.liveness.isUnused(inst))2238 self: *FuncGen,
2232 return null;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
2234 const un_op = self.air.instructions.items(.data)[inst].un_op;2246 const un_op = self.air.instructions.items(.data)[inst].un_op;
2235 const operand = try self.resolveInst(un_op);2247 const operand = try self.resolveInst(un_op);
22362248 const operand_ty = self.air.typeOf(un_op);
2237 if (operand_is_ptr) {2249 const optional_ty = if (operand_is_ptr) operand_ty.childType() else operand_ty;
2238 const operand_ty = self.air.typeOf(un_op).elemType();2250 var buf: Type.Payload.ElemType = undefined;
2239 if (operand_ty.isPtrLikeOptional()) {2251 const payload_ty = optional_ty.optionalChild(&buf);
2240 const operand_llvm_ty = try self.dg.llvmType(operand_ty);2252 if (!payload_ty.hasCodeGenBits()) {
2241 const loaded = self.builder.buildLoad(operand, "");2253 if (invert) {
2242 return self.builder.buildICmp(.NE, loaded, operand_llvm_ty.constNull(), "");2254 return self.builder.buildNot(operand, "");
2255 } else {
2256 return operand;
2243 }2257 }
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)) {
2245 const index_type = self.context.intType(32);2266 const index_type = self.context.intType(32);
22462267
2247 var indices: [2]*const llvm.Value = .{2268 const indices: [2]*const llvm.Value = .{
2248 index_type.constNull(),2269 index_type.constNull(),
2249 index_type.constInt(1, .False),2270 index_type.constInt(1, .False),
2250 };2271 };
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 }
2253 }2280 }
22542281
2255 const operand_ty = self.air.typeOf(un_op);2282 const non_null_bit = self.builder.buildExtractValue(operand, 1, "");
2256 if (operand_ty.isPtrLikeOptional()) {2283 if (invert) {
2257 const operand_llvm_ty = try self.dg.llvmType(operand_ty);2284 return self.builder.buildNot(non_null_bit, "");
2258 return self.builder.buildICmp(.NE, operand, operand_llvm_ty.constNull(), "");2285 } else {
2286 return non_null_bit;
2259 }2287 }
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)).?, "");
2269 }2288 }
22702289
2271 fn airIsErr(2290 fn airIsErr(
src/type.zig+14
...@@ -4031,6 +4031,20 @@ pub const Type = extern union {...@@ -4031,6 +4031,20 @@ pub const Type = extern union {
4031 });4031 });
4032 }4032 }
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
4034 pub fn smallestUnsignedBits(max: u64) u16 {4048 pub fn smallestUnsignedBits(max: u64) u16 {
4035 if (max == 0) return 0;4049 if (max == 0) return 0;
4036 const base = std.math.log2(max);4050 const base = std.math.log2(max);
src/value.zig+8
...@@ -1365,12 +1365,20 @@ pub const Value = extern union {...@@ -1365,12 +1365,20 @@ pub const Value = extern union {
1365 const b_field_index = b.castTag(.enum_field_index).?.data;1365 const b_field_index = b.castTag(.enum_field_index).?.data;
1366 return a_field_index == b_field_index;1366 return a_field_index == b_field_index;
1367 },1367 },
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 },
1368 .elem_ptr => @panic("TODO: Implement more pointer eql cases"),1374 .elem_ptr => @panic("TODO: Implement more pointer eql cases"),
1369 .field_ptr => @panic("TODO: Implement more pointer eql cases"),1375 .field_ptr => @panic("TODO: Implement more pointer eql cases"),
1370 .eu_payload_ptr => @panic("TODO: Implement more pointer eql cases"),1376 .eu_payload_ptr => @panic("TODO: Implement more pointer eql cases"),
1371 .opt_payload_ptr => @panic("TODO: Implement more pointer eql cases"),1377 .opt_payload_ptr => @panic("TODO: Implement more pointer eql cases"),
1372 else => {},1378 else => {},
1373 }1379 }
1380 } else if (a_tag == .null_value or b_tag == .null_value) {
1381 return false;
1374 }1382 }
13751383
1376 if (a.pointerDecl()) |a_decl| {1384 if (a.pointerDecl()) |a_decl| {
test/behavior/optional.zig+29
...@@ -44,3 +44,32 @@ test "optional pointer to size zero struct" {...@@ -44,3 +44,32 @@ test "optional pointer to size zero struct" {
44 var o: ?*EmptyStruct = &e;44 var o: ?*EmptyStruct = &e;
45 try expect(o != null);45 try expect(o != null);
46}46}
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;...@@ -3,27 +3,6 @@ const testing = std.testing;
3const expect = testing.expect;3const expect = testing.expect;
4const expectEqual = testing.expectEqual;4const 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
27test "address of unwrap optional" {6test "address of unwrap optional" {
28 const S = struct {7 const S = struct {
29 const Foo = struct {8 const Foo = struct {
...@@ -143,14 +122,6 @@ test "coerce an anon struct literal to optional struct" {...@@ -143,14 +122,6 @@ test "coerce an anon struct literal to optional struct" {
143 comptime try S.doTheTest();122 comptime try S.doTheTest();
144}123}
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
154test "0-bit child type coerced to optional return ptr result location" {125test "0-bit child type coerced to optional return ptr result location" {
155 const S = struct {126 const S = struct {
156 fn doTheTest() !void {127 fn doTheTest() !void {