Commit 71d95c6597

Andrew Kelley <superjoe30@gmail.com>
2016-11-21 09:08:24
IR: support unwrap maybe operation
1 parent b47e2fa
src/all_types.hpp
@@ -1453,6 +1453,8 @@ enum IrInstructionId {
     IrInstructionIdAsm,
     IrInstructionIdCompileVar,
     IrInstructionIdSizeOf,
+    IrInstructionIdTestNull,
+    IrInstructionIdUnwrapMaybe,
 };
 
 struct IrInstruction {
@@ -1749,6 +1751,21 @@ struct IrInstructionSizeOf {
     IrInstruction *type_value;
 };
 
+// returns true if nonnull, returns false if null
+// this is so that `zeroes` sets maybe values to null
+struct IrInstructionTestNull {
+    IrInstruction base;
+
+    IrInstruction *value;
+};
+
+struct IrInstructionUnwrapMaybe {
+    IrInstruction base;
+
+    IrInstruction *value;
+    bool safety_check_on;
+};
+
 enum LValPurpose {
     LValPurposeNone,
     LValPurposeAssign,
@@ -1758,4 +1775,7 @@ enum LValPurpose {
 static const size_t slice_ptr_index = 0;
 static const size_t slice_len_index = 1;
 
+static const size_t maybe_child_index = 0;
+static const size_t maybe_null_index = 1;
+
 #endif
src/ast_render.cpp
@@ -706,6 +706,27 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
                 fprintf(ar->f, "null");
                 break;
             }
+        case NodeTypeIfVarExpr:
+            {
+                AstNodeVariableDeclaration *var_decl = &node->data.if_var_expr.var_decl;
+                const char *var_str = var_decl->is_const ? "const" : "var";
+                const char *var_name = buf_ptr(var_decl->symbol);
+                const char *ptr_str = node->data.if_var_expr.var_is_ptr ? "*" : "";
+                fprintf(ar->f, "if (%s %s%s", var_str, ptr_str, var_name);
+                if (var_decl->type) {
+                    fprintf(ar->f, ": ");
+                    render_node_ungrouped(ar, var_decl->type);
+                }
+                fprintf(ar->f, " = ");
+                render_node_grouped(ar, var_decl->expr);
+                fprintf(ar->f, ") ");
+                render_node_grouped(ar, node->data.if_var_expr.then_block);
+                if (node->data.if_var_expr.else_node) {
+                    fprintf(ar->f, " else ");
+                    render_node_grouped(ar, node->data.if_var_expr.else_node);
+                }
+                break;
+            }
         case NodeTypeFnDecl:
         case NodeTypeParamDecl:
         case NodeTypeErrorValueDecl:
@@ -715,7 +736,6 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
         case NodeTypeStructValueField:
         case NodeTypeUse:
         case NodeTypeZeroesLiteral:
-        case NodeTypeIfVarExpr:
         case NodeTypeForExpr:
         case NodeTypeSwitchExpr:
         case NodeTypeSwitchProng:
src/codegen.cpp
@@ -1427,7 +1427,56 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru
     return LLVMBuildCall(g->builder, asm_fn, param_values, input_and_output_count, "");
 }
 
+// 0 - null, 1 - non null
+static LLVMValueRef gen_null_bit(CodeGen *g, TypeTableEntry *ptr_type, LLVMValueRef maybe_ptr) {
+    assert(ptr_type->id == TypeTableEntryIdPointer);
+    TypeTableEntry *maybe_type = ptr_type->data.pointer.child_type;
+    assert(maybe_type->id == TypeTableEntryIdMaybe);
+    TypeTableEntry *child_type = maybe_type->data.maybe.child_type;
+    LLVMValueRef maybe_struct_ref = get_handle_value(g, maybe_ptr, maybe_type);
+    bool maybe_is_ptr = (child_type->id == TypeTableEntryIdPointer || child_type->id == TypeTableEntryIdFn);
+    if (maybe_is_ptr) {
+        return LLVMBuildICmp(g->builder, LLVMIntNE, maybe_struct_ref, LLVMConstNull(child_type->type_ref), "");
+    } else {
+        LLVMValueRef maybe_field_ptr = LLVMBuildStructGEP(g->builder, maybe_struct_ref, maybe_null_index, "");
+        return LLVMBuildLoad(g->builder, maybe_field_ptr, "");
+    }
+}
+
+static LLVMValueRef ir_render_test_null(CodeGen *g, IrExecutable *executable, IrInstructionTestNull *instruction) {
+    TypeTableEntry *ptr_type = instruction->value->type_entry;
+    assert(ptr_type->id == TypeTableEntryIdPointer);
+    return gen_null_bit(g, ptr_type, ir_llvm_value(g, instruction->value));
+}
+
+static LLVMValueRef ir_render_unwrap_maybe(CodeGen *g, IrExecutable *executable,
+        IrInstructionUnwrapMaybe *instruction)
+{
+    TypeTableEntry *ptr_type = instruction->value->type_entry;
+    assert(ptr_type->id == TypeTableEntryIdPointer);
+    TypeTableEntry *maybe_type = ptr_type->data.pointer.child_type;
+    assert(maybe_type->id == TypeTableEntryIdMaybe);
+    TypeTableEntry *child_type = maybe_type->data.maybe.child_type;
+    bool maybe_is_ptr = (child_type->id == TypeTableEntryIdPointer || child_type->id == TypeTableEntryIdFn);
+    LLVMValueRef maybe_ptr = ir_llvm_value(g, instruction->value);
+    if (ir_want_debug_safety(g, &instruction->base) && instruction->safety_check_on) {
+        LLVMValueRef nonnull_bit = gen_null_bit(g, ptr_type, maybe_ptr);
+        LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "UnwrapMaybeOk");
+        LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "UnwrapMaybeFail");
+        LLVMBuildCondBr(g->builder, nonnull_bit, ok_block, fail_block);
 
+        LLVMPositionBuilderAtEnd(g->builder, fail_block);
+        gen_debug_safety_crash(g);
+
+        LLVMPositionBuilderAtEnd(g->builder, ok_block);
+    }
+    if (maybe_is_ptr) {
+        return maybe_ptr;
+    } else {
+        LLVMValueRef maybe_struct_ref = get_handle_value(g, maybe_ptr, maybe_type);
+        return LLVMBuildStructGEP(g->builder, maybe_struct_ref, maybe_child_index, "");
+    }
+}
 
 static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, IrInstruction *instruction) {
     set_debug_source_node(g, instruction->source_node);
@@ -1476,6 +1525,10 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
             return ir_render_struct_field_ptr(g, executable, (IrInstructionStructFieldPtr *)instruction);
         case IrInstructionIdAsm:
             return ir_render_asm(g, executable, (IrInstructionAsm *)instruction);
+        case IrInstructionIdTestNull:
+            return ir_render_test_null(g, executable, (IrInstructionTestNull *)instruction);
+        case IrInstructionIdUnwrapMaybe:
+            return ir_render_unwrap_maybe(g, executable, (IrInstructionUnwrapMaybe *)instruction);
         case IrInstructionIdSwitchBr:
         case IrInstructionIdPhi:
         case IrInstructionIdContainerInitList:
src/ir.cpp
@@ -226,6 +226,14 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionSizeOf *) {
     return IrInstructionIdSizeOf;
 }
 
