Commit 647d13168a

Andrew Kelley <superjoe30@gmail.com>
2016-12-14 01:36:56
IR: implement maybe return expression
1 parent 3f3630d
src/ast_render.cpp
@@ -759,7 +759,7 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
                     fprintf(ar->f, ": ");
                     render_node_ungrouped(ar, var_decl->type);
                 }
-                fprintf(ar->f, " = ");
+                fprintf(ar->f, " ?= ");
                 render_node_grouped(ar, var_decl->expr);
                 fprintf(ar->f, ") ");
                 render_node_grouped(ar, node->data.if_var_expr.then_block);
src/ir.cpp
@@ -1731,7 +1731,13 @@ static void ir_gen_defers_for_block(IrBuilder *irb, Scope *inner_scope, Scope *o
     }
 }
 
-static IrInstruction *ir_gen_return(IrBuilder *irb, Scope *scope, AstNode *node) {
+static void ir_set_cursor_at_end(IrBuilder *irb, IrBasicBlock *basic_block) {
+    assert(basic_block);
+
+    irb->current_basic_block = basic_block;
+}
+
+static IrInstruction *ir_gen_return(IrBuilder *irb, Scope *scope, AstNode *node, LValPurpose lval) {
     assert(node->type == NodeTypeReturnExpr);
 
     FnTableEntry *fn_entry = exec_fn_entry(irb->exec);
@@ -1740,6 +1746,9 @@ static IrInstruction *ir_gen_return(IrBuilder *irb, Scope *scope, AstNode *node)
         return irb->codegen->invalid_instruction;
     }
 
+    Scope *outer_scope = fn_entry->child_scope;
+    bool is_inline = ir_should_inline(irb);
+
     AstNode *expr_node = node->data.return_expr.expr;
     switch (node->data.return_expr.kind) {
         case ReturnKindUnconditional:
@@ -1747,28 +1756,46 @@ static IrInstruction *ir_gen_return(IrBuilder *irb, Scope *scope, AstNode *node)
                 IrInstruction *return_value;
                 if (expr_node) {
                     return_value = ir_gen_node(irb, expr_node, scope);
+                    if (return_value == irb->codegen->invalid_instruction)
+                        return irb->codegen->invalid_instruction;
                 } else {
                     return_value = ir_build_const_void(irb, scope, node);
                 }
 
-                Scope *outer_scope = fn_entry->child_scope;
+                // TODO conditionally gen maybe defers and error defers
                 ir_gen_defers_for_block(irb, scope, outer_scope, false, false);
                 return ir_build_return(irb, scope, node, return_value);
             }
         case ReturnKindError:
             zig_panic("TODO gen IR for %%return");
         case ReturnKindMaybe:
-            zig_panic("TODO gen IR for ?return");
+            {
+                assert(expr_node);
+                IrInstruction *maybe_val_ptr = ir_gen_node_extra(irb, expr_node, scope, LValPurposeAddressOf);
+                if (maybe_val_ptr == irb->codegen->invalid_instruction)
+                    return irb->codegen->invalid_instruction;
+                IrInstruction *is_nonnull_val = ir_build_test_null(irb, scope, node, maybe_val_ptr);
+
+                IrBasicBlock *return_block = ir_build_basic_block(irb, scope, "MaybeRetReturn");
+                IrBasicBlock *continue_block = ir_build_basic_block(irb, scope, "MaybeRetContinue");
+                ir_build_cond_br(irb, scope, node, is_nonnull_val, continue_block, return_block, is_inline);
+
+                ir_set_cursor_at_end(irb, return_block);
+                ir_gen_defers_for_block(irb, scope, outer_scope, false, true);
+                IrInstruction *null = ir_build_const_null(irb, scope, node);
+                ir_build_return(irb, scope, node, null);
+
+                ir_set_cursor_at_end(irb, continue_block);
+                IrInstruction *unwrapped_ptr = ir_build_unwrap_maybe(irb, scope, node, maybe_val_ptr, false);
+                if (lval != LValPurposeNone)
+                    return unwrapped_ptr;
+                else
+                    return ir_build_load_ptr(irb, scope, node, unwrapped_ptr);
+            }
     }
     zig_unreachable();
 }
 
