diff --git a/src/all_types.hpp b/src/all_types.hpp index 3b6484869f..cde70b216e 100644 --- a/src/all_types.hpp +++ b/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 all_block_contexts; + ZigList all_labels; Buf symbol_name; TypeTableEntry *type_entry; // function type bool is_inline; @@ -1020,6 +1024,7 @@ struct FnTableEntry { ZigList cast_alloca_list; ZigList struct_val_expr_alloca_list; ZigList variable_list; + ZigList 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 decl_table; HashMap var_table; + HashMap label_table; // if the block is inside a function, this is the function it is in: FnTableEntry *fn_entry; diff --git a/src/analyze.cpp b/src/analyze.cpp index d908c7fcbe..a5ba68da3d 100644 --- a/src/analyze.cpp +++ b/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(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, diff --git a/src/codegen.cpp b/src/codegen.cpp index 588e831af6..dbab8ddd9b 100644 --- a/src/codegen.cpp +++ b/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 diff --git a/test/run_tests.cpp b/test/run_tests.cpp index 0ff7385d94..431452a23c 100644 --- a/test/run_tests.cpp +++ b/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"); } ////////////////////////////////////////////////////////////////////////////// diff --git a/test/self_hosted.zig b/test/self_hosted.zig index 34ca53e162..76f9e0453f 100644 --- a/test/self_hosted.zig +++ b/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; + + +