authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-01-20 13:34:47+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-01-21 00:33:32+00:00
log8bcb578507908e17e2081bb03f8c51eb508d51db
tree1e9caba23ae65432f8aa5abaa55f070e2b5220eb
parentb9198b708f8accb41fdb3f11bf635d63fbff461d

Sema: fix `is_non_null_ptr` handling for runtime-known pointers

We can still often determine a comptime result based on the type, even if the pointer is runtime-known. Also, we previously used load -> is non null instead of AIR `is_non_null_ptr` if the pointer is comptime-known, but that's a bad heuristic. Instead, we should check for the pointer to be comptime-known, *and* for the load to be comptime-known, and only in that case should we call `Sema.analyzeIsNonNull`. Resolves: #22556

4 files changed, 42 insertions(+), 21 deletions(-)

src/Sema.zig+17-18
...@@ -17477,10 +17477,10 @@ fn zirCmpEq(...@@ -17477,10 +17477,10 @@ fn zirCmpEq(
1747717477
17478 // comparing null with optionals17478 // comparing null with optionals
17479 if (lhs_ty_tag == .null and (rhs_ty_tag == .optional or rhs_ty.isCPtr(zcu))) {17479 if (lhs_ty_tag == .null and (rhs_ty_tag == .optional or rhs_ty.isCPtr(zcu))) {
17480 return sema.analyzeIsNull(block, src, rhs, op == .neq);17480 return sema.analyzeIsNull(block, rhs, op == .neq);
17481 }17481 }
17482 if (rhs_ty_tag == .null and (lhs_ty_tag == .optional or lhs_ty.isCPtr(zcu))) {17482 if (rhs_ty_tag == .null and (lhs_ty_tag == .optional or lhs_ty.isCPtr(zcu))) {
17483 return sema.analyzeIsNull(block, src, lhs, op == .neq);17483 return sema.analyzeIsNull(block, lhs, op == .neq);
17484 }17484 }
1748517485
17486 if (lhs_ty_tag == .null or rhs_ty_tag == .null) {17486 if (lhs_ty_tag == .null or rhs_ty_tag == .null) {
...@@ -19326,7 +19326,7 @@ fn zirIsNonNull(...@@ -19326,7 +19326,7 @@ fn zirIsNonNull(
19326 const src = block.nodeOffset(inst_data.src_node);19326 const src = block.nodeOffset(inst_data.src_node);
19327 const operand = try sema.resolveInst(inst_data.operand);19327 const operand = try sema.resolveInst(inst_data.operand);
19328 try sema.checkNullableType(block, src, sema.typeOf(operand));19328 try sema.checkNullableType(block, src, sema.typeOf(operand));
19329 return sema.analyzeIsNull(block, src, operand, true);19329 return sema.analyzeIsNull(block, operand, true);
19330}19330}
1933119331
19332fn zirIsNonNullPtr(19332fn zirIsNonNullPtr(
...@@ -19342,12 +19342,17 @@ fn zirIsNonNullPtr(...@@ -19342,12 +19342,17 @@ fn zirIsNonNullPtr(
19342 const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;19342 const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
19343 const src = block.nodeOffset(inst_data.src_node);19343 const src = block.nodeOffset(inst_data.src_node);
19344 const ptr = try sema.resolveInst(inst_data.operand);19344 const ptr = try sema.resolveInst(inst_data.operand);
19345 const ptr_ty = sema.typeOf(ptr);
19345 try sema.checkNullableType(block, src, sema.typeOf(ptr).elemType2(zcu));19346 try sema.checkNullableType(block, src, sema.typeOf(ptr).elemType2(zcu));
19346 if ((try sema.resolveValue(ptr)) == null) {19347 if (try sema.resolveValue(ptr)) |ptr_val| {
19347 return block.addUnOp(.is_non_null_ptr, ptr);19348 if (try sema.pointerDeref(block, src, ptr_val, ptr_ty)) |loaded_val| {
19349 return sema.analyzeIsNull(block, Air.internedToRef(loaded_val.toIntern()), true);
19350 }
19348 }19351 }
19349 const loaded = try sema.analyzeLoad(block, src, ptr, src);19352 if (ptr_ty.childType(zcu).isNullFromType(zcu)) |is_null| {
19350 return sema.analyzeIsNull(block, src, loaded, true);19353 return if (is_null) .bool_false else .bool_true;
19354 }
19355 return block.addUnOp(.is_non_null_ptr, ptr);
19351}19356}
1935219357
19353fn checkErrorType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !void {19358fn checkErrorType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !void {
...@@ -32405,7 +32410,6 @@ fn analyzeSliceLen(...@@ -32405,7 +32410,6 @@ fn analyzeSliceLen(
32405fn analyzeIsNull(32410fn analyzeIsNull(
32406 sema: *Sema,32411 sema: *Sema,
32407 block: *Block,32412 block: *Block,
32408 src: LazySrcLoc,
32409 operand: Air.Inst.Ref,32413 operand: Air.Inst.Ref,
32410 invert_logic: bool,32414 invert_logic: bool,
32411) CompileError!Air.Inst.Ref {32415) CompileError!Air.Inst.Ref {
...@@ -32421,15 +32425,10 @@ fn analyzeIsNull(...@@ -32421,15 +32425,10 @@ fn analyzeIsNull(
32421 return if (bool_value) .bool_true else .bool_false;32425 return if (bool_value) .bool_true else .bool_false;
32422 }32426 }
3242332427
32424 const inverted_non_null_res: Air.Inst.Ref = if (invert_logic) .bool_true else .bool_false;32428 if (sema.typeOf(operand).isNullFromType(zcu)) |is_null| {
32425 const operand_ty = sema.typeOf(operand);32429 const result = is_null != invert_logic;
32426 if (operand_ty.zigTypeTag(zcu) == .optional and operand_ty.optionalChild(zcu).zigTypeTag(zcu) == .noreturn) {32430 return if (result) .bool_true else .bool_false;
32427 return inverted_non_null_res;
32428 }32431 }
32429 if (operand_ty.zigTypeTag(zcu) != .optional and !operand_ty.isPtrLikeOptional(zcu)) {
32430 return inverted_non_null_res;
32431 }
32432 try sema.requireRuntimeBlock(block, src, null);
32433 const air_tag: Air.Inst.Tag = if (invert_logic) .is_non_null else .is_null;32432 const air_tag: Air.Inst.Tag = if (invert_logic) .is_non_null else .is_null;
32434 return block.addUnOp(air_tag, operand);32433 return block.addUnOp(air_tag, operand);
32435}32434}
...@@ -33007,7 +33006,7 @@ fn analyzeSlice(...@@ -33007,7 +33006,7 @@ fn analyzeSlice(
33007 if (block.wantSafety()) {33006 if (block.wantSafety()) {
33008 // requirement: slicing C ptr is non-null33007 // requirement: slicing C ptr is non-null
33009 if (ptr_ptr_child_ty.isCPtr(zcu)) {33008 if (ptr_ptr_child_ty.isCPtr(zcu)) {
33010 const is_non_null = try sema.analyzeIsNull(block, ptr_src, ptr, true);33009 const is_non_null = try sema.analyzeIsNull(block, ptr, true);
33011 try sema.addSafetyCheck(block, src, is_non_null, .unwrap_null);33010 try sema.addSafetyCheck(block, src, is_non_null, .unwrap_null);
33012 }33011 }
3301333012
...@@ -33065,7 +33064,7 @@ fn analyzeSlice(...@@ -33065,7 +33064,7 @@ fn analyzeSlice(
33065 if (block.wantSafety()) {33064 if (block.wantSafety()) {
33066 // requirement: slicing C ptr is non-null33065 // requirement: slicing C ptr is non-null
33067 if (ptr_ptr_child_ty.isCPtr(zcu)) {33066 if (ptr_ptr_child_ty.isCPtr(zcu)) {
33068 const is_non_null = try sema.analyzeIsNull(block, ptr_src, ptr, true);33067 const is_non_null = try sema.analyzeIsNull(block, ptr, true);
33069 try sema.addSafetyCheck(block, src, is_non_null, .unwrap_null);33068 try sema.addSafetyCheck(block, src, is_non_null, .unwrap_null);
33070 }33069 }
3307133070
src/Type.zig+10
...@@ -4114,6 +4114,16 @@ pub fn containerTypeName(ty: Type, ip: *const InternPool) InternPool.NullTermina...@@ -4114,6 +4114,16 @@ pub fn containerTypeName(ty: Type, ip: *const InternPool) InternPool.NullTermina
4114 };4114 };
4115}4115}
41164116
4117/// Returns `true` if a value of this type is always `null`.
4118/// Returns `false` if a value of this type is neve `null`.
4119/// Returns `null` otherwise.
4120pub fn isNullFromType(ty: Type, zcu: *const Zcu) ?bool {
4121 if (ty.zigTypeTag(zcu) != .optional and !ty.isCPtr(zcu)) return false;
4122 const child = ty.optionalChild(zcu);
4123 if (child.zigTypeTag(zcu) == .noreturn) return true; // `?noreturn` is always null
4124 return null;
4125}
4126
4117pub const @"u1": Type = .{ .ip_index = .u1_type };4127pub const @"u1": Type = .{ .ip_index = .u1_type };
4118pub const @"u8": Type = .{ .ip_index = .u8_type };4128pub const @"u8": Type = .{ .ip_index = .u8_type };
4119pub const @"u16": Type = .{ .ip_index = .u16_type };4129pub const @"u16": Type = .{ .ip_index = .u16_type };
test/behavior/optional.zig+14
...@@ -498,6 +498,20 @@ test "optional of noreturn used with orelse" {...@@ -498,6 +498,20 @@ test "optional of noreturn used with orelse" {
498 try expect(val == 123);498 try expect(val == 123);
499}499}
500500
501test "mutable optional of noreturn" {
502 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
503
504 var a: ?noreturn = null;
505 if (a) |*ptr| {
506 _ = ptr;
507 @compileError("bad");
508 } else {
509 // this is what we expect to hit
510 return;
511 }
512 @compileError("bad");
513}
514
501test "orelse on C pointer" {515test "orelse on C pointer" {
502516
503 // TODO https://github.com/ziglang/zig/issues/6597517 // TODO https://github.com/ziglang/zig/issues/6597
test/cases/compile_errors/branch_in_comptime_only_scope_uses_condbr_inline.zig+1-3
...@@ -15,11 +15,9 @@ pub export fn entry2() void {...@@ -15,11 +15,9 @@ pub export fn entry2() void {
15}15}
1616
17// error17// error
18// backend=stage2
19// target=native
20//18//
21// :5:15: error: unable to evaluate comptime expression19// :5:15: error: unable to evaluate comptime expression
22// :5:13: note: operation is runtime due to this operand20// :5:13: note: operation is runtime due to this operand
23// :4:72: note: '@shuffle' mask must be comptime-known21// :4:72: note: '@shuffle' mask must be comptime-known
24// :13:11: error: unable to evaluate comptime expression22// :13:11: error: unable to resolve comptime value
25// :12:72: note: '@shuffle' mask must be comptime-known23// :12:72: note: '@shuffle' mask must be comptime-known