| author | |
| committer | |
| log | 682cdeceaa7f55a14ad88ce01030888a9c18960e |
| tree | 1cc955c9090e9084509831491c70b3bcbdef02b3 |
| parent | 186126c2a4032424e1b1cdb8ac379fb2beab7429 |
* 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 | } |
| 4251 | 4251 | ||
| 4252 | pub 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 | |||
| 4266 | pub fn errorUnionType( | 4252 | pub 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); |
| 4112 | 4112 | ||
| 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 | } |
| 9677 | 9677 | ||
| 9678 | const result_ty = try Module.optionalType(sema.arena, elem_ty); | 9678 | const result_ty = try Type.optional(sema.arena, elem_ty); |
| 9679 | 9679 | ||
| 9680 | // special case zero bit types | 9680 | // 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 dynamic | 10517 | .@"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(); |
| 12798 | 12798 | ||
| 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 | } |
| 12880 | 12881 | ||
| 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 recover | 12920 | // 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 | } |
| 12908 | 12947 | ||
| 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 | } |
| 12911 | 12959 | ||
| 12912 | pub fn resolveTypeLayout( | 12960 | pub fn resolveTypeLayout( |
src/codegen/llvm.zig+68-49| ... | @@ -1430,7 +1430,7 @@ pub const FuncGen = struct { | ... | @@ -1430,7 +1430,7 @@ pub const FuncGen = struct { |
| 1430 | 1430 | ||
| 1431 | /// This stores the LLVM values used in a function, such that they can be referred to | 1431 | /// 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), |
| 1434 | 1434 | ||
| 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 | } |
| 1473 | 1473 | ||
| 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; | ||
| 1479 | 1477 | ||
| 1480 | // We have an LLVM value but we need to create a global constant and | 1478 | 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 | } |
| 1493 | 1497 | ||
| 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), |
| 1530 | 1534 | ||
| 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: on | 1624 | // 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 | } |
| 1624 | 1632 | ||
| ... | @@ -1722,8 +1730,7 @@ pub const FuncGen = struct { | ... | @@ -1722,8 +1730,7 @@ pub const FuncGen = struct { |
| 1722 | } | 1730 | } |
| 1723 | 1731 | ||
| 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; | ||
| 1727 | 1734 | ||
| 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 { |
| 1733 | 1740 | ||
| 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 | } |
| 2229 | 2236 | ||
| 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; | ||
| 2233 | 2245 | ||
| 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); |
| 2236 | 2248 | 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 | } | ||
| 2244 | 2264 | ||
| 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); |
| 2246 | 2267 | ||
| 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 | }; |
| 2251 | 2272 | ||
| 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 | } |
| 2254 | 2281 | ||
| 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 | } |
| 2270 | 2289 | ||
| 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 | } |
| 4033 | 4033 | ||
| 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 | } |
| 1375 | 1383 | ||
| 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 | |||
| 48 | test "equality compare optional pointers" { | ||
| 49 | try testNullPtrsEql(); | ||
| 50 | comptime try testNullPtrsEql(); | ||
| 51 | } | ||
| 52 | |||
| 53 | fn 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 | |||
| 69 | test "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; |
| 3 | const expect = testing.expect; | 3 | const expect = testing.expect; |
| 4 | const expectEqual = testing.expectEqual; | 4 | const expectEqual = testing.expectEqual; |
| 5 | 5 | ||
| 6 | test "equality compare nullable pointers" { | ||
| 7 | try testNullPtrsEql(); | ||
| 8 | comptime try testNullPtrsEql(); | ||
| 9 | } | ||
| 10 | |||
| 11 | fn 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 | |||
| 27 | test "address of unwrap optional" { | 6 | test "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 | } |
| 145 | 124 | ||
| 146 | test "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 | |||
| 154 | test "0-bit child type coerced to optional return ptr result location" { | 125 | test "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 { |