Commit 21eca6478f

Andrew Kelley <superjoe30@gmail.com>
2016-04-10 01:41:17
re-introduce goto
see #44
1 parent fdf6a18
src/all_types.hpp
@@ -24,6 +24,7 @@ struct BlockContext;
 struct TypeTableEntry;
 struct VariableTableEntry;
 struct ErrorTableEntry;
+struct LabelTableEntry;
 struct BuiltinFnEntry;
 struct TypeStructField;
 struct CodeGen;
@@ -538,6 +539,7 @@ struct AstNodeLabel {
 
     // populated by semantic analyzer
     Expr resolved_expr;
+    LabelTableEntry *label_entry;
 };
 
 struct AstNodeGoto {
@@ -545,6 +547,7 @@ struct AstNodeGoto {
 
     // populated by semantic analyzer
     Expr resolved_expr;
+    LabelTableEntry *label_entry;
 };
 
 struct AsmOutput {
@@ -1009,6 +1012,7 @@ struct FnTableEntry {
     ImportTableEntry *import_entry;
     // Required to be a pre-order traversal of the AST. (parents must come before children)
     ZigList<BlockContext *> all_block_contexts;
+    ZigList<LabelTableEntry *> all_labels;
     Buf symbol_name;
     TypeTableEntry *type_entry; // function type
     bool is_inline;
@@ -1020,6 +1024,7 @@ struct FnTableEntry {
     ZigList<AstNode *> cast_alloca_list;
     ZigList<StructValExprCodeGen *> struct_val_expr_alloca_list;
     ZigList<VariableTableEntry *> variable_list;
+    ZigList<AstNode *> goto_list;
 };
 
 enum BuiltinFnId {
@@ -1217,6 +1222,13 @@ struct ErrorTableEntry {
     AstNode *decl_node;
 };
 
+struct LabelTableEntry {
+    AstNode *decl_node;
+    LLVMBasicBlockRef basic_block;
+    bool used;
+    bool entered_from_fallthrough;
+};
+
 struct BlockContext {
     // One of: NodeTypeFnDef, NodeTypeBlock, NodeTypeRoot, NodeTypeDefer, NodeTypeVariableDeclaration
     AstNode *node;
@@ -1224,6 +1236,7 @@ struct BlockContext {
     // any variables that are introduced by this scope
     HashMap<Buf *, AstNode *, buf_hash, buf_eql_buf> decl_table;
     HashMap<Buf *, VariableTableEntry *, buf_hash, buf_eql_buf> var_table;
+    HashMap<Buf *, LabelTableEntry *, buf_hash, buf_eql_buf> label_table; 
 
     // if the block is inside a function, this is the function it is in:
     FnTableEntry *fn_entry;
src/analyze.cpp
@@ -1992,6 +1992,7 @@ BlockContext *new_block_context(AstNode *node, BlockContext *parent) {
     context->parent = parent;
     context->decl_table.init(1);
     context->var_table.init(1);
+    context->label_table.init(1);
 
     if (parent) {
         context->parent_loop_node = parent->parent_loop_node;
@@ -2037,6 +2038,18 @@ static VariableTableEntry *find_variable(CodeGen *g, BlockContext *orig_context,
     return nullptr;
 }
 
+static LabelTableEntry *find_label(CodeGen *g, BlockContext *orig_context, Buf *name) {
+    BlockContext *context = orig_context;
+    while (context && context->fn_entry) {
+        auto entry = context->label_table.maybe_get(name);
+        if (entry) {
+            return entry->value;
+        }
+        context = context->parent;
+    }
+    return nullptr;
+}
+
 static TypeEnumField *get_enum_field(TypeTableEntry *enum_type, Buf *name) {
     for (uint32_t i = 0; i < enum_type->data.enumeration.field_count; i += 1) {
         TypeEnumField *type_enum_field = &enum_type->data.enumeration.fields[i];
@@ -5326,8 +5339,19 @@ static TypeTableEntry *analyze_block_expr(CodeGen *g, ImportTableEntry *import,
     for (int i = 0; i < node->data.block.statements.length; i += 1) {
         AstNode *child = node->data.block.statements.at(i);
         if (child->type == NodeTypeLabel) {
-            add_node_error(g, child,
-                buf_sprintf("label and goto not supported yet, see https://github.com/andrewrk/zig/issues/44"));
+            FnTableEntry *fn_table_entry = child_context->fn_entry;
+            assert(fn_table_entry);
+
+            LabelTableEntry *label = allocate<LabelTableEntry>(1);
+            label->decl_node = child;
+            label->entered_from_fallthrough = (return_type->id != TypeTableEntryIdUnreachable);
+
+            child->block_context = child_context;
+            child->data.label.label_entry = label;
+            fn_table_entry->all_labels.append(label);
+
+            child_context->label_table.put(&child->data.label.name, label);
+
             return_type = g->builtin_types.entry_void;
             continue;
         }
@@ -5405,13 +5429,35 @@ static TypeTableEntry *analyze_asm_expr(CodeGen *g, ImportTableEntry *import, Bl
     return return_type;
 }
 
-static TypeTableEntry *analyze_goto(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+static TypeTableEntry *analyze_goto_pass1(CodeGen *g, ImportTableEntry *import, BlockContext *context,
         TypeTableEntry *expected_type, AstNode *node)
 {
-    add_node_error(g, node, buf_sprintf("goto is broken, see https://github.com/andrewrk/zig/issues/44"));
+    assert(node->type == NodeTypeGoto);
+
+    FnTableEntry *fn_table_entry = context->fn_entry;
+    assert(fn_table_entry);
+
+    fn_table_entry->goto_list.append(node);
+
     return g->builtin_types.entry_unreachable;
 }
 
+static void analyze_goto_pass2(CodeGen *g, ImportTableEntry *import, AstNode *node) {
+    assert(node->type == NodeTypeGoto);
+    Buf *label_name = &node->data.goto_expr.name;
+    BlockContext *context = node->block_context;
+    assert(context);
+    LabelTableEntry *label = find_label(g, context, label_name);
+
+    if (!label) {
+        add_node_error(g, node, buf_sprintf("no label in scope named '%s'", buf_ptr(label_name)));
+        return;
+    }
+
+    label->used = true;
+    node->data.goto_expr.label_entry = label;
+}
+
 static TypeTableEntry *analyze_expression_pointer_only(CodeGen *g, ImportTableEntry *import,
         BlockContext *context, TypeTableEntry *expected_type, AstNode *node, bool pointer_only)
 {
@@ -5433,7 +5479,7 @@ static TypeTableEntry *analyze_expression_pointer_only(CodeGen *g, ImportTableEn
             return_type = g->builtin_types.entry_void;
             break;
         case NodeTypeGoto:
-            analyze_goto(g, import, context, expected_type, node);
+            return_type = analyze_goto_pass1(g, import, context, expected_type, node);
             break;
         case NodeTypeBreak:
             return_type = analyze_break_expr(g, import, context, expected_type, node);
@@ -5611,6 +5657,21 @@ static void analyze_fn_body(CodeGen *g, FnTableEntry *fn_table_entry) {
     TypeTableEntry *block_return_type = analyze_expression(g, import, context, expected_type, node->data.fn_def.body);
 
     node->data.fn_def.implicit_return_type = block_return_type;
+
+    for (int i = 0; i < fn_table_entry->goto_list.length; i += 1) {
+        AstNode *goto_node = fn_table_entry->goto_list.at(i);
+        assert(goto_node->type == NodeTypeGoto);
+        analyze_goto_pass2(g, import, goto_node);
+    }
+
+    for (int i = 0; i < fn_table_entry->all_labels.length; i += 1) {
+        LabelTableEntry *label = fn_table_entry->all_labels.at(i);
+        if (!label->used) {
+            add_node_error(g, label->decl_node,
+                    buf_sprintf("label '%s' defined but not used",
+                        buf_ptr(&label->decl_node->data.label.name)));
+        }
+    }
 }
 
 static void add_top_level_decl(CodeGen *g, ImportTableEntry *import, BlockContext *block_context,
src/codegen.cpp
@@ -2719,6 +2719,29 @@ static LLVMValueRef gen_switch_expr(CodeGen *g, AstNode *node) {
     }
 }
 
+static LLVMValueRef gen_goto(CodeGen *g, AstNode *node) {
+    assert(node->type == NodeTypeGoto);
+
+    add_debug_source_node(g, node);
+    LLVMBuildBr(g->builder, node->data.goto_expr.label_entry->basic_block);
+    return nullptr;
+}
+
+static LLVMValueRef gen_label(CodeGen *g, AstNode *node) {
+    assert(node->type == NodeTypeLabel);
+
+    LabelTableEntry *label = node->data.label.label_entry;
+    assert(label);
+
+    LLVMBasicBlockRef basic_block = label->basic_block;
+    if (label->entered_from_fallthrough) {
+        add_debug_source_node(g, node);
+        LLVMBuildBr(g->builder, basic_block);
+    }
+    LLVMPositionBuilderAtEnd(g->builder, basic_block);
+    return nullptr;
+}
+
 static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) {
     Expr *expr = get_resolved_expr(node);
     if (expr->const_val.ok) {
@@ -2766,13 +2789,13 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) {
         case NodeTypeBlock:
             return gen_block(g, node, nullptr);
         case NodeTypeGoto:
-            zig_unreachable();
+            return gen_goto(g, node);
         case NodeTypeBreak:
             return gen_break(g, node);
         case NodeTypeContinue:
             return gen_continue(g, node);
         case NodeTypeLabel:
-            zig_unreachable();
+            return gen_label(g, node);
         case NodeTypeContainerInitExpr:
             return gen_container_init_expr(g, node);
         case NodeTypeSwitchExpr:
@@ -3105,6 +3128,16 @@ static void generate_error_name_table(CodeGen *g) {
     LLVMSetUnnamedAddr(g->err_name_table, true);
 }
 
+static void build_label_blocks(CodeGen *g, FnTableEntry *fn) {
+    LLVMBasicBlockRef entry_block = LLVMAppendBasicBlock(fn->fn_value, "entry");
+    for (int i = 0; i < fn->all_labels.length; i += 1) {
+        LabelTableEntry *label = fn->all_labels.at(i);
+        Buf *name = &label->decl_node->data.label.name;
+        label->basic_block = LLVMAppendBasicBlock(fn->fn_value, buf_ptr(name));
+    }
+    LLVMPositionBuilderAtEnd(g->builder, entry_block);
+}
+
 static void do_code_gen(CodeGen *g) {
     assert(!g->errors.length);
 
@@ -3279,8 +3312,7 @@ static void do_code_gen(CodeGen *g) {
         assert(proto_node->type == NodeTypeFnProto);
         AstNodeFnProto *fn_proto = &proto_node->data.fn_proto;
 
-        LLVMBasicBlockRef entry_block = LLVMAppendBasicBlock(fn, "entry");
-        LLVMPositionBuilderAtEnd(g->builder, entry_block);
+        build_label_blocks(g, fn_table_entry);
 
 
         // Set up debug info for blocks
test/run_tests.cpp
@@ -1788,6 +1788,28 @@ fn test1(a: i32, b: i32) -> i32 {
     return foo(a)(b);
 }
     )SOURCE", 1, ".tmp_source.zig:4:16: error: unable to resolve constant expression");
+
+    add_compile_fail_case("goto jumping into block", R"SOURCE(
+fn f() {
+    {
+a_label:
+    }
+    goto a_label;
+}
+    )SOURCE", 2,
+            ".tmp_source.zig:4:1: error: label 'a_label' defined but not used",
+            ".tmp_source.zig:6:5: error: no label in scope named 'a_label'");
+
+    add_compile_fail_case("goto jumping past a defer", R"SOURCE(
+fn f(b: bool) {
+    if (b) goto label;
+    defer derp();
+label:
+}
+fn derp(){}
+    )SOURCE", 2,
+            ".tmp_source.zig:3:12: error: no label in scope named 'label'",
+            ".tmp_source.zig:5:1: error: label 'label' defined but not used");
 }
 
 //////////////////////////////////////////////////////////////////////////////
test/self_hosted.zig
@@ -569,3 +569,25 @@ fn error_name_string() {
     assert(str_eql(@err_name(error.AnError), "AnError"));
     assert(str_eql(@err_name(error.ALongerErrorName), "ALongerErrorName"));
 }
+
+
+#attribute("test")
+fn goto_and_labels() {
+    goto_loop();
+    assert(goto_counter == 10);
+}
+fn goto_loop() {
+    var i: i32 = 0;
+    goto cond;
+loop:
+    i += 1;
+cond:
+    if (!(i < 10)) goto end;
+    goto_counter += 1;
+    goto loop;
+end:
+}
+var goto_counter: i32 = 0;
+
+
+