From 50bbb34594eedf7a978c00edb525bcea472b554b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 8 May 2019 16:06:34 -0400 Subject: [PATCH] C pointers support `null` See #1967 --- src/codegen.cpp | 22 +++---- src/ir.cpp | 97 ++++++++++++++++++++++++++----- test/compile_errors.zig | 2 +- test/stage1/behavior/pointers.zig | 28 +++++++++ 4 files changed, 121 insertions(+), 28 deletions(-) diff --git a/src/codegen.cpp b/src/codegen.cpp index 396dfb1f8..712ee908c 100644 --- a/src/codegen.cpp +++ b/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, diff --git a/src/ir.cpp b/src/ir.cpp index 91f931462..1b0fbd1f7 100644 --- a/src/ir.cpp +++ b/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(&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; } diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 60d1a9ac8..a367532ef 100644 --- a/test/compile_errors.zig +++ b/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( diff --git a/test/stage1/behavior/pointers.zig b/test/stage1/behavior/pointers.zig index be4771835..8b1f7d0cb 100644 --- a/test/stage1/behavior/pointers.zig +++ b/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); +}