Commit 5eaead6a56

Andrew Kelley <andrew@ziglang.org>
2019-03-25 17:55:45
implement allowzero pointer attribute
closes #1953 only needed for freestanding targets. also adds safety for `@intToPtr` when the address is zero.
1 parent 3306e43
doc/docgen.zig
@@ -779,6 +779,7 @@ fn tokenizeAndPrintRaw(docgen_tokenizer: *Tokenizer, out: var, source_token: Tok
             std.zig.Token.Id.Keyword_use,
             std.zig.Token.Id.Keyword_var,
             std.zig.Token.Id.Keyword_volatile,
+            std.zig.Token.Id.Keyword_allowzero,
             std.zig.Token.Id.Keyword_while,
             => {
                 try out.write("<span class=\"tok-kw\">");
doc/langref.html.in
@@ -1721,7 +1721,7 @@ test "comptime @intToPtr" {
     }
 }
       {#code_end#}
-      {#see_also|Optional Pointers|@intToPtr|@ptrToInt#}
+      {#see_also|Optional Pointers|@intToPtr|@ptrToInt|C Pointers|Pointers to Zero Bit Types#}
       {#header_open|volatile#}
       <p>Loads and stores are assumed to not have side effects. If a given load or store
       should have side effects, such as Memory Mapped Input/Output (MMIO), use {#syntax#}volatile{#endsyntax#}.
@@ -1850,7 +1850,25 @@ fn foo(bytes: []u8) u32 {
 }
       {#code_end#}
       {#header_close#}
-      {#see_also|C Pointers|Pointers to Zero Bit Types#}
+
+      {#header_open|allowzero#}
+      <p>
+      This pointer attribute allows a pointer to have address zero. This is only ever needed on the
+      freestanding OS target, where the address zero is mappable. In this code example, if the pointer
+      did not have the {#syntax#}allowzero{#endsyntax#} attribute, this would be a
+      {#link|Pointer Cast Invalid Null#} panic:
+      </p>
+      {#code_begin|test|allowzero#}
+const std = @import("std");
+const assert = std.debug.assert;
+
+test "allowzero" {
+    var zero: usize = 0;
+    var ptr = @intToPtr(*allowzero i32, zero);
+    assert(@ptrToInt(ptr) == 0);
+}
+      {#code_end#}
+      {#header_close#}
       {#header_close#}
 
       {#header_open|Slices#}
@@ -6635,9 +6653,13 @@ fn add(a: i32, b: i32) i32 { return a + b; }
       {#header_close#}
 
       {#header_open|@intToPtr#}
-      <pre>{#syntax#}@intToPtr(comptime DestType: type, int: usize) DestType{#endsyntax#}</pre>
+      <pre>{#syntax#}@intToPtr(comptime DestType: type, address: usize) DestType{#endsyntax#}</pre>
+      <p>
+      Converts an integer to a {#link|pointer|Pointers#}. To convert the other way, use {#link|@ptrToInt#}.
+      </p>
       <p>
-      Converts an integer to a pointer. To convert the other way, use {#link|@ptrToInt#}.
+      If the destination pointer type does not allow address zero and {#syntax#}address{#endsyntax#}
+      is zero, this invokes safety-checked {#link|Undefined Behavior#}.
       </p>
       {#header_close#}
 
@@ -8128,6 +8150,11 @@ fn bar(f: *Foo) void {
       {#header_close#}
 
       {#header_open|Pointer Cast Invalid Null#}
+      <p>
+      This happens when casting a pointer with the address 0 to a pointer which may not have the address 0.
+      For example, {#link|C Pointers#}, {#link|Optional Pointers#}, and {#link|allowzero#} pointers
+      allow address zero, but normal {#link|Pointers#} do not.
+      </p>
       <p>At compile-time:</p>
       {#code_begin|test_err|null pointer casted to type#}
 comptime {
@@ -8988,7 +9015,7 @@ Available libcs:
       <ul>
       <li>Linux x86_64</li>
       <li>Windows x86_64</li>
-      <li>MacOS x86_64</li>
+      <li>macOS x86_64</li>
       </ul>
       {#header_close#}
       {#header_open|Style Guide#}
@@ -9412,8 +9439,8 @@ PrefixOp
 PrefixTypeOp
     &lt;- QUESTIONMARK
      / KEYWORD_promise MINUSRARROW
-     / ArrayTypeStart (ByteAlign / KEYWORD_const / KEYWORD_volatile)*
-     / PtrTypeStart (KEYWORD_align LPAREN Expr (COLON INTEGER COLON INTEGER)? RPAREN / KEYWORD_const / KEYWORD_volatile)*
+     / ArrayTypeStart (ByteAlign / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
+     / PtrTypeStart (KEYWORD_align LPAREN Expr (COLON INTEGER COLON INTEGER)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
 
 SuffixOp
     &lt;- LBRACKET Expr (DOT2 Expr?)? RBRACKET
@@ -9564,6 +9591,7 @@ TILDE                &lt;- '~'                skip
 
 end_of_word &lt;- ![a-zA-Z0-9_] skip
 KEYWORD_align       &lt;- 'align'       end_of_word
+KEYWORD_allowzero   &lt;- 'allowzero'   end_of_word
 KEYWORD_and         &lt;- 'and'         end_of_word
 KEYWORD_anyerror    &lt;- 'anyerror'    end_of_word
 KEYWORD_asm         &lt;- 'asm'         end_of_word
@@ -9614,7 +9642,7 @@ KEYWORD_var         &lt;- 'var'         end_of_word
 KEYWORD_volatile    &lt;- 'volatile'    end_of_word
 KEYWORD_while       &lt;- 'while'       end_of_word
 
-keyword &lt;- KEYWORD_align / KEYWORD_and / KEYWORD_anyerror / KEYWORD_asm
+keyword &lt;- KEYWORD_align / KEYWORD_and / KEYWORD_allowzero / KEYWORD_anyerror / KEYWORD_asm
          / KEYWORD_async / KEYWORD_await / KEYWORD_break / KEYWORD_cancel
          / KEYWORD_catch / KEYWORD_comptime / KEYWORD_const / KEYWORD_continue
          / KEYWORD_defer / KEYWORD_else / KEYWORD_enum / KEYWORD_errdefer
src/all_types.hpp
@@ -2668,7 +2668,7 @@ struct IrInstructionPtrType {
     PtrLen ptr_len;
     bool is_const;
     bool is_volatile;
-    bool allow_zero;
+    bool is_allow_zero;
 };
 
 struct IrInstructionPromiseType {
@@ -2684,7 +2684,7 @@ struct IrInstructionSliceType {
     IrInstruction *child_type;
     bool is_const;
     bool is_volatile;
-    bool allow_zero;
+    bool is_allow_zero;
 };
 
 struct IrInstructionGlobalAsm {
src/analyze.cpp
@@ -418,11 +418,9 @@ static const char *ptr_len_to_star_str(PtrLen ptr_len) {
 
 ZigType *get_pointer_to_type_extra(CodeGen *g, ZigType *child_type, bool is_const,
         bool is_volatile, PtrLen ptr_len, uint32_t byte_alignment,
-        uint32_t bit_offset_in_host, uint32_t host_int_bytes)
+        uint32_t bit_offset_in_host, uint32_t host_int_bytes, bool allow_zero)
 {
-    // TODO when implementing https://github.com/ziglang/zig/issues/1953
-    // move this to a parameter
-    bool allow_zero = (ptr_len == PtrLenC);
+    assert(ptr_len != PtrLenC || allow_zero);
     assert(!type_is_invalid(child_type));
     assert(ptr_len == PtrLenSingle || child_type->id != ZigTypeIdOpaque);
 
@@ -505,7 +503,7 @@ ZigType *get_pointer_to_type_extra(CodeGen *g, ZigType *child_type, bool is_cons
             bit_offset_in_host != 0 || allow_zero)
         {
             ZigType *peer_type = get_pointer_to_type_extra(g, child_type, false, false,
-                    PtrLenSingle, 0, 0, host_int_bytes);
+                    PtrLenSingle, 0, 0, host_int_bytes, false);
             entry->type_ref = peer_type->type_ref;
             entry->di_type = peer_type->di_type;
         } else {
@@ -548,7 +546,7 @@ ZigType *get_pointer_to_type_extra(CodeGen *g, ZigType *child_type, bool is_cons
 }
 
 ZigType *get_pointer_to_type(CodeGen *g, ZigType *child_type, bool is_const) {
-    return get_pointer_to_type_extra(g, child_type, is_const, false, PtrLenSingle, 0, 0, 0);
+    return get_pointer_to_type_extra(g, child_type, is_const, false, PtrLenSingle, 0, 0, 0, false);
 }
 
 ZigType *get_promise_frame_type(CodeGen *g, ZigType *return_type) {
@@ -857,7 +855,7 @@ ZigType *get_slice_type(CodeGen *g, ZigType *ptr_type) {
         ptr_type->data.pointer.explicit_alignment != 0 || ptr_type->data.pointer.allow_zero)
     {
         ZigType *peer_ptr_type = get_pointer_to_type_extra(g, child_type, false, false,
-                PtrLenUnknown, 0, 0, 0);
+                PtrLenUnknown, 0, 0, 0, false);
         ZigType *peer_slice_type = get_slice_type(g, peer_ptr_type);
 
         slice_type_common_init(g, ptr_type, entry);
@@ -881,10 +879,10 @@ ZigType *get_slice_type(CodeGen *g, ZigType *ptr_type) {
         {
             ZigType *grand_child_type = child_ptr_type->data.pointer.child_type;
             ZigType *bland_child_ptr_type = get_pointer_to_type_extra(g, grand_child_type, false, false,
-                    PtrLenUnknown, 0, 0, 0);
+                    PtrLenUnknown, 0, 0, 0, false);
             ZigType *bland_child_slice = get_slice_type(g, bland_child_ptr_type);
             ZigType *peer_ptr_type = get_pointer_to_type_extra(g, bland_child_slice, false, false,
-                    PtrLenUnknown, 0, 0, 0);
+                    PtrLenUnknown, 0, 0, 0, false);
             ZigType *peer_slice_type = get_slice_type(g, peer_ptr_type);
 
             entry->type_ref = peer_slice_type->type_ref;
@@ -1419,7 +1417,7 @@ static bool analyze_const_align(CodeGen *g, Scope *scope, AstNode *node, uint32_
 
 static bool analyze_const_string(CodeGen *g, Scope *scope, AstNode *node, Buf **out_buffer) {
     ZigType *ptr_type = get_pointer_to_type_extra(g, g->builtin_types.entry_u8, true, false,
-            PtrLenUnknown, 0, 0, 0);
+            PtrLenUnknown, 0, 0, 0, false);
     ZigType *str_type = get_slice_type(g, ptr_type);
     ConstExprValue *result_val = analyze_const_value(g, scope, node, str_type, nullptr);
     if (type_is_invalid(result_val->type))
@@ -5336,7 +5334,7 @@ void init_const_c_str_lit(CodeGen *g, ConstExprValue *const_val, Buf *str) {
     const_val->special = ConstValSpecialStatic;
     // TODO make this `[*]null u8` instead of `[*]u8`
     const_val->type = get_pointer_to_type_extra(g, g->builtin_types.entry_u8, true, false,
-            PtrLenUnknown, 0, 0, 0);
+            PtrLenUnknown, 0, 0, 0, false);
     const_val->data.x_ptr.special = ConstPtrSpecialBaseArray;
     const_val->data.x_ptr.data.base_array.array_val = array_val;
     const_val->data.x_ptr.data.base_array.elem_index = 0;
@@ -5481,7 +5479,7 @@ void init_const_slice(CodeGen *g, ConstExprValue *const_val, ConstExprValue *arr
     assert(array_val->type->id == ZigTypeIdArray);
 
     ZigType *ptr_type = get_pointer_to_type_extra(g, array_val->type->data.array.child_type,
-            is_const, false, PtrLenUnknown, 0, 0, 0);
+            is_const, false, PtrLenUnknown, 0, 0, 0, false);
 
     const_val->special = ConstValSpecialStatic;
     const_val->type = get_slice_type(g, ptr_type);
@@ -5506,7 +5504,7 @@ void init_const_ptr_array(CodeGen *g, ConstExprValue *const_val, ConstExprValue
 
     const_val->special = ConstValSpecialStatic;
     const_val->type = get_pointer_to_type_extra(g, child_type, is_const, false,
-            ptr_len, 0, 0, 0);
+            ptr_len, 0, 0, 0, false);
     const_val->data.x_ptr.special = ConstPtrSpecialBaseArray;
     const_val->data.x_ptr.data.base_array.array_val = array_val;
     const_val->data.x_ptr.data.base_array.elem_index = elem_index;
src/analyze.hpp
@@ -18,7 +18,9 @@ void emit_error_notes_for_ref_stack(CodeGen *g, ErrorMsg *msg);
 ZigType *new_type_table_entry(ZigTypeId id);
 ZigType *get_pointer_to_type(CodeGen *g, ZigType *child_type, bool is_const);
 ZigType *get_pointer_to_type_extra(CodeGen *g, ZigType *child_type, bool is_const,
-        bool is_volatile, PtrLen ptr_len, uint32_t byte_alignment, uint32_t bit_offset, uint32_t unaligned_bit_count);
+        bool is_volatile, PtrLen ptr_len,
+        uint32_t byte_alignment, uint32_t bit_offset, uint32_t unaligned_bit_count,
+        bool allow_zero);
 uint64_t type_size(CodeGen *g, ZigType *type_entry);
 uint64_t type_size_bits(CodeGen *g, ZigType *type_entry);
 ZigType *get_int_type(CodeGen *g, bool is_signed, uint32_t size_in_bits);
src/codegen.cpp
@@ -956,7 +956,7 @@ static LLVMValueRef get_panic_msg_ptr_val(CodeGen *g, PanicMsgId msg_id) {
     }
 
     ZigType *u8_ptr_type = get_pointer_to_type_extra(g, g->builtin_types.entry_u8, true, false,
-            PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0);
+            PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0, false);
     ZigType *str_type = get_slice_type(g, u8_ptr_type);
     return LLVMConstBitCast(val->global_refs->llvm_global, LLVMPointerType(str_type->type_ref, 0));
 }
@@ -1515,7 +1515,7 @@ static LLVMValueRef get_safety_crash_err_fn(CodeGen *g) {
 
 
     ZigType *u8_ptr_type = get_pointer_to_type_extra(g, g->builtin_types.entry_u8, true, false,
-            PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0);
+            PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0, false);
     ZigType *str_type = get_slice_type(g, u8_ptr_type);
     LLVMValueRef global_slice_fields[] = {
         full_buf_ptr,
@@ -3103,6 +3103,18 @@ static LLVMValueRef ir_render_widen_or_shorten(CodeGen *g, IrExecutable *executa
 static LLVMValueRef ir_render_int_to_ptr(CodeGen *g, IrExecutable *executable, IrInstructionIntToPtr *instruction) {
     ZigType *wanted_type = instruction->base.value.type;
     LLVMValueRef target_val = ir_llvm_value(g, instruction->target);
+    if (!ptr_allows_addr_zero(wanted_type) && ir_want_runtime_safety(g, &instruction->base)) {
+        LLVMValueRef zero = LLVMConstNull(LLVMTypeOf(target_val));
+        LLVMValueRef is_zero_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, target_val, zero, "");
+        LLVMBasicBlockRef bad_block = LLVMAppendBasicBlock(g->cur_fn_val, "PtrToIntBad");
+        LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "PtrToIntOk");
+        LLVMBuildCondBr(g->builder, is_zero_bit, bad_block, ok_block);
+
+        LLVMPositionBuilderAtEnd(g->builder, bad_block);
+        gen_safety_crash(g, PanicMsgIdPtrCastNull);
+
+        LLVMPositionBuilderAtEnd(g->builder, ok_block);
+    }
     return LLVMBuildIntToPtr(g->builder, target_val, wanted_type->type_ref, "");
 }
 
@@ -3270,7 +3282,7 @@ static LLVMValueRef ir_render_decl_var(CodeGen *g, IrExecutable *executable,
 
     if (have_init_expr) {
         ZigType *var_ptr_type = get_pointer_to_type_extra(g, var->var_type, false, false,
-                PtrLenSingle, var->align_bytes, 0, 0);
+                PtrLenSingle, var->align_bytes, 0, 0, false);
         LLVMValueRef llvm_init_val = ir_llvm_value(g, init_value);
         gen_assign_raw(g, var->value_ref, var_ptr_type, llvm_init_val);
     } else if (ir_want_runtime_safety(g, &decl_var_instruction->base)) {
@@ -4160,7 +4172,7 @@ static LLVMValueRef get_enum_tag_name_function(CodeGen *g, ZigType *enum_type) {
         return enum_type->data.enumeration.name_function;
 
     ZigType *u8_ptr_type = get_pointer_to_type_extra(g, g->builtin_types.entry_u8, false, false,
-            PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0);
+            PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0, false);
     ZigType *u8_slice_type = get_slice_type(g, u8_ptr_type);
     ZigType *tag_int_type = enum_type->data.enumeration.tag_int_type;
 
@@ -4953,7 +4965,7 @@ static LLVMValueRef ir_render_struct_init(CodeGen *g, IrExecutable *executable,
 
         ZigType *ptr_type = get_pointer_to_type_extra(g, type_struct_field->type_entry,
                 false, false, PtrLenSingle, field_align_bytes,
-                (uint32_t)type_struct_field->bit_offset_in_host, host_int_bytes);
+                (uint32_t)type_struct_field->bit_offset_in_host, host_int_bytes, false);
 
         gen_assign_raw(g, field_ptr, ptr_type, value);
     }
@@ -4969,7 +4981,7 @@ static LLVMValueRef ir_render_union_init(CodeGen *g, IrExecutable *executable, I
     uint32_t field_align_bytes = get_abi_alignment(g, type_union_field->type_entry);
     ZigType *ptr_type = get_pointer_to_type_extra(g, type_union_field->type_entry,
             false, false, PtrLenSingle, field_align_bytes,
-            0, 0);
+            0, 0, false);
 
     LLVMValueRef uncasted_union_ptr;
     // Even if safety is off in this block, if the union type has the safety field, we have to populate it
@@ -5224,7 +5236,7 @@ static LLVMValueRef get_coro_alloc_helper_fn_val(CodeGen *g, LLVMTypeRef alloc_f
     LLVMPositionBuilderAtEnd(g->builder, ok_block);
     LLVMValueRef payload_ptr = LLVMBuildStructGEP(g->builder, sret_ptr, err_union_payload_index, "");
     ZigType *u8_ptr_type = get_pointer_to_type_extra(g, g->builtin_types.entry_u8, false, false,
-            PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0);
+            PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0, false);
     ZigType *slice_type = get_slice_type(g, u8_ptr_type);
     size_t ptr_field_index = slice_type->data.structure.fields[slice_ptr_index].gen_index;
     LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder, payload_ptr, ptr_field_index, "");
@@ -6470,7 +6482,7 @@ static void generate_error_name_table(CodeGen *g) {
     assert(g->errors_by_index.length > 0);
 
     ZigType *u8_ptr_type = get_pointer_to_type_extra(g, g->builtin_types.entry_u8, true, false,
-            PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0);
+            PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0, false);
     ZigType *str_type = get_slice_type(g, u8_ptr_type);
 
     LLVMValueRef *values = allocate<LLVMValueRef>(g->errors_by_index.length);
@@ -7551,6 +7563,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
             "        is_volatile: bool,\n"
             "        alignment: comptime_int,\n"
             "        child: type,\n"
+            "        is_allowzero: bool,\n"
             "\n"
             "        pub const Size = enum {\n"
             "            One,\n"
@@ -8158,7 +8171,7 @@ static void create_test_compile_var_and_add_test_runner(CodeGen *g) {
     }
 
     ZigType *u8_ptr_type = get_pointer_to_type_extra(g, g->builtin_types.entry_u8, true, false,
-            PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0);
+            PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0, false);
     ZigType *str_type = get_slice_type(g, u8_ptr_type);
     ZigType *fn_type = get_test_fn_type(g);
 
src/ir.cpp
@@ -1348,7 +1348,7 @@ static IrInstruction *ir_build_br(IrBuilder *irb, Scope *scope, AstNode *source_
 
 static IrInstruction *ir_build_ptr_type(IrBuilder *irb, Scope *scope, AstNode *source_node,
         IrInstruction *child_type, bool is_const, bool is_volatile, PtrLen ptr_len,
-        IrInstruction *align_value, uint32_t bit_offset_start, uint32_t host_int_bytes)
+        IrInstruction *align_value, uint32_t bit_offset_start, uint32_t host_int_bytes, bool is_allow_zero)
 {
     IrInstructionPtrType *ptr_type_of_instruction = ir_build_instruction<IrInstructionPtrType>(irb, scope, source_node);
     ptr_type_of_instruction->align_value = align_value;
@@ -1358,6 +1358,7 @@ static IrInstruction *ir_build_ptr_type(IrBuilder *irb, Scope *scope, AstNode *s
     ptr_type_of_instruction->ptr_len = ptr_len;
     ptr_type_of_instruction->bit_offset_start = bit_offset_start;
     ptr_type_of_instruction->host_int_bytes = host_int_bytes;
+    ptr_type_of_instruction->is_allow_zero = is_allow_zero;
 
     if (align_value) ir_ref_instruction(align_value, irb->current_basic_block);
     ir_ref_instruction(child_type, irb->current_basic_block);
@@ -1627,13 +1628,14 @@ static IrInstruction *ir_build_promise_type(IrBuilder *irb, Scope *scope, AstNod
 }
 
 static IrInstruction *ir_build_slice_type(IrBuilder *irb, Scope *scope, AstNode *source_node,
-        IrInstruction *child_type, bool is_const, bool is_volatile, IrInstruction *align_value)
+        IrInstruction *child_type, bool is_const, bool is_volatile, IrInstruction *align_value, bool is_allow_zero)
 {
     IrInstructionSliceType *instruction = ir_build_instruction<IrInstructionSliceType>(irb, scope, source_node);
     instruction->is_const = is_const;
     instruction->is_volatile = is_volatile;
     instruction->child_type = child_type;
     instruction->align_value = align_value;
+    instruction->is_allow_zero = is_allow_zero;
 
     ir_ref_instruction(child_type, irb->current_basic_block);
     if (align_value) ir_ref_instruction(align_value, irb->current_basic_block);
@@ -5192,6 +5194,7 @@ static IrInstruction *ir_gen_pointer_type(IrBuilder *irb, Scope *scope, AstNode
     PtrLen ptr_len = star_token_to_ptr_len(node->data.pointer_type.star_token->id);
     bool is_const = node->data.pointer_type.is_const;
     bool is_volatile = node->data.pointer_type.is_volatile;
+    bool is_allow_zero = node->data.pointer_type.allow_zero_token != nullptr;
     AstNode *expr_node = node->data.pointer_type.op_expr;
     AstNode *align_expr = node->data.pointer_type.align_expr;
 
@@ -5239,7 +5242,7 @@ static IrInstruction *ir_gen_pointer_type(IrBuilder *irb, Scope *scope, AstNode
     }
 
     return ir_build_ptr_type(irb, scope, node, child_type, is_const, is_volatile,
-            ptr_len, align_value, bit_offset_start, host_int_bytes);
+            ptr_len, align_value, bit_offset_start, host_int_bytes, is_allow_zero);
 }
 
 static IrInstruction *ir_gen_catch_unreachable(IrBuilder *irb, Scope *scope, AstNode *source_node, AstNode *expr_node,
@@ -5826,6 +5829,7 @@ static IrInstruction *ir_gen_array_type(IrBuilder *irb, Scope *scope, AstNode *n
     AstNode *child_type_node = node->data.array_type.child_type;
     bool is_const = node->data.array_type.is_const;
     bool is_volatile = node->data.array_type.is_volatile;
+    bool is_allow_zero = node->data.array_type.allow_zero_token != nullptr;
     AstNode *align_expr = node->data.array_type.align_expr;
 
     Scope *comptime_scope = create_comptime_scope(irb->codegen, node, scope);
@@ -5838,6 +5842,10 @@ static IrInstruction *ir_gen_array_type(IrBuilder *irb, Scope *scope, AstNode *n
             add_node_error(irb->codegen, node, buf_create_from_str("volatile qualifier invalid on array type"));
             return irb->codegen->invalid_instruction;
         }
+        if (is_allow_zero) {
+            add_node_error(irb->codegen, node, buf_create_from_str("allowzero qualifier invalid on array type"));
+            return irb->codegen->invalid_instruction;
+        }
         if (align_expr != nullptr) {
             add_node_error(irb->codegen, node, buf_create_from_str("align qualifier invalid on array type"));
             return irb->codegen->invalid_instruction;
@@ -5866,7 +5874,7 @@ static IrInstruction *ir_gen_array_type(IrBuilder *irb, Scope *scope, AstNode *n
         if (child_type == irb->codegen->invalid_instruction)
             return child_type;
 
-        return ir_build_slice_type(irb, scope, node, child_type, is_const, is_volatile, align_value);
+        return ir_build_slice_type(irb, scope, node, child_type, is_const, is_volatile, align_value, is_allow_zero);
     }
 }
 
@@ -7760,7 +7768,7 @@ bool ir_gen(CodeGen *codegen, AstNode *node, Scope *scope, IrExecutable *ir_exec
         if (type_has_bits(return_type)) {
             IrInstruction *u8_ptr_type_unknown_len = ir_build_const_type(irb, scope, node,
                     get_pointer_to_type_extra(irb->codegen, irb->codegen->builtin_types.entry_u8,
-                        false, false, PtrLenUnknown, 0, 0, 0));
+                        false, false, PtrLenUnknown, 0, 0, 0, false));
             IrInstruction *result_ptr = ir_build_load_ptr(irb, scope, node, irb->exec->coro_result_ptr_field_ptr);
             IrInstruction *result_ptr_as_u8_ptr = ir_build_ptr_cast_src(irb, scope, node, u8_ptr_type_unknown_len,
                     result_ptr, false);
@@ -7814,7 +7822,7 @@ bool ir_gen(CodeGen *codegen, AstNode *node, Scope *scope, IrExecutable *ir_exec
         IrInstruction *coro_mem_ptr_maybe = ir_build_coro_free(irb, scope, node, coro_id, irb->exec->coro_handle);
         IrInstruction *u8_ptr_type_unknown_len = ir_build_const_type(irb, scope, node,
                 get_pointer_to_type_extra(irb->codegen, irb->codegen->builtin_types.entry_u8,
-                    false, false, PtrLenUnknown, 0, 0, 0));
+                    false, false, PtrLenUnknown, 0, 0, 0, false));
         IrInstruction *coro_mem_ptr = ir_build_ptr_cast_src(irb, scope, node, u8_ptr_type_unknown_len,
                 coro_mem_ptr_maybe, false);
         IrInstruction *coro_mem_ptr_ref = ir_build_ref(irb, scope, node, coro_mem_ptr, true, false);
@@ -9818,7 +9826,7 @@ static ZigType *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_node, ZigT
         ZigType *ptr_type = get_pointer_to_type_extra(
                 ira->codegen, prev_inst->value.type->data.array.child_type,
                 true, false, PtrLenUnknown,
-                0, 0, 0);
+                0, 0, 0, false);
         ZigType *slice_type = get_slice_type(ira->codegen, ptr_type);
         if (err_set_type != nullptr) {
             return get_error_union_type(ira->codegen, err_set_type, slice_type);
@@ -10243,7 +10251,7 @@ static IrInstruction *ir_get_const_ptr(IrAnalyze *ira, IrInstruction *instructio
         ConstPtrMut ptr_mut, bool ptr_is_const, bool ptr_is_volatile, uint32_t ptr_align)
 {
     ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, pointee_type,
-            ptr_is_const, ptr_is_volatile, PtrLenSingle, ptr_align, 0, 0);
+            ptr_is_const, ptr_is_volatile, PtrLenSingle, ptr_align, 0, 0, false);
     IrInstruction *const_instr = ir_const(ira, instruction, ptr_type);
     ConstExprValue *const_val = &const_instr->value;
     const_val->data.x_ptr.special = ConstPtrSpecialRef;
@@ -10576,7 +10584,7 @@ static IrInstruction *ir_get_ref(IrAnalyze *ira, IrInstruction *source_instructi
     }
 
     ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, value->value.type,
-            is_const, is_volatile, PtrLenSingle, 0, 0, 0);
+            is_const, is_volatile, PtrLenSingle, 0, 0, 0, false);
     IrInstruction *new_instruction = ir_build_ref(&ira->new_irb, source_instruction->scope,
             source_instruction->source_node, value, is_const, is_volatile);
     new_instruction->value.type = ptr_type;
@@ -11983,7 +11991,7 @@ static Buf *ir_resolve_str(IrAnalyze *ira, IrInstruction *value) {
         return nullptr;
 
     ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8,
-            true, false, PtrLenUnknown, 0, 0, 0);
+            true, false, PtrLenUnknown, 0, 0, 0, false);
     ZigType *str_type = get_slice_type(ira->codegen, ptr_type);
     IrInstruction *casted_value = ir_implicit_cast(ira, value, str_type);
     if (type_is_invalid(casted_value->value.type))
@@ -13154,7 +13162,7 @@ static IrInstruction *ir_analyze_array_cat(IrAnalyze *ira, IrInstructionBinOp *i
         out_array_val = out_val;
     } else if (is_slice(op1_type) || is_slice(op2_type)) {
         ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, child_type,
-                true, false, PtrLenUnknown, 0, 0, 0);
+                true, false, PtrLenUnknown, 0, 0, 0, false);
         result->value.type = get_slice_type(ira->codegen, ptr_type);
         out_array_val = create_const_vals(1);
         out_array_val->special = ConstValSpecialStatic;
@@ -13175,7 +13183,7 @@ static IrInstruction *ir_analyze_array_cat(IrAnalyze *ira, IrInstructionBinOp *i
         new_len += 1; // null byte
 
         // TODO make this `[*]null T` instead of `[*]T`
-        result->value.type = get_pointer_to_type_extra(ira->codegen, child_type, true, false, PtrLenUnknown, 0, 0, 0);
+        result->value.type = get_pointer_to_type_extra(ira->codegen, child_type, true, false, PtrLenUnknown, 0, 0, 0, false);
 
         out_array_val = create_const_vals(1);
         out_array_val->special = ConstValSpecialStatic;
@@ -14033,7 +14041,7 @@ no_mem_slot:
     IrInstruction *var_ptr_instruction = ir_build_var_ptr(&ira->new_irb,
             instruction->scope, instruction->source_node, var);
     var_ptr_instruction->value.type = get_pointer_to_type_extra(ira->codegen, var->var_type,
-            var->src_is_const, is_volatile, PtrLenSingle, var->align_bytes, 0, 0);
+            var->src_is_const, is_volatile, PtrLenSingle, var->align_bytes, 0, 0, false);
 
     bool in_fn_scope = (scope_fn_entry(var->parent_scope) != nullptr);
     var_ptr_instruction->value.data.rh_ptr = in_fn_scope ? RuntimeHintPtrStack : RuntimeHintPtrNonStack;
@@ -14328,7 +14336,7 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *call
     IrInstruction *casted_new_stack = nullptr;
     if (call_instruction->new_stack != nullptr) {
         ZigType *u8_ptr = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8,
-                false, false, PtrLenUnknown, 0, 0, 0);
+                false, false, PtrLenUnknown, 0, 0, 0, false);
         ZigType *u8_slice = get_slice_type(ira->codegen, u8_ptr);
         IrInstruction *new_stack = call_instruction->new_stack->child;
         if (type_is_invalid(new_stack->value.type))
@@ -15262,7 +15270,8 @@ static ZigType *adjust_ptr_align(CodeGen *g, ZigType *ptr_type, uint32_t new_ali
             ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile,
             ptr_type->data.pointer.ptr_len,
             new_align,
-            ptr_type->data.pointer.bit_offset_in_host, ptr_type->data.pointer.host_int_bytes);
+            ptr_type->data.pointer.bit_offset_in_host, ptr_type->data.pointer.host_int_bytes,
+            ptr_type->data.pointer.allow_zero);
 }
 
 static ZigType *adjust_slice_align(CodeGen *g, ZigType *slice_type, uint32_t new_align) {
@@ -15279,7 +15288,8 @@ static ZigType *adjust_ptr_len(CodeGen *g, ZigType *ptr_type, PtrLen ptr_len) {
             ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile,
             ptr_len,
             ptr_type->data.pointer.explicit_alignment,
-            ptr_type->data.pointer.bit_offset_in_host, ptr_type->data.pointer.host_int_bytes);
+            ptr_type->data.pointer.bit_offset_in_host, ptr_type->data.pointer.host_int_bytes,
+            ptr_type->data.pointer.allow_zero);
 }
 
 static IrInstruction *ir_analyze_instruction_elem_ptr(IrAnalyze *ira, IrInstructionElemPtr *elem_ptr_instruction) {
@@ -15330,7 +15340,7 @@ static IrInstruction *ir_analyze_instruction_elem_ptr(IrAnalyze *ira, IrInstruct
             return_type = get_pointer_to_type_extra(ira->codegen, child_type,
                     ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile,
                     elem_ptr_instruction->ptr_len,
-                    ptr_type->data.pointer.explicit_alignment, 0, 0);
+                    ptr_type->data.pointer.explicit_alignment, 0, 0, false);
         } else {
             uint64_t elem_val_scalar;
             if (!ir_resolve_usize(ira, elem_index, &elem_val_scalar))
@@ -15342,7 +15352,7 @@ static IrInstruction *ir_analyze_instruction_elem_ptr(IrAnalyze *ira, IrInstruct
             return_type = get_pointer_to_type_extra(ira->codegen, child_type,
                     ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile,
                     elem_ptr_instruction->ptr_len,
-                    1, (uint32_t)bit_offset, ptr_type->data.pointer.host_int_bytes);
+                    1, (uint32_t)bit_offset, ptr_type->data.pointer.host_int_bytes, false);
         }
     } else if (array_type->id == ZigTypeIdPointer) {
         if (array_type->data.pointer.ptr_len == PtrLenSingle) {
@@ -15693,7 +15703,7 @@ static IrInstruction *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field_
                     ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, field->type_entry,
                             is_const, is_volatile, PtrLenSingle, align_bytes,
                             (uint32_t)(ptr_bit_offset + field->bit_offset_in_host),
-                            (uint32_t)host_int_bytes_for_result_type);
+                            (uint32_t)host_int_bytes_for_result_type, false);
                     IrInstruction *result = ir_const(ira, source_instr, ptr_type);
                     ConstExprValue *const_val = &result->value;
                     const_val->data.x_ptr.special = ConstPtrSpecialBaseStruct;
@@ -15709,7 +15719,7 @@ static IrInstruction *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field_
                     PtrLenSingle,
                     align_bytes,
                     (uint32_t)(ptr_bit_offset + field->bit_offset_in_host),
-                    host_int_bytes_for_result_type);
+                    host_int_bytes_for_result_type, false);
             return result;
         } else {
             return ir_analyze_container_member_access_inner(ira, bare_type, field_name,
@@ -15748,7 +15758,7 @@ static IrInstruction *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field_
 
                     ZigType *field_type = field->type_entry;
                     ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, field_type,
-                            is_const, is_volatile, PtrLenSingle, 0, 0, 0);
+                            is_const, is_volatile, PtrLenSingle, 0, 0, 0, false);
 
                     IrInstruction *result = ir_const(ira, source_instr, ptr_type);
                     ConstExprValue *const_val = &result->value;
@@ -15761,7 +15771,7 @@ static IrInstruction *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field_
 
             IrInstruction *result = ir_build_union_field_ptr(&ira->new_irb, source_instr->scope, source_instr->source_node, container_ptr, field);
             result->value.type = get_pointer_to_type_extra(ira->codegen, field->type_entry, is_const, is_volatile,
-                    PtrLenSingle, 0, 0, 0);
+                    PtrLenSingle, 0, 0, 0, false);
             return result;
         } else {
             return ir_analyze_container_member_access_inner(ira, bare_type, field_name,
@@ -16480,6 +16490,7 @@ static IrInstruction *ir_analyze_instruction_slice_type(IrAnalyze *ira,
 
     bool is_const = slice_type_instruction->is_const;
     bool is_volatile = slice_type_instruction->is_volatile;
+    bool is_allow_zero = slice_type_instruction->is_allow_zero;
 
     switch (child_type->id) {
         case ZigTypeIdInvalid: // handled above
@@ -16516,7 +16527,7 @@ static IrInstruction *ir_analyze_instruction_slice_type(IrAnalyze *ira,
                 if ((err = type_resolve(ira->codegen, child_type, ResolveStatusZeroBitsKnown)))
                     return ira->codegen->invalid_instruction;
                 ZigType *slice_ptr_type = get_pointer_to_type_extra(ira->codegen, child_type,
-                        is_const, is_volatile, PtrLenUnknown, align_bytes, 0, 0);
+                        is_const, is_volatile, PtrLenUnknown, align_bytes, 0, 0, is_allow_zero);
                 ZigType *result_type = get_slice_type(ira->codegen, slice_ptr_type);
                 return ir_const_type(ira, &slice_type_instruction->base, result_type);
             }
@@ -16749,7 +16760,7 @@ static IrInstruction *ir_analyze_unwrap_optional_payload(IrAnalyze *ira, IrInstr
 
     ZigType *child_type = type_entry->data.maybe.child_type;
     ZigType *result_type = get_pointer_to_type_extra(ira->codegen, child_type,
-            ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile, PtrLenSingle, 0, 0, 0);
+            ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile, PtrLenSingle, 0, 0, 0, false);
 
     if (instr_is_comptime(base_ptr)) {
         ConstExprValue *val = ir_resolve_const(ira, base_ptr, UndefBad);
@@ -17668,7 +17679,7 @@ static IrInstruction *ir_analyze_instruction_err_name(IrAnalyze *ira, IrInstruct
         return ira->codegen->invalid_instruction;
 
     ZigType *u8_ptr_type = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8,
-            true, false, PtrLenUnknown, 0, 0, 0);
+            true, false, PtrLenUnknown, 0, 0, 0, false);
     ZigType *str_type = get_slice_type(ira->codegen, u8_ptr_type);
     if (casted_value->value.special == ConstValSpecialStatic) {
         ErrorTableEntry *err = casted_value->value.data.x_err_set;
@@ -17713,7 +17724,7 @@ static IrInstruction *ir_analyze_instruction_enum_tag_name(IrAnalyze *ira, IrIns
     ZigType *u8_ptr_type = get_pointer_to_type_extra(
             ira->codegen, ira->codegen->builtin_types.entry_u8,
             true, false, PtrLenUnknown,
-            0, 0, 0);
+            0, 0, 0, false);
     result->value.type = get_slice_type(ira->codegen, u8_ptr_type);
     return result;
 }
@@ -17767,7 +17778,7 @@ static IrInstruction *ir_analyze_instruction_field_parent_ptr(IrAnalyze *ira,
             field_ptr->value.type->data.pointer.is_const,
             field_ptr->value.type->data.pointer.is_volatile,
             PtrLenSingle,
-            field_ptr_align, 0, 0);
+            field_ptr_align, 0, 0, false);
     IrInstruction *casted_field_ptr = ir_implicit_cast(ira, field_ptr, field_ptr_type);
     if (type_is_invalid(casted_field_ptr->value.type))
         return ira->codegen->invalid_instruction;
@@ -17776,7 +17787,7 @@ static IrInstruction *ir_analyze_instruction_field_parent_ptr(IrAnalyze *ira,
             casted_field_ptr->value.type->data.pointer.is_const,
             casted_field_ptr->value.type->data.pointer.is_volatile,
             PtrLenSingle,
-            parent_ptr_align, 0, 0);
+            parent_ptr_align, 0, 0, false);
 
     if (instr_is_comptime(casted_field_ptr)) {
         ConstExprValue *field_ptr_val = ir_resolve_const(ira, casted_field_ptr, UndefBad);
@@ -18112,7 +18123,7 @@ static Error ir_make_type_info_defs(IrAnalyze *ira, IrInstruction *source_instr,
                     ZigType *u8_ptr = get_pointer_to_type_extra(
                         ira->codegen, ira->codegen->builtin_types.entry_u8,
                         true, false, PtrLenUnknown,
-                        0, 0, 0);
+                        0, 0, 0, false);
                     fn_def_fields[6].type = get_optional_type(ira->codegen, get_slice_type(ira->codegen, u8_ptr));
                     if (fn_node->is_extern && buf_len(fn_node->lib_name) > 0) {
                         fn_def_fields[6].data.x_optional = create_const_vals(1);
@@ -18216,7 +18227,7 @@ static ConstExprValue *create_ptr_like_type_info(IrAnalyze *ira, ZigType *ptr_ty
     result->special = ConstValSpecialStatic;
     result->type = type_info_pointer_type;
 
-    ConstExprValue *fields = create_const_vals(5);
+    ConstExprValue *fields = create_const_vals(6);
     result->data.x_struct.fields = fields;
 
     // size: Size
@@ -18247,6 +18258,11 @@ static ConstExprValue *create_ptr_like_type_info(IrAnalyze *ira, ZigType *ptr_ty
     fields[4].special = ConstValSpecialStatic;
     fields[4].type = ira->codegen->builtin_types.entry_type;
     fields[4].data.x_type = attrs_type->data.pointer.child_type;
+    // is_allowzero: bool
+    ensure_field_index(result->type, "is_allowzero", 5);
+    fields[5].special = ConstValSpecialStatic;
+    fields[5].type = ira->codegen->builtin_types.entry_bool;
+    fields[5].data.x_bool = attrs_type->data.pointer.allow_zero;
 
     return result;
 };
@@ -19473,12 +19489,12 @@ static IrInstruction *ir_analyze_instruction_from_bytes(IrAnalyze *ira, IrInstru
 
     ZigType *dest_ptr_type = get_pointer_to_type_extra(ira->codegen, dest_child_type,
             src_ptr_const, src_ptr_volatile, PtrLenUnknown,
-            src_ptr_align, 0, 0);
+            src_ptr_align, 0, 0, false);
     ZigType *dest_slice_type = get_slice_type(ira->codegen, dest_ptr_type);
 
     ZigType *u8_ptr = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8,
             src_ptr_const, src_ptr_volatile, PtrLenUnknown,
-            src_ptr_align, 0, 0);
+            src_ptr_align, 0, 0, false);
     ZigType *u8_slice = get_slice_type(ira->codegen, u8_ptr);
 
     IrInstruction *casted_value = ir_implicit_cast(ira, target, u8_slice);
@@ -19545,7 +19561,7 @@ static IrInstruction *ir_analyze_instruction_to_bytes(IrAnalyze *ira, IrInstruct
 
     ZigType *dest_ptr_type = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8,
             src_ptr_type->data.pointer.is_const, src_ptr_type->data.pointer.is_volatile, PtrLenUnknown,
-            alignment, 0, 0);
+            alignment, 0, 0, false);
     ZigType *dest_slice_type = get_slice_type(ira->codegen, dest_ptr_type);
 
     if (instr_is_comptime(target)) {
@@ -19773,7 +19789,7 @@ static IrInstruction *ir_analyze_instruction_memset(IrAnalyze *ira, IrInstructio
         dest_align = get_abi_alignment(ira->codegen, u8);
     }
     ZigType *u8_ptr = get_pointer_to_type_extra(ira->codegen, u8, false, dest_is_volatile,
-            PtrLenUnknown, dest_align, 0, 0);
+            PtrLenUnknown, dest_align, 0, 0, false);
 
     IrInstruction *casted_dest_ptr = ir_implicit_cast(ira, dest_ptr, u8_ptr);
     if (type_is_invalid(casted_dest_ptr->value.type))
@@ -19895,9 +19911,9 @@ static IrInstruction *ir_analyze_instruction_memcpy(IrAnalyze *ira, IrInstructio
 
     ZigType *usize = ira->codegen->builtin_types.entry_usize;
     ZigType *u8_ptr_mut = get_pointer_to_type_extra(ira->codegen, u8, false, dest_is_volatile,
-            PtrLenUnknown, dest_align, 0, 0);
+            PtrLenUnknown, dest_align, 0, 0, false);
     ZigType *u8_ptr_const = get_pointer_to_type_extra(ira->codegen, u8, true, src_is_volatile,
-            PtrLenUnknown, src_align, 0, 0);
+            PtrLenUnknown, src_align, 0, 0, false);
 
     IrInstruction *casted_dest_ptr = ir_implicit_cast(ira, dest_ptr, u8_ptr_mut);
     if (type_is_invalid(casted_dest_ptr->value.type))
@@ -20061,7 +20077,7 @@ static IrInstruction *ir_analyze_instruction_slice(IrAnalyze *ira, IrInstruction
             ptr_ptr_type->data.pointer.is_const || is_comptime_const,
             ptr_ptr_type->data.pointer.is_volatile,
             PtrLenUnknown,
-            ptr_ptr_type->data.pointer.explicit_alignment, 0, 0);
+            ptr_ptr_type->data.pointer.explicit_alignment, 0, 0, false);
         return_type = get_slice_type(ira->codegen, slice_ptr_type);
     } else if (array_type->id == ZigTypeIdPointer) {
         if (array_type->data.pointer.ptr_len == PtrLenSingle) {
@@ -20071,7 +20087,7 @@ static IrInstruction *ir_analyze_instruction_slice(IrAnalyze *ira, IrInstruction
                         main_type->data.pointer.child_type,
                         array_type->data.pointer.is_const, array_type->data.pointer.is_volatile,
                         PtrLenUnknown,
-                        array_type->data.pointer.explicit_alignment, 0, 0);
+                        array_type->data.pointer.explicit_alignment, 0, 0, false);
                 return_type = get_slice_type(ira->codegen, slice_ptr_type);
             } else {
                 ir_add_error(ira, &instruction->base, buf_sprintf("slice of single-item pointer"));
@@ -20586,7 +20602,7 @@ static IrInstruction *ir_analyze_instruction_overflow_op(IrAnalyze *ira, IrInstr
         expected_ptr_type = get_pointer_to_type_extra(ira->codegen, dest_type,
                 false, result_ptr->value.type->data.pointer.is_volatile,
                 PtrLenSingle,
-                alignment, 0, 0);
+                alignment, 0, 0, false);
     } else {
         expected_ptr_type = get_pointer_to_type(ira->codegen, dest_type, false);
     }
@@ -20753,7 +20769,7 @@ static IrInstruction *ir_analyze_instruction_unwrap_err_payload(IrAnalyze *ira,
 
     ZigType *result_type = get_pointer_to_type_extra(ira->codegen, payload_type,
             ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile,
-            PtrLenSingle, 0, 0, 0);
+            PtrLenSingle, 0, 0, 0, false);
     if (instr_is_comptime(value)) {
         ConstExprValue *ptr_val = ir_resolve_const(ira, value, UndefBad);
         if (!ptr_val)
@@ -21125,7 +21141,7 @@ static IrInstruction *ir_analyze_instruction_panic(IrAnalyze *ira, IrInstruction
     }
 
     ZigType *u8_ptr_type = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8,
-            true, false, PtrLenUnknown, 0, 0, 0);
+            true, false, PtrLenUnknown, 0, 0, 0, false);
     ZigType *str_type = get_slice_type(ira->codegen, u8_ptr_type);
     IrInstruction *casted_msg = ir_implicit_cast(ira, msg, str_type);
     if (type_is_invalid(casted_msg->value.type))
@@ -21794,10 +21810,17 @@ static IrInstruction *ir_analyze_int_to_ptr(IrAnalyze *ira, IrInstruction *sourc
         if (!val)
             return ira->codegen->invalid_instruction;
 
+        uint64_t addr = bigint_as_unsigned(&val->data.x_bigint);
+        if (!ptr_allows_addr_zero(ptr_type) && addr == 0) {
+            ir_add_error(ira, source_instr,
+                    buf_sprintf("pointer type '%s' does not allow address zero", buf_ptr(&ptr_type->name)));
+            return ira->codegen->invalid_instruction;
+        }
+
         IrInstruction *result = ir_const(ira, source_instr, ptr_type);
         result->value.data.x_ptr.special = ConstPtrSpecialHardCodedAddr;
         result->value.data.x_ptr.mut = ConstPtrMutRuntimeVar;
-        result->value.data.x_ptr.data.hard_coded_addr.addr = bigint_as_unsigned(&val->data.x_bigint);
+        result->value.data.x_ptr.data.hard_coded_addr.addr = addr;
         return result;
     }
 
@@ -21951,6 +21974,9 @@ static IrInstruction *ir_analyze_instruction_ptr_type(IrAnalyze *ira, IrInstruct
         } else if (child_type->id == ZigTypeIdOpaque) {
             ir_add_error(ira, &instruction->base, buf_sprintf("C pointers cannot point opaque types"));
             return ira->codegen->invalid_instruction;
+        } else if (instruction->is_allow_zero) {
+            ir_add_error(ira, &instruction->base, buf_sprintf("C pointers always allow address zero"));
+            return ira->codegen->invalid_instruction;
         }
     }
 
@@ -21969,10 +21995,12 @@ static IrInstruction *ir_analyze_instruction_ptr_type(IrAnalyze *ira, IrInstruct
         align_bytes = 0;
     }
 
+    bool allow_zero = instruction->is_allow_zero || instruction->ptr_len == PtrLenC;
+
     ZigType *result_type = get_pointer_to_type_extra(ira->codegen, child_type,
             instruction->is_const, instruction->is_volatile,
             instruction->ptr_len, align_bytes,
-            instruction->bit_offset_start, instruction->host_int_bytes);
+            instruction->bit_offset_start, instruction->host_int_bytes, allow_zero);
     return ir_const_type(ira, &instruction->base, result_type);
 }
 
src/parser.cpp
@@ -2530,6 +2530,12 @@ static AstNode *ast_parse_prefix_type_op(ParseContext *pc) {
     if (array != nullptr) {
         assert(array->type == NodeTypeArrayType);
         while (true) {
+            Token *allowzero_token = eat_token_if(pc, TokenIdKeywordAllowZero);
+            if (allowzero_token != nullptr) {
+                array->data.array_type.allow_zero_token = allowzero_token;
+                continue;
+            }
+
             AstNode *align_expr = ast_parse_byte_align(pc);
             if (align_expr != nullptr) {
                 array->data.array_type.align_expr = align_expr;
@@ -2545,7 +2551,6 @@ static AstNode *ast_parse_prefix_type_op(ParseContext *pc) {
                 array->data.array_type.is_volatile = true;
                 continue;
             }
-
             break;
         }
 
@@ -2560,6 +2565,12 @@ static AstNode *ast_parse_prefix_type_op(ParseContext *pc) {
         if (child == nullptr)
             child = ptr;
         while (true) {
+            Token *allowzero_token = eat_token_if(pc, TokenIdKeywordAllowZero);
+            if (allowzero_token != nullptr) {
+                child->data.pointer_type.allow_zero_token = allowzero_token;
+                continue;
+            }
+
             if (eat_token_if(pc, TokenIdKeywordAlign) != nullptr) {
                 expect_token(pc, TokenIdLParen);
                 AstNode *align_expr = ast_parse_expr(pc);
src/tokenizer.cpp
@@ -107,6 +107,7 @@ struct ZigKeyword {
 
 static const struct ZigKeyword zig_keywords[] = {
     {"align", TokenIdKeywordAlign},
+    {"allowzero", TokenIdKeywordAllowZero},
     {"and", TokenIdKeywordAnd},
     {"anyerror", TokenIdKeywordAnyerror},
     {"asm", TokenIdKeywordAsm},
@@ -1495,6 +1496,7 @@ const char * token_name(TokenId id) {
         case TokenIdIntLiteral: return "IntLiteral";
         case TokenIdKeywordAsync: return "async";
         case TokenIdKeywordAnyerror: return "anyerror";
+        case TokenIdKeywordAllowZero: return "allowzero";
         case TokenIdKeywordAwait: return "await";
         case TokenIdKeywordResume: return "resume";
         case TokenIdKeywordSuspend: return "suspend";
src/tokenizer.hpp
@@ -50,6 +50,7 @@ enum TokenId {
     TokenIdFloatLiteral,
     TokenIdIntLiteral,
     TokenIdKeywordAlign,
+    TokenIdKeywordAllowZero,
     TokenIdKeywordAnd,
     TokenIdKeywordAnyerror,
     TokenIdKeywordAsm,
@@ -73,6 +74,7 @@ enum TokenId {
     TokenIdKeywordFor,
     TokenIdKeywordIf,
     TokenIdKeywordInline,
+    TokenIdKeywordLinkSection,
     TokenIdKeywordNakedCC,
     TokenIdKeywordNoAlias,
     TokenIdKeywordNull,
@@ -83,7 +85,6 @@ enum TokenId {
     TokenIdKeywordPub,
     TokenIdKeywordResume,
     TokenIdKeywordReturn,
-    TokenIdKeywordLinkSection,
     TokenIdKeywordStdcallCC,
     TokenIdKeywordStruct,
     TokenIdKeywordSuspend,
std/zig/ast.zig
@@ -125,6 +125,7 @@ pub const Error = union(enum) {
     ExtraAlignQualifier: ExtraAlignQualifier,
     ExtraConstQualifier: ExtraConstQualifier,
     ExtraVolatileQualifier: ExtraVolatileQualifier,
+    ExtraAllowZeroQualifier: ExtraAllowZeroQualifier,
     ExpectedPrimaryExpr: ExpectedPrimaryExpr,
     ExpectedToken: ExpectedToken,
     ExpectedCommaOrEnd: ExpectedCommaOrEnd,
@@ -149,6 +150,7 @@ pub const Error = union(enum) {
             @TagType(Error).ExtraAlignQualifier => |*x| return x.render(tokens, stream),
             @TagType(Error).ExtraConstQualifier => |*x| return x.render(tokens, stream),
             @TagType(Error).ExtraVolatileQualifier => |*x| return x.render(tokens, stream),
+            @TagType(Error).ExtraAllowZeroQualifier => |*x| return x.render(tokens, stream),
             @TagType(Error).ExpectedPrimaryExpr => |*x| return x.render(tokens, stream),
             @TagType(Error).ExpectedToken => |*x| return x.render(tokens, stream),
             @TagType(Error).ExpectedCommaOrEnd => |*x| return x.render(tokens, stream),
@@ -175,6 +177,7 @@ pub const Error = union(enum) {
             @TagType(Error).ExtraAlignQualifier => |x| return x.token,
             @TagType(Error).ExtraConstQualifier => |x| return x.token,
             @TagType(Error).ExtraVolatileQualifier => |x| return x.token,
+            @TagType(Error).ExtraAllowZeroQualifier => |x| return x.token,
             @TagType(Error).ExpectedPrimaryExpr => |x| return x.token,
             @TagType(Error).ExpectedToken => |x| return x.token,
             @TagType(Error).ExpectedCommaOrEnd => |x| return x.token,
@@ -198,6 +201,7 @@ pub const Error = union(enum) {
     pub const ExtraAlignQualifier = SimpleError("Extra align qualifier");
     pub const ExtraConstQualifier = SimpleError("Extra const qualifier");
     pub const ExtraVolatileQualifier = SimpleError("Extra volatile qualifier");
+    pub const ExtraAllowZeroQualifier = SimpleError("Extra allowzero qualifier");
 
     pub const ExpectedCall = struct {
         node: *Node,
@@ -1540,6 +1544,7 @@ pub const Node = struct {
         };
 
         pub const PtrInfo = struct {
+            allowzero_token: ?TokenIndex,
             align_info: ?Align,
             const_token: ?TokenIndex,
             volatile_token: ?TokenIndex,
std/zig/parse.zig
@@ -1688,6 +1688,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree {
                             .align_info = null,
                             .const_token = null,
                             .volatile_token = null,
+                            .allowzero_token = null,
                         },
                     };
                     stack.append(State{ .TypeExprBegin = OptionalCtx{ .Required = &node.rhs } }) catch unreachable;
@@ -1743,6 +1744,15 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree {
                         addr_of_info.volatile_token = token_index;
                         continue;
                     },
+                    Token.Id.Keyword_allowzero => {
+                        stack.append(state) catch unreachable;
+                        if (addr_of_info.allowzero_token != null) {
+                            ((try tree.errors.addOne())).* = Error{ .ExtraAllowZeroQualifier = Error.ExtraAllowZeroQualifier{ .token = token_index } };
+                            return tree;
+                        }
+                        addr_of_info.allowzero_token = token_index;
+                        continue;
+                    },
                     else => {
                         prevToken(&tok_it, &tree);
                         continue;
@@ -3552,6 +3562,7 @@ fn tokenIdToPrefixOp(id: Token.Id) ?ast.Node.PrefixOp.Op {
                 .align_info = null,
                 .const_token = null,
                 .volatile_token = null,
+                .allowzero_token = null,
             },
         },
         Token.Id.QuestionMark => ast.Node.PrefixOp.Op{ .OptionalType = void{} },
std/zig/parser_test.zig
@@ -1,3 +1,10 @@
+test "zig fmt: allowzero pointer" {
+    try testCanonical(
+        \\const T = [*]allowzero const u8;
+        \\
+    );
+}
+
 test "zig fmt: enum literal" {
     try testCanonical(
         \\const x = .hi;
std/zig/render.zig
@@ -379,6 +379,9 @@ fn renderExpression(
                         else => usize(0),
                     };
                     try renderTokenOffset(tree, stream, prefix_op_node.op_token, indent, start_col, Space.None, star_offset); // *
+                    if (ptr_info.allowzero_token) |allowzero_token| {
+                        try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero
+                    }
                     if (ptr_info.align_info) |align_info| {
                         const lparen_token = tree.prevToken(align_info.node.firstToken());
                         const align_token = tree.prevToken(lparen_token);
@@ -416,6 +419,9 @@ fn renderExpression(
                     try renderToken(tree, stream, prefix_op_node.op_token, indent, start_col, Space.None); // [
                     try renderToken(tree, stream, tree.nextToken(prefix_op_node.op_token), indent, start_col, Space.None); // ]
 
+                    if (ptr_info.allowzero_token) |allowzero_token| {
+                        try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero
+                    }
                     if (ptr_info.align_info) |align_info| {
                         const lparen_token = tree.prevToken(align_info.node.firstToken());
                         const align_token = tree.prevToken(lparen_token);
std/zig/tokenizer.zig
@@ -13,6 +13,7 @@ pub const Token = struct {
 
     pub const keywords = []Keyword{
         Keyword{ .bytes = "align", .id = Id.Keyword_align },
+        Keyword{ .bytes = "allowzero", .id = Id.Keyword_allowzero },
         Keyword{ .bytes = "and", .id = Id.Keyword_and },
         Keyword{ .bytes = "anyerror", .id = Id.Keyword_anyerror },
         Keyword{ .bytes = "asm", .id = Id.Keyword_asm },
@@ -143,6 +144,7 @@ pub const Token = struct {
         BracketStarCBracket,
         ShebangLine,
         Keyword_align,
+        Keyword_allowzero,
         Keyword_and,
         Keyword_anyerror,
         Keyword_asm,
test/stage1/behavior/pointers.zig
@@ -137,3 +137,16 @@ test "compare equality of optional and non-optional pointer" {
     expect(a == b);
     expect(b == a);
 }
+
+test "allowzero pointer and slice" {
+    var ptr = @intToPtr([*]allowzero i32, 0);
+    var opt_ptr: ?[*]allowzero i32 = ptr;
+    expect(opt_ptr != null);
+    expect(@ptrToInt(ptr) == 0);
+    var slice = ptr[0..10];
+    expect(@typeOf(slice) == []allowzero i32);
+    expect(@ptrToInt(&slice[5]) == 20);
+
+    expect(@typeInfo(@typeOf(ptr)).Pointer.is_allowzero);
+    expect(@typeInfo(@typeOf(slice)).Pointer.is_allowzero);
+}
test/compile_errors.zig
@@ -2,6 +2,15 @@ const tests = @import("tests.zig");
 const builtin = @import("builtin");
 
 pub fn addCases(cases: *tests.CompileErrorContext) void {
+    cases.add(
+        "@ptrToInt 0 to non optional pointer",
+        \\export fn entry() void {
+        \\    var b = @intToPtr(*i32, 0);
+        \\}
+    ,
+        "tmp.zig:2:13: error: pointer type '*i32' does not allow address zero",
+    );
+
     cases.add(
         "cast enum literal to enum but it doesn't match",
         \\const Foo = enum {
test/runtime_safety.zig
@@ -1,6 +1,16 @@
 const tests = @import("tests.zig");
 
 pub fn addCases(cases: *tests.CompareOutputContext) void {
+    cases.addRuntimeSafety("@ptrToInt address zero to non-optional pointer",
+        \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn {
+        \\    @import("std").os.exit(126);
+        \\}
+        \\pub fn main() void {
+        \\    var zero: usize = 0;
+        \\    var b = @intToPtr(*i32, zero);
+        \\}
+    );
+
     cases.addRuntimeSafety("pointer casting null to non-optional pointer",
         \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn {
         \\    @import("std").os.exit(126);