+static constexpr IrInstructionId ir_instruction_id(IrInstructionTestNull *) {
+    return IrInstructionIdTestNull;
+}
+
+static constexpr IrInstructionId ir_instruction_id(IrInstructionUnwrapMaybe *) {
+    return IrInstructionIdUnwrapMaybe;
+}
+
 template<typename T>
 static T *ir_create_instruction(IrExecutable *exec, AstNode *source_node) {
     T *special_instruction = allocate<T>(1);
@@ -878,6 +886,44 @@ static IrInstruction *ir_build_size_of(IrBuilder *irb, AstNode *source_node, IrI
     return &instruction->base;
 }
 
+static IrInstruction *ir_build_test_null(IrBuilder *irb, AstNode *source_node, IrInstruction *value) {
+    IrInstructionTestNull *instruction = ir_build_instruction<IrInstructionTestNull>(irb, source_node);
+    instruction->value = value;
+
+    ir_ref_instruction(value);
+
+    return &instruction->base;
+}
+
+static IrInstruction *ir_build_test_null_from(IrBuilder *irb, IrInstruction *old_instruction,
+        IrInstruction *value)
+{
+    IrInstruction *new_instruction = ir_build_test_null(irb, old_instruction->source_node, value);
+    ir_link_new_instruction(new_instruction, old_instruction);
+    return new_instruction;
+}
+
+static IrInstruction *ir_build_unwrap_maybe(IrBuilder *irb, AstNode *source_node, IrInstruction *value,
+        bool safety_check_on)
+{
+    IrInstructionUnwrapMaybe *instruction = ir_build_instruction<IrInstructionUnwrapMaybe>(irb, source_node);
+    instruction->value = value;
+    instruction->safety_check_on = safety_check_on;
+
+    ir_ref_instruction(value);
+
+    return &instruction->base;
+}
+
+static IrInstruction *ir_build_unwrap_maybe_from(IrBuilder *irb, IrInstruction *old_instruction,
+        IrInstruction *value, bool safety_check_on)
+{
+    IrInstruction *new_instruction = ir_build_unwrap_maybe(irb, old_instruction->source_node,
+            value, safety_check_on);
+    ir_link_new_instruction(new_instruction, old_instruction);
+    return new_instruction;
+}
+
 static void ir_gen_defers_for_block(IrBuilder *irb, BlockContext *inner_block, BlockContext *outer_block,
         bool gen_error_defers, bool gen_maybe_defers)
 {
@@ -1164,7 +1210,6 @@ static IrInstruction *ir_gen_null_literal(IrBuilder *irb, AstNode *node) {
     return ir_build_const_null(irb, node);
 }
 
-
 static IrInstruction *ir_gen_decl_ref(IrBuilder *irb, AstNode *source_node, AstNode *decl_node,
         LValPurpose lval, BlockContext *scope)
 {
@@ -1502,6 +1547,15 @@ static IrInstruction *ir_gen_prefix_op_id(IrBuilder *irb, AstNode *node, IrUnOp
     return ir_gen_prefix_op_id_lval(irb, node, op_id, LValPurposeNone);
 }
 
+static IrInstruction *ir_gen_prefix_op_unwrap_maybe(IrBuilder *irb, AstNode *node) {
+    AstNode *expr = node->data.prefix_op_expr.primary_expr;
+    IrInstruction *value = ir_gen_node(irb, expr, node->block_context);
+    if (value == irb->codegen->invalid_instruction)
+        return value;
+
+    return ir_build_unwrap_maybe(irb, node, value, true);
+}
+
 static IrInstruction *ir_gen_prefix_op_expr(IrBuilder *irb, AstNode *node, LValPurpose lval) {
     assert(node->type == NodeTypePrefixOpExpr);
 
@@ -1531,7 +1585,7 @@ static IrInstruction *ir_gen_prefix_op_expr(IrBuilder *irb, AstNode *node, LValP
         case PrefixOpUnwrapError:
             return ir_gen_prefix_op_id(irb, node, IrUnOpUnwrapError);
         case PrefixOpUnwrapMaybe:
-            return ir_gen_prefix_op_id(irb, node, IrUnOpUnwrapMaybe);
+            return ir_gen_prefix_op_unwrap_maybe(irb, node);
     }
     zig_unreachable();
 }
@@ -1883,6 +1937,69 @@ static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, AstNode *node) {
     return ir_build_asm(irb, node, input_list, output_types, return_count, is_volatile);
 }
 
+static IrInstruction *ir_gen_if_var_expr(IrBuilder *irb, AstNode *node) {
+    assert(node->type == NodeTypeIfVarExpr);
+
+    AstNodeVariableDeclaration *var_decl = &node->data.if_var_expr.var_decl;
+    AstNode *expr_node = var_decl->expr;
+    AstNode *then_node = node->data.if_var_expr.then_block;
+    AstNode *else_node = node->data.if_var_expr.else_node;
+    bool var_is_ptr = node->data.if_var_expr.var_is_ptr;
+
+    IrInstruction *expr_value = ir_gen_node_extra(irb, expr_node, node->block_context, LValPurposeAddressOf);
+    if (expr_value == irb->codegen->invalid_instruction)
+        return expr_value;
+
+    IrInstruction *is_nonnull_value = ir_build_test_null(irb, node, expr_value);
+
+    IrBasicBlock *then_block = ir_build_basic_block(irb, "MaybeThen");
+    IrBasicBlock *else_block = ir_build_basic_block(irb, "MaybeElse");
+    IrBasicBlock *endif_block = ir_build_basic_block(irb, "MaybeEndIf");
+
+    bool is_inline = (node->block_context->fn_entry == nullptr);
+    ir_build_cond_br(irb, node, is_nonnull_value, then_block, else_block, is_inline);
+
+    ir_set_cursor_at_end(irb, then_block);
+    IrInstruction *var_type = nullptr;
+    if (var_decl->type)
+        var_type = ir_gen_node(irb, var_decl->type, node->block_context);
+    BlockContext *child_scope = new_block_context(node, node->block_context);
+    bool is_shadowable = false;
+    bool is_const = var_decl->is_const;
+    VariableTableEntry *var = ir_add_local_var(irb, node, child_scope,
+            var_decl->symbol, is_const, is_const, is_shadowable, is_inline);
+    IrInstruction *var_ptr_value = ir_build_unwrap_maybe(irb, node, expr_value, false);
+    IrInstruction *var_value = var_is_ptr ? var_ptr_value : ir_build_load_ptr(irb, node, var_ptr_value);
+    ir_build_var_decl(irb, node, var, var_type, var_value); 
+    IrInstruction *then_expr_result = ir_gen_node(irb, then_node, child_scope);
+    if (then_expr_result == irb->codegen->invalid_instruction)
+        return then_expr_result;
+    IrBasicBlock *after_then_block = irb->current_basic_block;
+    ir_build_br(irb, node, endif_block, is_inline);
+
+    ir_set_cursor_at_end(irb, else_block);
+    IrInstruction *else_expr_result;
+    if (else_node) {
+        else_expr_result = ir_gen_node(irb, else_node, node->block_context);
+        if (else_expr_result == irb->codegen->invalid_instruction)
+            return else_expr_result;
+    } else {
+        else_expr_result = ir_build_const_void(irb, node);
+    }
+    IrBasicBlock *after_else_block = irb->current_basic_block;
+    ir_build_br(irb, node, endif_block, is_inline);
+
+    ir_set_cursor_at_end(irb, endif_block);
+    IrInstruction **incoming_values = allocate<IrInstruction *>(2);
+    incoming_values[0] = then_expr_result;
+    incoming_values[1] = else_expr_result;
+    IrBasicBlock **incoming_blocks = allocate<IrBasicBlock *>(2);
+    incoming_blocks[0] = after_then_block;
+    incoming_blocks[1] = after_else_block;
+
+    return ir_build_phi(irb, node, 2, incoming_blocks, incoming_values);
+}
+
 static IrInstruction *ir_gen_node_extra(IrBuilder *irb, AstNode *node, BlockContext *block_context,
         LValPurpose lval)
 {
@@ -1932,10 +2049,11 @@ static IrInstruction *ir_gen_node_extra(IrBuilder *irb, AstNode *node, BlockCont
             return ir_gen_asm_expr(irb, node);
         case NodeTypeNullLiteral:
             return ir_gen_null_literal(irb, node);
+        case NodeTypeIfVarExpr:
+            return ir_gen_if_var_expr(irb, node);
         case NodeTypeUnwrapErrorExpr:
         case NodeTypeDefer:
         case NodeTypeSliceExpr:
-        case NodeTypeIfVarExpr:
         case NodeTypeGoto:
         case NodeTypeBreak:
         case NodeTypeContinue:
@@ -4488,6 +4606,82 @@ static TypeTableEntry *ir_analyze_instruction_size_of(IrAnalyze *ira,
     zig_unreachable();
 }
 
+static TypeTableEntry *ir_analyze_instruction_test_null(IrAnalyze *ira,
+        IrInstructionTestNull *test_null_instruction)
+{
+    IrInstruction *value = test_null_instruction->value->other;
+    if (value->type_entry->id == TypeTableEntryIdInvalid)
+        return ira->codegen->builtin_types.entry_invalid;
+
+    // This will be a pointer type because test null IR instruction operates on a pointer to a thing.
+    TypeTableEntry *ptr_type = value->type_entry;
+    assert(ptr_type->id == TypeTableEntryIdPointer);
+
+    TypeTableEntry *type_entry = ptr_type->data.pointer.child_type;
+    if (type_entry->id != TypeTableEntryIdMaybe) {
+        add_node_error(ira->codegen, test_null_instruction->base.source_node,
+                buf_sprintf("expected nullable type, found '%s'", buf_ptr(&type_entry->name)));
+        return ira->codegen->builtin_types.entry_invalid;
+    }
+
+    if (value->static_value.special != ConstValSpecialRuntime) {
+        ConstExprValue *maybe_val = value->static_value.data.x_ptr.base_ptr;
+        assert(value->static_value.data.x_ptr.index == SIZE_MAX);
+
+        if (maybe_val->special != ConstValSpecialRuntime) {
+            bool depends_on_compile_var = maybe_val->depends_on_compile_var;
+            ConstExprValue *out_val = ir_build_const_from(ira, &test_null_instruction->base,
+                    depends_on_compile_var);
+            out_val->data.x_bool = (maybe_val->data.x_maybe == nullptr);
+            return ira->codegen->builtin_types.entry_bool;
+        }
+    }
+
+    ir_build_test_null_from(&ira->new_irb, &test_null_instruction->base, value);
+    return ira->codegen->builtin_types.entry_bool;
+}
+
+static TypeTableEntry *ir_analyze_instruction_unwrap_maybe(IrAnalyze *ira,
+        IrInstructionUnwrapMaybe *unwrap_maybe_instruction)
+{
+    IrInstruction *value = unwrap_maybe_instruction->value->other;
+    if (value->type_entry->id == TypeTableEntryIdInvalid)
+        return ira->codegen->builtin_types.entry_invalid;
+
+    // This will be a pointer type because test null IR instruction operates on a pointer to a thing.
+    TypeTableEntry *ptr_type = value->type_entry;
+    assert(ptr_type->id == TypeTableEntryIdPointer);
+
+    TypeTableEntry *type_entry = ptr_type->data.pointer.child_type;
+    if (type_entry->id == TypeTableEntryIdInvalid) {
+        return ira->codegen->builtin_types.entry_invalid;
+    } else if (type_entry->id != TypeTableEntryIdMaybe) {
+        add_node_error(ira->codegen, unwrap_maybe_instruction->base.source_node,
+                buf_sprintf("expected nullable type, found '%s'", buf_ptr(&type_entry->name)));
+        return ira->codegen->builtin_types.entry_invalid;
+    }
+    TypeTableEntry *child_type = type_entry->data.maybe.child_type;
+    TypeTableEntry *result_type = get_pointer_to_type(ira->codegen, child_type, false);
+
+    if (value->static_value.special != ConstValSpecialRuntime) {
+        ConstExprValue *maybe_val = value->static_value.data.x_ptr.base_ptr;
+        assert(value->static_value.data.x_ptr.index == SIZE_MAX);
+
+        if (maybe_val->special != ConstValSpecialRuntime) {
+            bool depends_on_compile_var = maybe_val->depends_on_compile_var;
+            ConstExprValue *out_val = ir_build_const_from(ira, &unwrap_maybe_instruction->base,
+                    depends_on_compile_var);
+            out_val->data.x_ptr.base_ptr = maybe_val;
+            out_val->data.x_ptr.index = SIZE_MAX;
+            return result_type;
+        }
+    }
+
+    ir_build_unwrap_maybe_from(&ira->new_irb, &unwrap_maybe_instruction->base, value,
+            unwrap_maybe_instruction->safety_check_on);
+    return result_type;
+}
+
 static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstruction *instruction) {
     switch (instruction->id) {
         case IrInstructionIdInvalid:
@@ -4544,6 +4738,10 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi
             return ir_analyze_instruction_compile_var(ira, (IrInstructionCompileVar *)instruction);
         case IrInstructionIdSizeOf:
             return ir_analyze_instruction_size_of(ira, (IrInstructionSizeOf *)instruction);
+        case IrInstructionIdTestNull:
+            return ir_analyze_instruction_test_null(ira, (IrInstructionTestNull *)instruction);
+        case IrInstructionIdUnwrapMaybe:
+            return ir_analyze_instruction_unwrap_maybe(ira, (IrInstructionUnwrapMaybe *)instruction);
         case IrInstructionIdSwitchBr:
         case IrInstructionIdCast:
         case IrInstructionIdContainerInitList:
@@ -4654,6 +4852,8 @@ bool ir_has_side_effects(IrInstruction *instruction) {
         case IrInstructionIdSliceType:
         case IrInstructionIdCompileVar:
         case IrInstructionIdSizeOf:
+        case IrInstructionIdTestNull:
+        case IrInstructionIdUnwrapMaybe:
             return false;
         case IrInstructionIdAsm:
             {
@@ -6266,32 +6466,6 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //    return result_type;
 //}
 //
-//static TypeTableEntry *analyze_if_var_expr(CodeGen *g, ImportTableEntry *import, BlockContext *parent_context,
-//        TypeTableEntry *expected_type, AstNode *node)
-//{
-//    assert(node->type == NodeTypeIfVarExpr);
-//
-//    BlockContext *child_context = new_block_context(node, parent_context);
-//
-//    analyze_variable_declaration_raw(g, import, child_context, node, &node->data.if_var_expr.var_decl, true,
-//        nullptr, node->data.if_var_expr.var_is_ptr);
-//    VariableTableEntry *var = node->data.if_var_expr.var_decl.variable;
-//    if (var->type->id == TypeTableEntryIdInvalid) {
-//        return g->builtin_types.entry_invalid;
-//    }
-//    AstNode *var_expr_node = node->data.if_var_expr.var_decl.expr;
-//    ConstExprValue *var_const_val = &get_resolved_expr(var_expr_node)->const_val;
-//    bool cond_is_const = var_const_val->ok;
-//    bool cond_bool_val = cond_is_const ? (var_const_val->data.x_maybe != nullptr) : false;
-//
-//
-//    AstNode **then_node = &node->data.if_var_expr.then_block;
-//    AstNode **else_node = &node->data.if_var_expr.else_node;
-//
-//    return analyze_if(g, import, child_context, expected_type,
-//            node, then_node, else_node, cond_is_const, cond_bool_val);
-//}
-//
 //static TypeTableEntry *bad_method_call(CodeGen *g, AstNode *node, TypeTableEntry *container_type,
 //        TypeTableEntry *expected_param_type, FnTableEntry *fn_table_entry)
 //{
@@ -7407,92 +7581,6 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //    }
 //}
 //
-//
-//static VariableTableEntry *analyze_variable_declaration_raw(CodeGen *g, ImportTableEntry *import,
-//        BlockContext *context, AstNode *source_node,
-//        AstNodeVariableDeclaration *variable_declaration,
-//        bool expr_is_maybe, AstNode *decl_node, bool var_is_ptr)
-//{
-//    bool is_const = variable_declaration->is_const;
-//    bool is_export = (variable_declaration->top_level_decl.visib_mod == VisibModExport);
-//    bool is_extern = variable_declaration->is_extern;
-//
-//    TypeTableEntry *explicit_type = nullptr;
-//    if (variable_declaration->type != nullptr) {
-//        explicit_type = analyze_type_expr(g, import, context, variable_declaration->type);
-//        if (explicit_type->id == TypeTableEntryIdUnreachable) {
-//            add_node_error(g, variable_declaration->type,
-//                buf_sprintf("variable of type 'unreachable' not allowed"));
-//            explicit_type = g->builtin_types.entry_invalid;
-//        }
-//    }
-//
-//    TypeTableEntry *implicit_type = nullptr;
-//    if (explicit_type && explicit_type->id == TypeTableEntryIdInvalid) {
-//        implicit_type = explicit_type;
-//    } else if (variable_declaration->expr) {
-//        implicit_type = analyze_expression(g, import, context, explicit_type, variable_declaration->expr);
-//        if (implicit_type->id == TypeTableEntryIdInvalid) {
-//            // ignore the poison value
-//        } else if (expr_is_maybe) {
-//            if (implicit_type->id == TypeTableEntryIdMaybe) {
-//                if (var_is_ptr) {
-//                    // TODO if the expression is constant, can't get pointer to it
-//                    implicit_type = get_pointer_to_type(g, implicit_type->data.maybe.child_type, false);
-//                } else {
-//                    implicit_type = implicit_type->data.maybe.child_type;
-//                }
-//            } else {
-//                add_node_error(g, variable_declaration->expr, buf_sprintf("expected maybe type"));
-//                implicit_type = g->builtin_types.entry_invalid;
-//            }
-//        } else if (implicit_type->id == TypeTableEntryIdUnreachable) {
-//            add_node_error(g, source_node,
-//                buf_sprintf("variable initialization is unreachable"));
-//            implicit_type = g->builtin_types.entry_invalid;
-//        } else if ((!is_const || is_export) &&
-//                (implicit_type->id == TypeTableEntryIdNumLitFloat ||
-//                 implicit_type->id == TypeTableEntryIdNumLitInt))
-//        {
-//            add_node_error(g, source_node, buf_sprintf("unable to infer variable type"));
-//            implicit_type = g->builtin_types.entry_invalid;
-//        } else if (implicit_type->id == TypeTableEntryIdMetaType && !is_const) {
-//            add_node_error(g, source_node, buf_sprintf("variable of type 'type' must be constant"));
-//            implicit_type = g->builtin_types.entry_invalid;
-//        }
-//        if (implicit_type->id != TypeTableEntryIdInvalid && !context->fn_entry) {
-//            ConstExprValue *const_val = &get_resolved_expr(variable_declaration->expr)->const_val;
-//            if (!const_val->ok) {
-//                add_node_error(g, first_executing_node(variable_declaration->expr),
-//                        buf_sprintf("global variable initializer requires constant expression"));
-//            }
-//        }
-//    } else if (!is_extern) {
-//        add_node_error(g, source_node, buf_sprintf("variables must be initialized"));
-//        implicit_type = g->builtin_types.entry_invalid;
-//    }
-//
-//    TypeTableEntry *type = explicit_type != nullptr ? explicit_type : implicit_type;
-//    assert(type != nullptr); // should have been caught by the parser
-//
-//    VariableTableEntry *var = add_local_var(g, source_node, import, context,
-//            variable_declaration->symbol, type, is_const,
-//            expr_is_maybe ? nullptr : variable_declaration->expr);
-//
-//    variable_declaration->variable = var;
-//
-//    return var;
-//}
-//
-//static VariableTableEntry *analyze_variable_declaration(CodeGen *g, ImportTableEntry *import,
-//        BlockContext *context, TypeTableEntry *expected_type, AstNode *node)
-//{
-//    AstNodeVariableDeclaration *variable_declaration = &node->data.variable_declaration;
-//    return analyze_variable_declaration_raw(g, import, context, node, variable_declaration,
-//            false, nullptr, false);
-//}
-//
-//
 //static TypeTableEntry *analyze_while_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
 //        TypeTableEntry *expected_type, AstNode *node)
 //{
@@ -8581,64 +8669,6 @@ static void analyze_goto_pass2(CodeGen *g, ImportTableEntry *import, AstNode *no
 //    return nullptr;
 //}
 //
-//static LLVMValueRef gen_unwrap_maybe_expr(CodeGen *g, AstNode *node) {
-//    assert(node->type == NodeTypeBinOpExpr);
-//    assert(node->data.bin_op_expr.bin_op == BinOpTypeUnwrapMaybe);
-//
-//    AstNode *op1_node = node->data.bin_op_expr.op1;
-//    AstNode *op2_node = node->data.bin_op_expr.op2;
-//
-//    LLVMValueRef maybe_struct_ref = gen_expr(g, op1_node);
-//
-//    TypeTableEntry *maybe_type = get_expr_type(op1_node);
-//    assert(maybe_type->id == TypeTableEntryIdMaybe);
-//    TypeTableEntry *child_type = maybe_type->data.maybe.child_type;
-//
-//    LLVMValueRef cond_value;
-//    if (child_type->id == TypeTableEntryIdPointer ||
-//        child_type->id == TypeTableEntryIdFn)
-//    {
-//        cond_value = LLVMBuildICmp(g->builder, LLVMIntNE, maybe_struct_ref,
-//                LLVMConstNull(child_type->type_ref), "");
-//    } else {
-//        LLVMValueRef maybe_field_ptr = LLVMBuildStructGEP(g->builder, maybe_struct_ref, 1, "");
-//        cond_value = LLVMBuildLoad(g->builder, maybe_field_ptr, "");
-//    }
-//
-//    LLVMBasicBlockRef non_null_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "MaybeNonNull");
-//    LLVMBasicBlockRef null_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "MaybeNull");
-//    LLVMBasicBlockRef end_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "MaybeEnd");
-//
-//    bool null_reachable = get_expr_type(op2_node)->id != TypeTableEntryIdUnreachable;
-//
-//    LLVMBuildCondBr(g->builder, cond_value, non_null_block, null_block);
-//
-//    LLVMPositionBuilderAtEnd(g->builder, non_null_block);
-//    LLVMValueRef non_null_result = gen_unwrap_maybe(g, op1_node, maybe_struct_ref);
-//    LLVMBuildBr(g->builder, end_block);
-//    LLVMBasicBlockRef post_non_null_result_block = LLVMGetInsertBlock(g->builder);
-//
-//    LLVMPositionBuilderAtEnd(g->builder, null_block);
-//    LLVMValueRef null_result = gen_expr(g, op2_node);
-//    if (null_reachable) {
-//        LLVMBuildBr(g->builder, end_block);
-//    }
-//    LLVMBasicBlockRef post_null_result_block = LLVMGetInsertBlock(g->builder);
-//
-//    LLVMPositionBuilderAtEnd(g->builder, end_block);
-//    if (null_reachable) {
-//        LLVMValueRef phi = LLVMBuildPhi(g->builder, LLVMTypeOf(non_null_result), "");
-//        LLVMValueRef incoming_values[2] = {non_null_result, null_result};
-//        LLVMBasicBlockRef incoming_blocks[2] = {post_non_null_result_block, post_null_result_block};
-//        LLVMAddIncoming(phi, incoming_values, incoming_blocks, 2);
-//        return phi;
-//    } else {
-//        return non_null_result;
-//    }
-//
-//    return nullptr;
-//}
-//
 //static LLVMValueRef gen_unwrap_err_expr(CodeGen *g, AstNode *node) {
 //    assert(node->type == NodeTypeUnwrapErrorExpr);
 //
@@ -8914,120 +8944,6 @@ static void analyze_goto_pass2(CodeGen *g, ImportTableEntry *import, AstNode *no
 //    }
 //}
 //
-//static LLVMValueRef gen_if_var_then_block(CodeGen *g, AstNode *node, VariableTableEntry *variable, bool maybe_is_ptr,
-//        LLVMValueRef init_val, TypeTableEntry *child_type, AstNode *then_node)
-//{
-//    if (node->data.if_var_expr.var_is_ptr) {
-//        LLVMValueRef payload_ptr;
-//        if (maybe_is_ptr) {
-//            zig_panic("TODO");
-//        } else {
-//            payload_ptr = LLVMBuildStructGEP(g->builder, init_val, 0, "");
-//        }
-//        LLVMBuildStore(g->builder, payload_ptr, variable->value_ref);
-//    } else {
-//        LLVMValueRef payload_val;
-//        if (maybe_is_ptr) {
-//            payload_val = init_val;
-//        } else {
-//            LLVMValueRef payload_ptr = LLVMBuildStructGEP(g->builder, init_val, 0, "");
-//            payload_val = get_handle_value(g, payload_ptr, child_type);
-//        }
-//        gen_assign_raw(g, node, BinOpTypeAssign, variable->value_ref, payload_val,
-//                variable->type, child_type);
-//    }
-//    gen_var_debug_decl(g, variable);
-//
-//    return gen_expr(g, then_node);
-//}
-//
-//static LLVMValueRef gen_if_var_expr(CodeGen *g, AstNode *node) {
-//    assert(node->type == NodeTypeIfVarExpr);
-//    assert(node->data.if_var_expr.var_decl.expr);
-//
-//    AstNodeVariableDeclaration *var_decl = &node->data.if_var_expr.var_decl;
-//    VariableTableEntry *variable = var_decl->variable;
-//
-//    // test if value is the maybe state
-//    TypeTableEntry *expr_type = get_expr_type(var_decl->expr);
-//    TypeTableEntry *child_type = expr_type->data.maybe.child_type;
-//
-//    LLVMValueRef init_val = gen_expr(g, var_decl->expr);
-//
-//
-//    AstNode *then_node = node->data.if_var_expr.then_block;
-//    AstNode *else_node = node->data.if_var_expr.else_node;
-//    bool maybe_is_ptr = child_type->id == TypeTableEntryIdPointer || child_type->id == TypeTableEntryIdFn;
-//
-//    ConstExprValue *const_val = &get_resolved_expr(var_decl->expr)->const_val;
-//    if (const_val->ok) {
-//        if (const_val->data.x_maybe) {
-//            return gen_if_var_then_block(g, node, variable, maybe_is_ptr, init_val, child_type, then_node);
-//        } else {
-//            return gen_expr(g, else_node);
-//        }
-//    }
-//
-//    LLVMValueRef cond_value;
-//    if (maybe_is_ptr) {
-//        cond_value = LLVMBuildICmp(g->builder, LLVMIntNE, init_val, LLVMConstNull(child_type->type_ref), "");
-//    } else {
-//        LLVMValueRef maybe_field_ptr = LLVMBuildStructGEP(g->builder, init_val, 1, "");
-//        cond_value = LLVMBuildLoad(g->builder, maybe_field_ptr, "");
-//    }
-//
-//    TypeTableEntry *then_type = get_expr_type(then_node);
-//    TypeTableEntry *else_type = get_expr_type(else_node);
-//
-//    bool use_then_value = type_has_bits(then_type);
-//    bool use_else_value = type_has_bits(else_type);
-//
-//    LLVMBasicBlockRef then_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "MaybeThen");
-//    LLVMBasicBlockRef else_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "MaybeElse");
-//
-//    LLVMBasicBlockRef endif_block;
-//    bool then_endif_reachable = then_type->id != TypeTableEntryIdUnreachable;
-//    bool else_endif_reachable = else_type->id != TypeTableEntryIdUnreachable;
-//    if (then_endif_reachable || else_endif_reachable) {
-//        endif_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "MaybeEndIf");
-//    }
-//
-//    LLVMBuildCondBr(g->builder, cond_value, then_block, else_block);
-//
-//    LLVMPositionBuilderAtEnd(g->builder, then_block);
-//    LLVMValueRef then_expr_result = gen_if_var_then_block(g, node, variable, maybe_is_ptr, init_val, child_type, then_node);
-//
-//    if (then_endif_reachable) {
-//        LLVMBuildBr(g->builder, endif_block);
-//    }
-//    LLVMBasicBlockRef after_then_block = LLVMGetInsertBlock(g->builder);
-//
-//
-//    LLVMPositionBuilderAtEnd(g->builder, else_block);
-//    LLVMValueRef else_expr_result = gen_expr(g, else_node);
-//    if (else_endif_reachable) {
-//        LLVMBuildBr(g->builder, endif_block);
-//    }
-//    LLVMBasicBlockRef after_else_block = LLVMGetInsertBlock(g->builder);
-//
-//    if (then_endif_reachable || else_endif_reachable) {
-//        LLVMPositionBuilderAtEnd(g->builder, endif_block);
-//        if (use_then_value && use_else_value) {
-//            LLVMValueRef phi = LLVMBuildPhi(g->builder, LLVMTypeOf(then_expr_result), "");
-//            LLVMValueRef incoming_values[2] = {then_expr_result, else_expr_result};
-//            LLVMBasicBlockRef incoming_blocks[2] = {after_then_block, after_else_block};
-//            LLVMAddIncoming(phi, incoming_values, incoming_blocks, 2);
-//            return phi;
-//        } else if (use_then_value) {
-//            return then_expr_result;
-//        } else if (use_else_value) {
-//            return else_expr_result;
-//        }
-//    }
-//
-//    return nullptr;
-//}
-//
 //static LLVMValueRef gen_block(CodeGen *g, AstNode *block_node, TypeTableEntry *implicit_return_type) {
 //    assert(block_node->type == NodeTypeBlock);
 //
@@ -9667,20 +9583,6 @@ static void analyze_goto_pass2(CodeGen *g, ImportTableEntry *import, AstNode *no
 //    zig_unreachable();
 //}
 //
-//static LLVMValueRef gen_unwrap_maybe(CodeGen *g, AstNode *node, LLVMValueRef maybe_struct_ref) {
-//    TypeTableEntry *type_entry = get_expr_type(node);
-//    assert(type_entry->id == TypeTableEntryIdMaybe);
-//    TypeTableEntry *child_type = type_entry->data.maybe.child_type;
-//    if (child_type->id == TypeTableEntryIdPointer ||
-//        child_type->id == TypeTableEntryIdFn)
-//    {
-//        return maybe_struct_ref;
-//    } else {
-//        LLVMValueRef maybe_field_ptr = LLVMBuildStructGEP(g->builder, maybe_struct_ref, 0, "");
-//        return get_handle_value(g, maybe_field_ptr, child_type);
-//    }
-//}
-//
 //static size_t get_conditional_defer_count(BlockContext *inner_block, BlockContext *outer_block) {
 //    size_t result = 0;
 //    while (inner_block != outer_block) {
src/ir_print.cpp
@@ -497,6 +497,20 @@ static void ir_print_size_of(IrPrint *irp, IrInstructionSizeOf *instruction) {
     fprintf(irp->f, ")");
 }
 
+static void ir_print_test_null(IrPrint *irp, IrInstructionTestNull *instruction) {
+    fprintf(irp->f, "*");
+    ir_print_other_instruction(irp, instruction->value);
+    fprintf(irp->f, " == null");
+}
+
+static void ir_print_unwrap_maybe(IrPrint *irp, IrInstructionUnwrapMaybe *instruction) {
+    fprintf(irp->f, "&??*");
+    ir_print_other_instruction(irp, instruction->value);
+    if (!instruction->safety_check_on) {
+        fprintf(irp->f, " // no safety");
+    }
+}
+
 static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
     ir_print_prefix(irp, instruction);
     switch (instruction->id) {
@@ -592,6 +606,12 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
         case IrInstructionIdSizeOf:
             ir_print_size_of(irp, (IrInstructionSizeOf *)instruction);
             break;
+        case IrInstructionIdTestNull:
+            ir_print_test_null(irp, (IrInstructionTestNull *)instruction);
+            break;
+        case IrInstructionIdUnwrapMaybe:
+            ir_print_unwrap_maybe(irp, (IrInstructionUnwrapMaybe *)instruction);
+            break;
         case IrInstructionIdSwitchBr:
             zig_panic("TODO print more IR instructions");
     }