Commit 50bbb34594

Andrew Kelley <andrew@ziglang.org>
2019-05-08 22:06:34
C pointers support `null`
See #1967
1 parent be7cacf
Changed files (4)
src/codegen.cpp
@@ -4023,19 +4023,19 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru
 }
 
 static LLVMValueRef gen_non_null_bit(CodeGen *g, ZigType *maybe_type, LLVMValueRef maybe_handle) {
-    assert(maybe_type->id == ZigTypeIdOptional);
+    assert(maybe_type->id == ZigTypeIdOptional ||
+            (maybe_type->id == ZigTypeIdPointer && maybe_type->data.pointer.allow_zero));
+
     ZigType *child_type = maybe_type->data.maybe.child_type;
-    if (!type_has_bits(child_type)) {
+    if (!type_has_bits(child_type))
         return maybe_handle;
-    } else {
-        bool is_scalar = !handle_is_ptr(maybe_type);
-        if (is_scalar) {
-            return LLVMBuildICmp(g->builder, LLVMIntNE, maybe_handle, LLVMConstNull(get_llvm_type(g, maybe_type)), "");
-        } else {
-            LLVMValueRef maybe_field_ptr = LLVMBuildStructGEP(g->builder, maybe_handle, maybe_null_index, "");
-            return gen_load_untyped(g, maybe_field_ptr, 0, false, "");
-        }
-    }
+
+    bool is_scalar = !handle_is_ptr(maybe_type);
+    if (is_scalar)
+        return LLVMBuildICmp(g->builder, LLVMIntNE, maybe_handle, LLVMConstNull(get_llvm_type(g, maybe_type)), "");
+
+    LLVMValueRef maybe_field_ptr = LLVMBuildStructGEP(g->builder, maybe_handle, maybe_null_index, "");
+    return gen_load_untyped(g, maybe_field_ptr, 0, false, "");
 }
 
 static LLVMValueRef ir_render_test_non_null(CodeGen *g, IrExecutable *executable,
src/ir.cpp
@@ -10283,6 +10283,12 @@ static IrInstruction *ir_const_bool(IrAnalyze *ira, IrInstruction *source_instru
     return result;
 }
 
+static IrInstruction *ir_const_undef(IrAnalyze *ira, IrInstruction *source_instruction, ZigType *ty) {
+    IrInstruction *result = ir_const(ira, source_instruction, ty);
+    result->value.special = ConstValSpecialUndef;
+    return result;
+}
+
 static IrInstruction *ir_const_void(IrAnalyze *ira, IrInstruction *source_instruction) {
     return ir_const(ira, source_instruction, ira->codegen->builtin_types.entry_void);
 }
@@ -10596,19 +10602,34 @@ static IrInstruction *ir_analyze_null_to_maybe(IrAnalyze *ira, IrInstruction *so
     assert(instr_is_comptime(value));
 
     ConstExprValue *val = ir_resolve_const(ira, value, UndefBad);
-    assert(val);
+    assert(val != nullptr);
 
-    IrInstructionConst *const_instruction = ir_create_instruction<IrInstructionConst>(&ira->new_irb, source_instr->scope, source_instr->source_node);
-    const_instruction->base.value.special = ConstValSpecialStatic;
+    IrInstruction *result = ir_const(ira, source_instr, wanted_type);
+    result->value.special = ConstValSpecialStatic;
     if (get_codegen_ptr_type(wanted_type) != nullptr) {
-        const_instruction->base.value.data.x_ptr.special = ConstPtrSpecialNull;
+        result->value.data.x_ptr.special = ConstPtrSpecialNull;
     } else if (is_opt_err_set(wanted_type)) {
-        const_instruction->base.value.data.x_err_set = nullptr;
+        result->value.data.x_err_set = nullptr;
     } else {
-        const_instruction->base.value.data.x_optional = nullptr;
+        result->value.data.x_optional = nullptr;
     }
-    const_instruction->base.value.type = wanted_type;
-    return &const_instruction->base;
+    return result;
+}
+
+static IrInstruction *ir_analyze_null_to_c_pointer(IrAnalyze *ira, IrInstruction *source_instr,
+        IrInstruction *value, ZigType *wanted_type)
+{
+    assert(wanted_type->id == ZigTypeIdPointer);
+    assert(wanted_type->data.pointer.ptr_len == PtrLenC);
+    assert(instr_is_comptime(value));
+
+    ConstExprValue *val = ir_resolve_const(ira, value, UndefBad);
+    assert(val != nullptr);
+
+    IrInstruction *result = ir_const(ira, source_instr, wanted_type);
+    result->value.data.x_ptr.special = ConstPtrSpecialNull;
+    result->value.data.x_ptr.mut = ConstPtrMutComptimeConst;
+    return result;
 }
 
 static IrInstruction *ir_get_ref(IrAnalyze *ira, IrInstruction *source_instruction, IrInstruction *value,
@@ -11610,6 +11631,13 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst
         return ir_analyze_null_to_maybe(ira, source_instr, value, wanted_type);
     }
 
+    // cast from null literal to C pointer
+    if (wanted_type->id == ZigTypeIdPointer && wanted_type->data.pointer.ptr_len == PtrLenC &&
+        actual_type->id == ZigTypeIdNull)
+    {
+        return ir_analyze_null_to_c_pointer(ira, source_instr, value, wanted_type);
+    }
+
     // cast from [N]T to E![]const T
     if (wanted_type->id == ZigTypeIdErrorUnion &&
         is_slice(wanted_type->data.error_union.payload_type) &&
@@ -12227,14 +12255,12 @@ static IrInstruction *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp *
 
     IrBinOp op_id = bin_op_instruction->op_id;
     bool is_equality_cmp = (op_id == IrBinOpCmpEq || op_id == IrBinOpCmpNotEq);
-    if (is_equality_cmp &&
+    if (is_equality_cmp && op1->value.type->id == ZigTypeIdNull && op2->value.type->id == ZigTypeIdNull) {
+        return ir_const_bool(ira, &bin_op_instruction->base, (op_id == IrBinOpCmpEq));
+    } else if (is_equality_cmp &&
         ((op1->value.type->id == ZigTypeIdNull && op2->value.type->id == ZigTypeIdOptional) ||
-        (op2->value.type->id == ZigTypeIdNull && op1->value.type->id == ZigTypeIdOptional) ||
-        (op1->value.type->id == ZigTypeIdNull && op2->value.type->id == ZigTypeIdNull)))
+        (op2->value.type->id == ZigTypeIdNull && op1->value.type->id == ZigTypeIdOptional)))
     {
-        if (op1->value.type->id == ZigTypeIdNull && op2->value.type->id == ZigTypeIdNull) {
-            return ir_const_bool(ira, &bin_op_instruction->base, (op_id == IrBinOpCmpEq));
-        }
         IrInstruction *maybe_op;
         if (op1->value.type->id == ZigTypeIdNull) {
             maybe_op = op2;
@@ -12256,6 +12282,44 @@ static IrInstruction *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp *
             source_node, maybe_op);
         is_non_null->value.type = ira->codegen->builtin_types.entry_bool;
 
+        if (op_id == IrBinOpCmpEq) {
+            IrInstruction *result = ir_build_bool_not(&ira->new_irb, bin_op_instruction->base.scope,
+                bin_op_instruction->base.source_node, is_non_null);
+            result->value.type = ira->codegen->builtin_types.entry_bool;
+            return result;
+        } else {
+            return is_non_null;
+        }
+    } else if (is_equality_cmp && 
+        ((op1->value.type->id == ZigTypeIdNull && op2->value.type->id == ZigTypeIdPointer &&
+            op2->value.type->data.pointer.ptr_len == PtrLenC) ||
+        (op2->value.type->id == ZigTypeIdNull && op1->value.type->id == ZigTypeIdPointer &&
+            op1->value.type->data.pointer.ptr_len == PtrLenC)))
+    {
+        IrInstruction *c_ptr_op;
+        if (op1->value.type->id == ZigTypeIdNull) {
+            c_ptr_op = op2;
+        } else if (op2->value.type->id == ZigTypeIdNull) {
+            c_ptr_op = op1;
+        } else {
+            zig_unreachable();
+        }
+        if (instr_is_comptime(c_ptr_op)) {
+            ConstExprValue *c_ptr_val = ir_resolve_const(ira, c_ptr_op, UndefOk);
+            if (!c_ptr_val)
+                return ira->codegen->invalid_instruction;
+            if (c_ptr_val->special == ConstValSpecialUndef)
+                return ir_const_undef(ira, &bin_op_instruction->base, ira->codegen->builtin_types.entry_bool);
+            bool is_null = c_ptr_val->data.x_ptr.special == ConstPtrSpecialNull ||
+                (c_ptr_val->data.x_ptr.special == ConstPtrSpecialHardCodedAddr &&
+                    c_ptr_val->data.x_ptr.data.hard_coded_addr.addr == 0);
+            bool bool_result = (op_id == IrBinOpCmpEq) ? is_null : !is_null;
+            return ir_const_bool(ira, &bin_op_instruction->base, bool_result);
+        }
+        IrInstruction *is_non_null = ir_build_test_nonnull(&ira->new_irb, bin_op_instruction->base.scope,
+            source_node, c_ptr_op);
+        is_non_null->value.type = ira->codegen->builtin_types.entry_bool;
+
         if (op_id == IrBinOpCmpEq) {
             IrInstruction *result = ir_build_bool_not(&ira->new_irb, bin_op_instruction->base.scope,
                 bin_op_instruction->base.source_node, is_non_null);
@@ -12265,8 +12329,9 @@ static IrInstruction *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp *
             return is_non_null;
         }
     } else if (op1->value.type->id == ZigTypeIdNull || op2->value.type->id == ZigTypeIdNull) {
-        ir_add_error_node(ira, source_node, buf_sprintf("only optionals (not '%s') can compare to null",
-            buf_ptr(&(op1->value.type->id == ZigTypeIdNull ? op2->value.type->name : op1->value.type->name))));
+        ZigType *non_null_type = (op1->value.type->id == ZigTypeIdNull) ? op2->value.type : op1->value.type;
+        ir_add_error_node(ira, source_node, buf_sprintf("comparison of '%s' with null",
+            buf_ptr(&non_null_type->name)));
         return ira->codegen->invalid_instruction;
     }
 
test/stage1/behavior/pointers.zig
@@ -150,3 +150,31 @@ test "allowzero pointer and slice" {
     expect(@typeInfo(@typeOf(ptr)).Pointer.is_allowzero);
     expect(@typeInfo(@typeOf(slice)).Pointer.is_allowzero);
 }
+
+test "assign null directly to C pointer and test null equality" {
+    var x: [*c]i32 = null;
+    expect(x == null);
+    expect(null == x);
+    expect(!(x != null));
+    expect(!(null != x));
+
+    const y: [*c]i32 = null;
+    expect(y == null);
+    expect(null == y);
+    expect(!(y != null));
+    expect(!(null != y));
+
+    var n: i32 = 1234;
+    var x1: [*c]i32 = &n;
+    expect(!(x1 == null));
+    expect(!(null == x1));
+    expect(x1 != null);
+    expect(null != x1);
+
+    const nc: i32 = 1234;
+    const y1: [*c]const i32 = &nc;
+    expect(!(y1 == null));
+    expect(!(null == y1));
+    expect(y1 != null);
+    expect(null != y1);
+}
test/compile_errors.zig
@@ -863,7 +863,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
         \\    _ = &x == null;
         \\}
     ,
-        "tmp.zig:3:12: error: only optionals (not '*i32') can compare to null",
+        "tmp.zig:3:12: error: comparison of '*i32' with null",
     );
 
     cases.add(