authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-11-16 13:59:53-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-11-16 14:01:07-07:00
log85e427e4b21d88a870b4a34e9562f2ce7ab65fb7
treec3655f0ea36fad5508f5652d86427a6bfb0da35e
parent4eaf3c665e8c0b028a7ea02a0fbcb1950c8b1a25

stage2: equality compare optional with non-optional


5 files changed, 165 insertions(+), 68 deletions(-)

src/codegen/llvm.zig+120-32
......@@ -1931,11 +1931,93 @@ pub const FuncGen = struct {
19311931 const lhs = try self.resolveInst(bin_op.lhs);
19321932 const rhs = try self.resolveInst(bin_op.rhs);
19331933 const operand_ty = self.air.typeOf(bin_op.lhs);
1934 var buffer: Type.Payload.Bits = undefined;
1934
1935 return self.cmp(lhs, rhs, operand_ty, op);
1936 }
1937
1938 fn cmp(
1939 self: *FuncGen,
1940 lhs: *const llvm.Value,
1941 rhs: *const llvm.Value,
1942 operand_ty: Type,
1943 op: math.CompareOperator,
1944 ) *const llvm.Value {
1945 var int_buffer: Type.Payload.Bits = undefined;
1946 var opt_buffer: Type.Payload.ElemType = undefined;
19351947
19361948 const int_ty = switch (operand_ty.zigTypeTag()) {
1937 .Enum => operand_ty.intTagType(&buffer),
1938 .Int, .Bool, .Pointer, .Optional, .ErrorSet => operand_ty,
1949 .Enum => operand_ty.intTagType(&int_buffer),
1950 .Int, .Bool, .Pointer, .ErrorSet => operand_ty,
1951 .Optional => blk: {
1952 const payload_ty = operand_ty.optionalChild(&opt_buffer);
1953 if (!payload_ty.hasCodeGenBits() or operand_ty.isPtrLikeOptional()) {
1954 break :blk operand_ty;
1955 }
1956 // We need to emit instructions to check for equality/inequality
1957 // of optionals that are not pointers.
1958 const is_by_ref = isByRef(operand_ty);
1959 const lhs_non_null = self.optIsNonNull(lhs, is_by_ref);
1960 const rhs_non_null = self.optIsNonNull(rhs, is_by_ref);
1961 const llvm_i2 = self.context.intType(2);
1962 const lhs_non_null_i2 = self.builder.buildZExt(lhs_non_null, llvm_i2, "");
1963 const rhs_non_null_i2 = self.builder.buildZExt(rhs_non_null, llvm_i2, "");
1964 const lhs_shifted = self.builder.buildShl(lhs_non_null_i2, llvm_i2.constInt(1, .False), "");
1965 const lhs_rhs_ored = self.builder.buildOr(lhs_shifted, rhs_non_null_i2, "");
1966 const both_null_block = self.context.appendBasicBlock(self.llvm_func, "BothNull");
1967 const mixed_block = self.context.appendBasicBlock(self.llvm_func, "Mixed");
1968 const both_pl_block = self.context.appendBasicBlock(self.llvm_func, "BothNonNull");
1969 const end_block = self.context.appendBasicBlock(self.llvm_func, "End");
1970 const llvm_switch = self.builder.buildSwitch(lhs_rhs_ored, mixed_block, 2);
1971 const llvm_i2_00 = llvm_i2.constInt(0b00, .False);
1972 const llvm_i2_11 = llvm_i2.constInt(0b11, .False);
1973 llvm_switch.addCase(llvm_i2_00, both_null_block);
1974 llvm_switch.addCase(llvm_i2_11, both_pl_block);
1975
1976 self.builder.positionBuilderAtEnd(both_null_block);
1977 _ = self.builder.buildBr(end_block);
1978
1979 self.builder.positionBuilderAtEnd(mixed_block);
1980 _ = self.builder.buildBr(end_block);
1981
1982 self.builder.positionBuilderAtEnd(both_pl_block);
1983 const lhs_payload = self.optPayloadHandle(lhs, is_by_ref);
1984 const rhs_payload = self.optPayloadHandle(rhs, is_by_ref);
1985 const payload_cmp = self.cmp(lhs_payload, rhs_payload, payload_ty, op);
1986 _ = self.builder.buildBr(end_block);
1987 const both_pl_block_end = self.builder.getInsertBlock();
1988
1989 self.builder.positionBuilderAtEnd(end_block);
1990 const incoming_blocks: [3]*const llvm.BasicBlock = .{
1991 both_null_block,
1992 mixed_block,
1993 both_pl_block_end,
1994 };
1995 const llvm_i1 = self.context.intType(1);
1996 const llvm_i1_0 = llvm_i1.constInt(0, .False);
1997 const llvm_i1_1 = llvm_i1.constInt(1, .False);
1998 const incoming_values: [3]*const llvm.Value = .{
1999 switch (op) {
2000 .eq => llvm_i1_1,
2001 .neq => llvm_i1_0,
2002 else => unreachable,
2003 },
2004 switch (op) {
2005 .eq => llvm_i1_0,
2006 .neq => llvm_i1_1,
2007 else => unreachable,
2008 },
2009 payload_cmp,
2010 };
2011
2012 const phi_node = self.builder.buildPhi(llvm_i1, "");
2013 comptime assert(incoming_values.len == incoming_blocks.len);
2014 phi_node.addIncoming(
2015 &incoming_values,
2016 &incoming_blocks,
2017 incoming_values.len,
2018 );
2019 return phi_node;
2020 },
19392021 .Float => {
19402022 const operation: llvm.RealPredicate = switch (op) {
19412023 .eq => .OEQ,
......@@ -2493,24 +2575,8 @@ pub const FuncGen = struct {
24932575 }
24942576 }
24952577
2496 if (operand_is_ptr or isByRef(optional_ty)) {
2497 const index_type = self.context.intType(32);
2498
2499 const indices: [2]*const llvm.Value = .{
2500 index_type.constNull(),
2501 index_type.constInt(1, .False),
2502 };
2503
2504 const field_ptr = self.builder.buildInBoundsGEP(operand, &indices, indices.len, "");
2505 const non_null_bit = self.builder.buildLoad(field_ptr, "");
2506 if (invert) {
2507 return self.builder.buildNot(non_null_bit, "");
2508 } else {
2509 return non_null_bit;
2510 }
2511 }
2512
2513 const non_null_bit = self.builder.buildExtractValue(operand, 1, "");
2578 const is_by_ref = operand_is_ptr or isByRef(optional_ty);
2579 const non_null_bit = self.optIsNonNull(operand, is_by_ref);
25142580 if (invert) {
25152581 return self.builder.buildNot(non_null_bit, "");
25162582 } else {
......@@ -2622,17 +2688,7 @@ pub const FuncGen = struct {
26222688 return operand;
26232689 }
26242690
2625 if (isByRef(payload_ty)) {
2626 // We have a pointer and we need to return a pointer to the first field.
2627 const index_type = self.context.intType(32);
2628 const indices: [2]*const llvm.Value = .{
2629 index_type.constNull(), // dereference the pointer
2630 index_type.constNull(), // first field is the payload
2631 };
2632 return self.builder.buildInBoundsGEP(operand, &indices, indices.len, "");
2633 }
2634
2635 return self.builder.buildExtractValue(operand, 0, "");
2691 return self.optPayloadHandle(operand, isByRef(payload_ty));
26362692 }
26372693
26382694 fn airErrUnionPayload(
......@@ -3748,6 +3804,38 @@ pub const FuncGen = struct {
37483804 }
37493805 }
37503806
3807 /// Assumes the optional is not pointer-like and payload has bits.
3808 fn optIsNonNull(self: *FuncGen, opt_handle: *const llvm.Value, is_by_ref: bool) *const llvm.Value {
3809 if (is_by_ref) {
3810 const index_type = self.context.intType(32);
3811
3812 const indices: [2]*const llvm.Value = .{
3813 index_type.constNull(),
3814 index_type.constInt(1, .False),
3815 };
3816
3817 const field_ptr = self.builder.buildInBoundsGEP(opt_handle, &indices, indices.len, "");
3818 return self.builder.buildLoad(field_ptr, "");
3819 }
3820
3821 return self.builder.buildExtractValue(opt_handle, 1, "");
3822 }
3823
3824 /// Assumes the optional is not pointer-like and payload has bits.
3825 fn optPayloadHandle(self: *FuncGen, opt_handle: *const llvm.Value, is_by_ref: bool) *const llvm.Value {
3826 if (is_by_ref) {
3827 // We have a pointer and we need to return a pointer to the first field.
3828 const index_type = self.context.intType(32);
3829 const indices: [2]*const llvm.Value = .{
3830 index_type.constNull(), // dereference the pointer
3831 index_type.constNull(), // first field is the payload
3832 };
3833 return self.builder.buildInBoundsGEP(opt_handle, &indices, indices.len, "");
3834 }
3835
3836 return self.builder.buildExtractValue(opt_handle, 0, "");
3837 }
3838
37513839 fn callFloor(self: *FuncGen, arg: *const llvm.Value, ty: Type) !*const llvm.Value {
37523840 return self.callFloatUnary(arg, ty, "floor");
37533841 }
src/codegen/llvm/bindings.zig+6-1
......@@ -98,7 +98,12 @@ pub const Value = opaque {
9898 extern fn LLVMAppendExistingBasicBlock(Fn: *const Value, BB: *const BasicBlock) void;
9999
100100 pub const addIncoming = LLVMAddIncoming;
101 extern fn LLVMAddIncoming(PhiNode: *const Value, IncomingValues: [*]*const Value, IncomingBlocks: [*]*const BasicBlock, Count: c_uint) void;
101 extern fn LLVMAddIncoming(
102 PhiNode: *const Value,
103 IncomingValues: [*]const *const Value,
104 IncomingBlocks: [*]const *const BasicBlock,
105 Count: c_uint,
106 ) void;
102107
103108 pub const getNextInstruction = LLVMGetNextInstruction;
104109 extern fn LLVMGetNextInstruction(Inst: *const Value) ?*const Value;
src/type.zig+5-1
......@@ -175,7 +175,11 @@ pub const Type = extern union {
175175 => false,
176176
177177 .Pointer => is_equality_cmp or ty.isCPtr(),
178 .Optional => is_equality_cmp and ty.isPtrLikeOptional(),
178 .Optional => {
179 if (!is_equality_cmp) return false;
180 var buf: Payload.ElemType = undefined;
181 return ty.optionalChild(&buf).isSelfComparable(is_equality_cmp);
182 },
179183 };
180184 }
181185
test/behavior/optional.zig+34
......@@ -103,3 +103,37 @@ test "nested optional field in struct" {
103103 };
104104 try expect(s.x.?.y == 127);
105105}
106
107test "equality compare optional with non-optional" {
108 try test_cmp_optional_non_optional();
109 comptime try test_cmp_optional_non_optional();
110}
111
112fn test_cmp_optional_non_optional() !void {
113 var ten: i32 = 10;
114 var opt_ten: ?i32 = 10;
115 var five: i32 = 5;
116 var int_n: ?i32 = null;
117
118 try expect(int_n != ten);
119 try expect(opt_ten == ten);
120 try expect(opt_ten != five);
121
122 // test evaluation is always lexical
123 // ensure that the optional isn't always computed before the non-optional
124 var mutable_state: i32 = 0;
125 _ = blk1: {
126 mutable_state += 1;
127 break :blk1 @as(?f64, 10.0);
128 } != blk2: {
129 try expect(mutable_state == 1);
130 break :blk2 @as(f64, 5.0);
131 };
132 _ = blk1: {
133 mutable_state += 1;
134 break :blk1 @as(f64, 10.0);
135 } != blk2: {
136 try expect(mutable_state == 2);
137 break :blk2 @as(?f64, 5.0);
138 };
139}
test/behavior/optional_stage1.zig-34
......@@ -3,40 +3,6 @@ const testing = std.testing;
33const expect = testing.expect;
44const expectEqual = testing.expectEqual;
55
6test "equality compare optional with non-optional" {
7 try test_cmp_optional_non_optional();
8 comptime try test_cmp_optional_non_optional();
9}
10
11fn test_cmp_optional_non_optional() !void {
12 var ten: i32 = 10;
13 var opt_ten: ?i32 = 10;
14 var five: i32 = 5;
15 var int_n: ?i32 = null;
16
17 try expect(int_n != ten);
18 try expect(opt_ten == ten);
19 try expect(opt_ten != five);
20
21 // test evaluation is always lexical
22 // ensure that the optional isn't always computed before the non-optional
23 var mutable_state: i32 = 0;
24 _ = blk1: {
25 mutable_state += 1;
26 break :blk1 @as(?f64, 10.0);
27 } != blk2: {
28 try expect(mutable_state == 1);
29 break :blk2 @as(f64, 5.0);
30 };
31 _ = blk1: {
32 mutable_state += 1;
33 break :blk1 @as(f64, 10.0);
34 } != blk2: {
35 try expect(mutable_state == 2);
36 break :blk2 @as(?f64, 5.0);
37 };
38}
39
406test "unwrap function call with optional pointer return value" {
417 const S = struct {
428 fn entry() !void {