-static void ir_set_cursor_at_end(IrBuilder *irb, IrBasicBlock *basic_block) {
-    assert(basic_block);
-
-    irb->current_basic_block = basic_block;
-}
-
 static VariableTableEntry *create_local_var(CodeGen *codegen, AstNode *node, Scope *parent_scope,
         Buf *name, bool src_is_const, bool gen_is_const, bool is_shadowable, bool is_inline)
 {
@@ -3606,7 +3633,7 @@ static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scop
         case NodeTypeArrayAccessExpr:
             return ir_gen_array_access(irb, scope, node, lval);
         case NodeTypeReturnExpr:
-            return ir_lval_wrap(irb, scope, ir_gen_return(irb, scope, node), lval);
+            return ir_gen_return(irb, scope, node, lval);
         case NodeTypeFieldAccessExpr:
             return ir_gen_field_access(irb, scope, node, lval);
         case NodeTypeThisLiteral:
@@ -3827,6 +3854,7 @@ static TypeTableEntry *ir_determine_peer_types(IrAnalyze *ira, AstNode *source_n
         return ira->codegen->builtin_types.entry_invalid;
     }
     bool any_are_pure_error = (prev_inst->type_entry->id == TypeTableEntryIdPureError);
+    bool any_are_null = (prev_inst->type_entry->id == TypeTableEntryIdNullLit);
     for (size_t i = 1; i < instruction_count; i += 1) {
         IrInstruction *cur_inst = instructions[i];
         TypeTableEntry *cur_type = cur_inst->type_entry;
@@ -3836,9 +3864,15 @@ static TypeTableEntry *ir_determine_peer_types(IrAnalyze *ira, AstNode *source_n
         } else if (prev_type->id == TypeTableEntryIdPureError) {
             prev_inst = cur_inst;
             continue;
+        } else if (prev_type->id == TypeTableEntryIdNullLit) {
+            prev_inst = cur_inst;
+            continue;
         } else if (cur_type->id == TypeTableEntryIdPureError) {
             any_are_pure_error = true;
             continue;
+        } else if (cur_type->id == TypeTableEntryIdNullLit) {
+            any_are_null = true;
+            continue;
         } else if (types_match_const_cast_only(prev_type, cur_type)) {
             continue;
         } else if (types_match_const_cast_only(cur_type, prev_type)) {
@@ -3889,9 +3923,13 @@ static TypeTableEntry *ir_determine_peer_types(IrAnalyze *ira, AstNode *source_n
                 return ira->codegen->builtin_types.entry_invalid;
             }
         } else {
-            add_node_error(ira->codegen, source_node,
+            ErrorMsg *msg = add_node_error(ira->codegen, source_node,
                 buf_sprintf("incompatible types: '%s' and '%s'",
                     buf_ptr(&prev_type->name), buf_ptr(&cur_type->name)));
+            add_error_note(ira->codegen, msg, prev_inst->source_node,
+                buf_sprintf("type '%s' here", buf_ptr(&prev_type->name)));
+            add_error_note(ira->codegen, msg, cur_inst->source_node,
+                buf_sprintf("type '%s' here", buf_ptr(&cur_type->name)));
 
             return ira->codegen->builtin_types.entry_invalid;
         }
@@ -3903,9 +3941,23 @@ static TypeTableEntry *ir_determine_peer_types(IrAnalyze *ira, AstNode *source_n
             add_node_error(ira->codegen, source_node,
                 buf_sprintf("unable to make error union out of number literal"));
             return ira->codegen->builtin_types.entry_invalid;
+        } else if (prev_inst->type_entry->id == TypeTableEntryIdNullLit) {
+            add_node_error(ira->codegen, source_node,
+                buf_sprintf("unable to make error union out of null literal"));
+            return ira->codegen->builtin_types.entry_invalid;
         } else {
             return get_error_type(ira->codegen, prev_inst->type_entry);
         }
+    } else if (any_are_null && prev_inst->type_entry->id != TypeTableEntryIdNullLit) {
+        if (prev_inst->type_entry->id == TypeTableEntryIdNumLitInt ||
+            prev_inst->type_entry->id == TypeTableEntryIdNumLitFloat)
+        {
+            add_node_error(ira->codegen, source_node,
+                buf_sprintf("unable to make maybe out of number literal"));
+            return ira->codegen->builtin_types.entry_invalid;
+        } else {
+            return get_maybe_type(ira->codegen, prev_inst->type_entry);
+        }
     } else {
         return prev_inst->type_entry;
     }
@@ -9053,16 +9105,9 @@ bool ir_has_side_effects(IrInstruction *instruction) {
 //static TypeTableEntry *analyze_return_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
 //        TypeTableEntry *expected_type, AstNode *node)
 //{
-//    if (!node->data.return_expr.expr) {
-//        node->data.return_expr.expr = create_ast_void_node(g, import, node);
-//        normalize_parent_ptrs(node);
-//    }
-//
 //    TypeTableEntry *expected_return_type = get_return_type(context);
 //
 //    switch (node->data.return_expr.kind) {
-//        case ReturnKindUnconditional:
-//            zig_panic("TODO moved to ir.cpp");
 //        case ReturnKindError:
 //            {
 //                TypeTableEntry *expected_err_type;
@@ -9093,34 +9138,6 @@ bool ir_has_side_effects(IrInstruction *instruction) {
 //                    return g->builtin_types.entry_invalid;
 //                }
 //            }
-//        case ReturnKindMaybe:
-//            {
-//                TypeTableEntry *expected_maybe_type;
-//                if (expected_type) {
-//                    expected_maybe_type = get_maybe_type(g, expected_type);
-//                } else {
-//                    expected_maybe_type = nullptr;
-//                }
-//                TypeTableEntry *resolved_type = analyze_expression(g, import, context, expected_maybe_type,
-//                        node->data.return_expr.expr);
-//                if (resolved_type->id == TypeTableEntryIdInvalid) {
-//                    return resolved_type;
-//                } else if (resolved_type->id == TypeTableEntryIdMaybe) {
-//                    if (expected_return_type->id != TypeTableEntryIdMaybe) {
-//                        ErrorMsg *msg = add_node_error(g, node,
-//                            buf_sprintf("?return statement in function with return type '%s'",
-//                                buf_ptr(&expected_return_type->name)));
-//                        AstNode *return_type_node = context->fn_entry->fn_def_node->data.fn_def.fn_proto->data.fn_proto.return_type;
-//                        add_error_note(g, msg, return_type_node, buf_sprintf("function return type here"));
-//                    }
-//
-//                    return resolved_type->data.maybe.child_type;
-//                } else {
-//                    add_node_error(g, node->data.return_expr.expr,
-//                        buf_sprintf("expected maybe type, found '%s'", buf_ptr(&resolved_type->name)));
-//                    return g->builtin_types.entry_invalid;
-//                }
-//            }
 //    }
 //    zig_unreachable();
 //}
@@ -9344,43 +9361,6 @@ bool ir_has_side_effects(IrInstruction *instruction) {
 //                    return nullptr;
 //                }
 //            }
-//        case ReturnKindMaybe:
-//            {
-//                assert(value_type->id == TypeTableEntryIdMaybe);
-//                TypeTableEntry *child_type = value_type->data.maybe.child_type;
-//
-//                LLVMBasicBlockRef return_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "MaybeRetReturn");
-//                LLVMBasicBlockRef continue_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "MaybeRetContinue");
-//
-//                LLVMValueRef maybe_val_ptr = LLVMBuildStructGEP(g->builder, value, 1, "");
-//                LLVMValueRef is_non_null = LLVMBuildLoad(g->builder, maybe_val_ptr, "");
-//
-//                LLVMValueRef zero = LLVMConstNull(LLVMInt1Type());
-//                LLVMValueRef cond_val = LLVMBuildICmp(g->builder, LLVMIntNE, is_non_null, zero, "");
-//                LLVMBuildCondBr(g->builder, cond_val, continue_block, return_block);
-//
-//                LLVMPositionBuilderAtEnd(g->builder, return_block);
-//                TypeTableEntry *return_type = g->cur_fn->type_entry->data.fn.fn_type_id.return_type;
-//                assert(return_type->id == TypeTableEntryIdMaybe);
-//                if (handle_is_ptr(return_type)) {
-//                    assert(g->cur_ret_ptr);
-//
-//                    LLVMValueRef maybe_bit_ptr = LLVMBuildStructGEP(g->builder, g->cur_ret_ptr, 1, "");
-//                    LLVMBuildStore(g->builder, zero, maybe_bit_ptr);
-//                    LLVMBuildRetVoid(g->builder);
-//                } else {
-//                    LLVMValueRef ret_zero_value = LLVMConstNull(return_type->type_ref);
-//                    gen_return(g, node, ret_zero_value, ReturnKnowledgeKnownNull);
-//                }
-//
-//                LLVMPositionBuilderAtEnd(g->builder, continue_block);
-//                if (type_has_bits(child_type)) {
-//                    LLVMValueRef val_ptr = LLVMBuildStructGEP(g->builder, value, 0, "");
-//                    return get_handle_value(g, val_ptr, child_type);
-//                } else {
-//                    return nullptr;
-//                }
-//            }
 //    }
 //    zig_unreachable();
 //}
src/ir_print.cpp
@@ -585,7 +585,7 @@ static void ir_print_size_of(IrPrint *irp, IrInstructionSizeOf *instruction) {
 static void ir_print_test_null(IrPrint *irp, IrInstructionTestNull *instruction) {
     fprintf(irp->f, "*");
     ir_print_other_instruction(irp, instruction->value);
-    fprintf(irp->f, " == null");
+    fprintf(irp->f, " != null");
 }
 
 static void ir_print_unwrap_maybe(IrPrint *irp, IrInstructionUnwrapMaybe *instruction) {
test/self_hosted2.zig
@@ -343,6 +343,18 @@ fn shlWithOverflow() {
     assert(result == 0b1011111111111100);
 }
 
+fn assignToIfVarPtr() {
+
+    var maybe_bool: ?bool = true;
+
+    if (const *b ?= maybe_bool) {
+        *b = false;
+    }
+
+    assert(??maybe_bool == false);
+}
+
+
 fn assert(ok: bool) {
     if (!ok)
         @unreachable();
@@ -377,6 +389,7 @@ fn runAllTests() {
     intTypeBuiltin();
     overflowIntrinsics();
     shlWithOverflow();
+    assignToIfVarPtr();
 }
 
 export nakedcc fn _start() -> unreachable {