Commit f8ca6c70c7

Andrew Kelley <superjoe30@gmail.com>
2015-12-03 08:47:35
add labels and goto
1 parent c89f77d
doc/vim/syntax/zig.vim
@@ -1,13 +1,13 @@
 " Vim syntax file
 " Language: Zig
 " Maintainer: Andrew Kelley
-" Latest Revision: 27 November 2015
+" Latest Revision: 02 December 2015
 
 if exists("b:current_syntax")
   finish
 endif
 
-syn keyword zigKeyword fn return mut const extern unreachable export pub as use if else let void
+syn keyword zigKeyword fn return mut const extern unreachable export pub as use if else let void goto
 syn keyword zigType bool i8 u8 i16 u16 i32 u32 i64 u64 isize usize f32 f64 f128
 
 syn region zigCommentLine start="//" end="$" contains=zigTodo,@Spell
example/hello_world/hello.zig
@@ -6,7 +6,18 @@ extern {
     fn exit(code: i32) -> unreachable;
 }
 
+fn loop(a : i32) {
+    if a == 0 {
+        goto done;
+    }
+    puts("loop");
+    loop(a - 1);
+
+done:
+    return;
+}
+
 export fn _start() -> unreachable {
-    puts("Hello, world!");
+    loop(3);
     exit(0);
 }
src/analyze.cpp
@@ -131,6 +131,25 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t
     resolve_type(g, node->data.fn_proto.return_type);
 }
 
+static void preview_function_labels(CodeGen *g, AstNode *node, FnTableEntry *fn_table_entry) {
+    assert(node->type == NodeTypeBlock);
+
+    for (int i = 0; i < node->data.block.statements.length; i += 1) {
+        AstNode *label_node = node->data.block.statements.at(i);
+        if (label_node->type != NodeTypeLabel)
+            continue;
+
+        LabelTableEntry *label_entry = allocate<LabelTableEntry>(1);
+        label_entry->label_node = label_node;
+        Buf *name = &label_node->data.label.name;
+        fn_table_entry->label_table.put(name, label_entry);
+
+        assert(!label_node->codegen_node);
+        label_node->codegen_node = allocate<CodeGenNode>(1);
+        label_node->codegen_node->data.label_entry = label_entry;
+    }
+}
+
 static void preview_function_declarations(CodeGen *g, ImportTableEntry *import, AstNode *node) {
     switch (node->type) {
         case NodeTypeExternBlock:
@@ -158,6 +177,7 @@ static void preview_function_declarations(CodeGen *g, ImportTableEntry *import,
                 fn_table_entry->calling_convention = LLVMCCallConv;
                 fn_table_entry->import_entry = import;
                 fn_table_entry->symbol_table.init(8);
+                fn_table_entry->label_table.init(8);
 
                 resolve_function_proto(g, fn_proto, fn_table_entry);
 
@@ -208,6 +228,7 @@ static void preview_function_declarations(CodeGen *g, ImportTableEntry *import,
                     fn_table_entry->internal_linkage = is_internal;
                     fn_table_entry->calling_convention = is_internal ? LLVMFastCallConv : LLVMCCallConv;
                     fn_table_entry->symbol_table.init(8);
+                    fn_table_entry->label_table.init(8);
 
                     g->fn_protos.append(fn_table_entry);
                     g->fn_defs.append(fn_table_entry);
@@ -222,6 +243,8 @@ static void preview_function_declarations(CodeGen *g, ImportTableEntry *import,
                     assert(!proto_node->codegen_node);
                     proto_node->codegen_node = allocate<CodeGenNode>(1);
                     proto_node->codegen_node->data.fn_proto_node.fn_table_entry = fn_table_entry;
+
+                    preview_function_labels(g, node->data.fn_def.body, fn_table_entry);
                 }
             }
             break;
@@ -290,6 +313,8 @@ static void preview_function_declarations(CodeGen *g, ImportTableEntry *import,
         case NodeTypeCastExpr:
         case NodeTypePrefixOpExpr:
         case NodeTypeIfExpr:
+        case NodeTypeLabel:
+        case NodeTypeGoto:
             zig_unreachable();
     }
 }
@@ -339,6 +364,8 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
                 return_type = g->builtin_types.entry_void;
                 for (int i = 0; i < node->data.block.statements.length; i += 1) {
                     AstNode *child = node->data.block.statements.at(i);
+                    if (child->type == NodeTypeLabel)
+                        continue;
                     if (return_type == g->builtin_types.entry_unreachable) {
                         if (child->type == NodeTypeVoid) {
                             // {unreachable;void;void} is allowed.
@@ -365,7 +392,7 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
 
                 if (actual_return_type == g->builtin_types.entry_unreachable) {
                     // "return exit(0)" should just be "exit(0)".
-                    add_node_error(g, node, buf_sprintf("returning is unreachable."));
+                    add_node_error(g, node, buf_sprintf("returning is unreachable"));
                     actual_return_type = g->builtin_types.entry_invalid;
                 }
 
@@ -373,7 +400,6 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
                 return_type = g->builtin_types.entry_unreachable;
                 break;
             }
-
         case NodeTypeVariableDeclaration:
             {
                 zig_panic("TODO: analyze variable declaration");
@@ -382,6 +408,21 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
                 break;
             }
 
+        case NodeTypeGoto:
+            {
+                FnTableEntry *fn_table_entry = get_context_fn_entry(context);
+                auto table_entry = fn_table_entry->label_table.maybe_get(&node->data.go_to.name);
+                if (table_entry) {
+                    assert(!node->codegen_node);
+                    node->codegen_node = allocate<CodeGenNode>(1);
+                    node->codegen_node->data.label_entry = table_entry->value;
+                } else {
+                    add_node_error(g, node,
+                            buf_sprintf("use of undeclared label '%s'", buf_ptr(&node->data.go_to.name)));
+                }
+                return_type = g->builtin_types.entry_unreachable;
+                break;
+            }
         case NodeTypeBinOpExpr:
             {
                 switch (node->data.bin_op_expr.bin_op) {
@@ -563,8 +604,18 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
                 TypeTableEntry *then_type = analyze_expression(g, import, context, expected_type,
                         node->data.if_expr.then_block);
 
-                check_type_compatibility(g, node, expected_type, else_type);
-                return_type = then_type;
+                TypeTableEntry *primary_type;
+                TypeTableEntry *other_type;
+                if (then_type == g->builtin_types.entry_unreachable) {
+                    primary_type = else_type;
+                    other_type = then_type;
+                } else {
+                    primary_type = then_type;
+                    other_type = else_type;
+                }
+
+                check_type_compatibility(g, node, expected_type, other_type);
+                return_type = primary_type;
                 break;
             }
         case NodeTypeDirective:
@@ -577,14 +628,19 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
         case NodeTypeExternBlock:
         case NodeTypeFnDef:
         case NodeTypeUse:
+        case NodeTypeLabel:
             zig_unreachable();
     }
     assert(return_type);
     check_type_compatibility(g, node, expected_type, return_type);
 
-    assert(!node->codegen_node);
-    node->codegen_node = allocate<CodeGenNode>(1);
-    node->codegen_node->data.expr_node.type_entry = return_type;
+    if (node->codegen_node) {
+        assert(node->type == NodeTypeGoto);
+    } else {
+        assert(node->type != NodeTypeGoto);
+        node->codegen_node = allocate<CodeGenNode>(1);
+    }
+    node->codegen_node->expr_node.type_entry = return_type;
 
     return return_type;
 }
@@ -652,6 +708,8 @@ static void analyze_top_level_declaration(CodeGen *g, ImportTableEntry *import,
         case NodeTypeCastExpr:
         case NodeTypePrefixOpExpr:
         case NodeTypeIfExpr:
+        case NodeTypeLabel:
+        case NodeTypeGoto:
             zig_unreachable();
     }
 }
src/codegen.cpp
@@ -115,7 +115,7 @@ static LLVMValueRef get_variable_value(CodeGen *g, Buf *name) {
 }
 
 static TypeTableEntry *get_expr_type(AstNode *node) {
-    return node->codegen_node->data.expr_node.type_entry;
+    return node->codegen_node->expr_node.type_entry;
 }
 
 static LLVMValueRef gen_fn_call_expr(CodeGen *g, AstNode *node) {
@@ -407,11 +407,13 @@ static LLVMValueRef gen_if_expr(CodeGen *g, AstNode *node) {
 
         LLVMPositionBuilderAtEnd(g->builder, then_block);
         LLVMValueRef then_expr_result = gen_expr(g, node->data.if_expr.then_block);
-        LLVMBuildBr(g->builder, endif_block);
+        if (get_expr_type(node->data.if_expr.then_block) != g->builtin_types.entry_unreachable)
+            LLVMBuildBr(g->builder, endif_block);
 
         LLVMPositionBuilderAtEnd(g->builder, else_block);
         LLVMValueRef else_expr_result = gen_expr(g, node->data.if_expr.else_node);
-        LLVMBuildBr(g->builder, endif_block);
+        if (get_expr_type(node->data.if_expr.else_node) != g->builtin_types.entry_unreachable)
+            LLVMBuildBr(g->builder, endif_block);
 
         LLVMPositionBuilderAtEnd(g->builder, endif_block);
         if (use_expr_value) {
@@ -435,7 +437,8 @@ static LLVMValueRef gen_if_expr(CodeGen *g, AstNode *node) {
 
     LLVMPositionBuilderAtEnd(g->builder, then_block);
     gen_expr(g, node->data.if_expr.then_block);
-    LLVMBuildBr(g->builder, endif_block);
+    if (get_expr_type(node->data.if_expr.then_block) != g->builtin_types.entry_unreachable)
+        LLVMBuildBr(g->builder, endif_block);
 
     LLVMPositionBuilderAtEnd(g->builder, endif_block);
     return nullptr;
@@ -518,6 +521,17 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) {
             }
         case NodeTypeBlock:
             return gen_block(g, node, nullptr);
+        case NodeTypeGoto:
+            add_debug_source_node(g, node);
+            return LLVMBuildBr(g->builder, node->codegen_node->data.label_entry->basic_block);
+        case NodeTypeLabel:
+            {
+                LLVMBasicBlockRef basic_block = node->codegen_node->data.label_entry->basic_block;
+                add_debug_source_node(g, node);
+                LLVMValueRef result = LLVMBuildBr(g->builder, basic_block);
+                LLVMPositionBuilderAtEnd(g->builder, basic_block);
+                return result;
+            }
         case NodeTypeRoot:
         case NodeTypeRootExportDecl:
         case NodeTypeFnProto:
@@ -533,6 +547,20 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) {
     zig_unreachable();
 }
 
+static void build_label_blocks(CodeGen *g, AstNode *block_node) {
+    assert(block_node->type == NodeTypeBlock);
+    for (int i = 0; i < block_node->data.block.statements.length; i += 1) {
+        AstNode *label_node = block_node->data.block.statements.at(i);
+        if (label_node->type != NodeTypeLabel)
+            continue;
+
+        Buf *name = &label_node->data.label.name;
+        label_node->codegen_node->data.label_entry->basic_block = LLVMAppendBasicBlock(
+                g->cur_fn->fn_value, buf_ptr(name));
+    }
+
+}
+
 static LLVMZigDISubroutineType *create_di_function_type(CodeGen *g, AstNodeFnProto *fn_proto,
         LLVMZigDIFile *di_file)
 {
@@ -623,10 +651,13 @@ static void do_code_gen(CodeGen *g) {
         codegen_fn_def->params = allocate<LLVMValueRef>(LLVMCountParams(fn));
         LLVMGetParams(fn, codegen_fn_def->params);
 
+        build_label_blocks(g, fn_def_node->data.fn_def.body);
+
         TypeTableEntry *implicit_return_type = codegen_fn_def->implicit_return_type;
         gen_block(g, fn_def_node->data.fn_def.body, implicit_return_type);
 
         g->block_scopes.pop();
+
     }
     assert(!g->errors.length);
 
src/main.cpp
@@ -9,6 +9,7 @@
 #include "buffer.hpp"
 #include "codegen.hpp"
 #include "os.hpp"
+#include "error.hpp"
 
 #include <stdio.h>
 
@@ -48,6 +49,8 @@ struct Build {
 };
 
 static int build(const char *arg0, Build *b) {
+    int err;
+
     if (!b->in_file)
         return usage(arg0);
 
@@ -59,11 +62,17 @@ static int build(const char *arg0, Build *b) {
     Buf root_source_name = BUF_INIT;
     if (buf_eql_str(&in_file_buf, "-")) {
         os_get_cwd(&root_source_dir);
-        os_fetch_file(stdin, &root_source_code);
+        if ((err = os_fetch_file(stdin, &root_source_code))) {
+            fprintf(stderr, "unable to read stdin: %s\n", err_str(err));
+            return 1;
+        }
         buf_init_from_str(&root_source_name, "");
     } else {
         os_path_split(&in_file_buf, &root_source_dir, &root_source_name);
-        os_fetch_file_path(buf_create_from_str(b->in_file), &root_source_code);
+        if ((err = os_fetch_file_path(buf_create_from_str(b->in_file), &root_source_code))) {
+            fprintf(stderr, "unable to open '%s': %s\n", b->in_file, err_str(err));
+            return 1;
+        }
     }
 
     CodeGen *g = codegen_create(&root_source_dir);
src/parser.cpp
@@ -95,6 +95,10 @@ const char *node_type_str(NodeType node_type) {
             return "Void";
         case NodeTypeIfExpr:
             return "IfExpr";
+        case NodeTypeLabel:
+            return "Label";
+        case NodeTypeGoto:
+            return "Label";
     }
     zig_unreachable();
 }
@@ -260,6 +264,12 @@ void ast_print(AstNode *node, int indent) {
             if (node->data.if_expr.else_node)
                 ast_print(node->data.if_expr.else_node, indent + 2);
             break;
+        case NodeTypeLabel:
+            fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(&node->data.label.name));
+            break;
+        case NodeTypeGoto:
+            fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(&node->data.go_to.name));
+            break;
     }
 }
 
@@ -581,7 +591,7 @@ static AstNode *ast_parse_grouped_expr(ParseContext *pc, int *token_index, bool
 }
 
 /*
-PrimaryExpression : token(Number) | token(String) | token(Unreachable) | GroupedExpression | token(Symbol)
+PrimaryExpression : token(Number) | token(String) | token(Unreachable) | GroupedExpression | token(Symbol) | Goto
 */
 static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool mandatory) {
     Token *token = &pc->tokens->at(*token_index);
@@ -609,6 +619,16 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool
         ast_buf_from_token(pc, token, &node->data.symbol);
         *token_index += 1;
         return node;
+    } else if (token->id == TokenIdKeywordGoto) {
+        AstNode *node = ast_create_node(pc, NodeTypeGoto, token);
+        *token_index += 1;
+
+        Token *dest_symbol = &pc->tokens->at(*token_index);
+        *token_index += 1;
+        ast_expect_token(pc, dest_symbol, TokenIdSymbol);
+
+        ast_buf_from_token(pc, dest_symbol, &node->data.go_to.name);
+        return node;
     }
 
     AstNode *grouped_expr_node = ast_parse_grouped_expr(pc, token_index, false);
@@ -1181,7 +1201,36 @@ static AstNode *ast_parse_expression(ParseContext *pc, int *token_index, bool ma
 }
 
 /*
-Statement : NonBlockExpression token(Semicolon) | BlockExpression
+Label: token(Symbol) token(Colon)
+*/
+static AstNode *ast_parse_label(ParseContext *pc, int *token_index, bool mandatory) {
+    Token *symbol_token = &pc->tokens->at(*token_index);
+    if (symbol_token->id != TokenIdSymbol) {
+        if (mandatory) {
+            ast_invalid_token_error(pc, symbol_token);
+        } else {
+            return nullptr;
+        }
+    }
+
+    Token *colon_token = &pc->tokens->at(*token_index + 1);
+    if (colon_token->id != TokenIdColon) {
+        if (mandatory) {
+            ast_invalid_token_error(pc, colon_token);
+        } else {
+            return nullptr;
+        }
+    }
+
+    *token_index += 2;
+
+    AstNode *node = ast_create_node(pc, NodeTypeLabel, symbol_token);
+    ast_buf_from_token(pc, symbol_token, &node->data.label.name);
+    return node;
+}
+
+/*
+Statement : Label | NonBlockExpression token(Semicolon) | BlockExpression
 Block : token(LBrace) list(option(Statement), token(Semicolon)) token(RBrace)
 */
 static AstNode *ast_parse_block(ParseContext *pc, int *token_index, bool mandatory) {
@@ -1204,12 +1253,18 @@ static AstNode *ast_parse_block(ParseContext *pc, int *token_index, bool mandato
     // {2;} -> {2;void}
     // {;2} -> {void;2}
     for (;;) {
-        AstNode *statement_node = ast_parse_block_expr(pc, token_index, false);
-        bool semicolon_expected = !statement_node;
-        if (!statement_node) {
-            statement_node = ast_parse_non_block_expr(pc, token_index, false);
+        AstNode *statement_node = ast_parse_label(pc, token_index, false);
+        bool semicolon_expected;
+        if (statement_node) {
+            semicolon_expected = false;
+        } else {
+            statement_node = ast_parse_block_expr(pc, token_index, false);
+            semicolon_expected = !statement_node;
             if (!statement_node) {
-                statement_node = ast_create_node(pc, NodeTypeVoid, last_token);
+                statement_node = ast_parse_non_block_expr(pc, token_index, false);
+                if (!statement_node) {
+                    statement_node = ast_create_node(pc, NodeTypeVoid, last_token);
+                }
             }
         }
         node->data.block.statements.append(statement_node);
src/parser.hpp
@@ -41,6 +41,8 @@ enum NodeType {
     NodeTypeUse,
     NodeTypeVoid,
     NodeTypeIfExpr,
+    NodeTypeLabel,
+    NodeTypeGoto,
 };
 
 struct AstNodeRoot {
@@ -182,6 +184,14 @@ struct AstNodeIfExpr {
     AstNode *else_node; // null, block node, or other if expr node
 };
 
+struct AstNodeLabel {
+    Buf name;
+};
+
+struct AstNodeGoto {
+    Buf name;
+};
+
 struct AstNode {
     enum NodeType type;
     int line;
@@ -207,6 +217,8 @@ struct AstNode {
         AstNodeFnCallExpr fn_call_expr;
         AstNodeUse use;
         AstNodeIfExpr if_expr;
+        AstNodeLabel label;
+        AstNodeGoto go_to;
         Buf number;
         Buf string;
         Buf symbol;
src/semantic_info.hpp
@@ -43,6 +43,11 @@ struct SymbolTableEntry {
     int param_index; // only valid in the case of parameters
 };
 
+struct LabelTableEntry {
+    AstNode *label_node;
+    LLVMBasicBlockRef basic_block;
+};
+
 struct FnTableEntry {
     LLVMValueRef fn_value;
     AstNode *proto_node;
@@ -54,6 +59,7 @@ struct FnTableEntry {
 
     // reminder: hash tables must be initialized before use
     HashMap<Buf *, SymbolTableEntry *, buf_hash, buf_eql_buf> symbol_table;
+    HashMap<Buf *, LabelTableEntry *, buf_hash, buf_eql_buf> label_table;
 };
 
 struct CodeGen {
@@ -100,6 +106,7 @@ struct CodeGen {
 
     OutType out_type;
     FnTableEntry *cur_fn;
+    LLVMBasicBlockRef cur_basic_block;
     bool c_stdint_used;
     AstNode *root_export_decl;
     int version_major;
@@ -132,9 +139,10 @@ struct CodeGenNode {
     union {
         TypeNode type_node; // for NodeTypeType
         FnDefNode fn_def_node; // for NodeTypeFnDef
-        ExprNode expr_node; // for all the expression nodes
         FnProtoNode fn_proto_node; // for NodeTypeFnProto
+        LabelTableEntry *label_entry; // for NodeTypeGoto and NodeTypeLabel
     } data;
+    ExprNode expr_node; // for all the expression nodes
 };
 
 static inline Buf *hack_get_fn_call_name(CodeGen *g, AstNode *node) {
src/tokenizer.cpp
@@ -189,6 +189,8 @@ static void end_token(Tokenize *t) {
         t->cur_tok->id = TokenIdKeywordIf;
     } else if (mem_eql_str(token_mem, token_len, "else")) {
         t->cur_tok->id = TokenIdKeywordElse;
+    } else if (mem_eql_str(token_mem, token_len, "goto")) {
+        t->cur_tok->id = TokenIdKeywordGoto;
     }
 
     t->cur_tok = nullptr;
@@ -586,6 +588,7 @@ static const char * token_name(Token *token) {
         case TokenIdKeywordVoid: return "Void";
         case TokenIdKeywordIf: return "If";
         case TokenIdKeywordElse: return "Else";
+        case TokenIdKeywordGoto: return "Goto";
         case TokenIdLParen: return "LParen";
         case TokenIdRParen: return "RParen";
         case TokenIdComma: return "Comma";
src/tokenizer.hpp
@@ -27,6 +27,7 @@ enum TokenId {
     TokenIdKeywordVoid,
     TokenIdKeywordIf,
     TokenIdKeywordElse,
+    TokenIdKeywordGoto,
     TokenIdLParen,
     TokenIdRParen,
     TokenIdComma,
src/zig_llvm.cpp
@@ -255,6 +255,19 @@ void LLVMZigDIBuilderFinalize(LLVMZigDIBuilder *dibuilder) {
     reinterpret_cast<DIBuilder*>(dibuilder)->finalize();
 }
 
+LLVMZigInsertionPoint *LLVMZigSaveInsertPoint(LLVMBuilderRef builder_wrapped) {
+    IRBuilderBase::InsertPoint *ip = new IRBuilderBase::InsertPoint();
+    *ip = unwrap(builder_wrapped)->saveIP();
+    return reinterpret_cast<LLVMZigInsertionPoint*>(ip);
+}
+
+void LLVMZigRestoreInsertPoint(LLVMBuilderRef builder, LLVMZigInsertionPoint *ip_wrapped) {
+    IRBuilderBase::InsertPoint *ip = reinterpret_cast<IRBuilderBase::InsertPoint*>(ip_wrapped);
+    unwrap(builder)->restoreIP(*ip);
+}
+
+//------------------------------------
+
 enum FloatAbi {
     FloatAbiHard,
     FloatAbiSoft,
src/zig_llvm.hpp
@@ -22,6 +22,7 @@ struct LLVMZigDIFile;
 struct LLVMZigDILexicalBlock;
 struct LLVMZigDISubprogram;
 struct LLVMZigDISubroutineType;
+struct LLVMZigInsertionPoint;
 
 void LLVMZigInitializeLoopStrengthReducePass(LLVMPassRegistryRef R);
 void LLVMZigInitializeLowerIntrinsicsPass(LLVMPassRegistryRef R);
@@ -75,6 +76,9 @@ LLVMZigDISubprogram *LLVMZigCreateFunction(LLVMZigDIBuilder *dibuilder, LLVMZigD
 
 void LLVMZigDIBuilderFinalize(LLVMZigDIBuilder *dibuilder);
 
+LLVMZigInsertionPoint *LLVMZigSaveInsertPoint(LLVMBuilderRef builder);
+void LLVMZigRestoreInsertPoint(LLVMBuilderRef builder, LLVMZigInsertionPoint *point);
+
 
 /*
  * This stuff is not LLVM API but it depends on the LLVM C++ API so we put it here.
test/run_tests.cpp
@@ -232,6 +232,30 @@ static void add_compiling_test_cases(void) {
             exit(0);
         }
     )SOURCE", "pass\n");
+
+    add_simple_case("goto", R"SOURCE(
+        #link("c")
+        extern {
+            fn puts(s: *const u8) -> i32;
+            fn exit(code: i32) -> unreachable;
+        }
+
+        fn loop(a : i32) {
+            if a == 0 {
+                goto done;
+            }
+            puts("loop");
+            loop(a - 1);
+
+        done:
+            return;
+        }
+
+        export fn _start() -> unreachable {
+            loop(3);
+            exit(0);
+        }
+    )SOURCE", "loop\nloop\nloop\n");
 }
 
 static void add_compile_failure_test_cases(void) {
@@ -305,6 +329,17 @@ fn a() {
     )SOURCE", 2,
             ".tmp_source.zig:3:5: error: use of undeclared identifier 'b'",
             ".tmp_source.zig:4:5: error: use of undeclared identifier 'c'");
+
+    add_compile_fail_case("goto cause unreachable code", R"SOURCE(
+fn a() {
+    goto done;
+    b();
+done:
+    return;
+}
+fn b() {}
+    )SOURCE", 1, ".tmp_source.zig:4:5: error: unreachable code");
+
 }
 
 static void print_compiler_invokation(TestCase *test_case, Buf *zig_stderr) {
README.md
@@ -45,7 +45,6 @@ make
  * variable declarations and assignment expressions
  * Type checking
  * loops
- * labels and goto
  * inline assembly and syscalls
  * conditional compilation and ability to check target platform and architecture
  * main function with command line arguments
@@ -110,7 +109,9 @@ PointerType : token(Star) token(Const) Type | token(Star) token(Mut) Type
 
 Block : token(LBrace) list(option(Statement), token(Semicolon)) token(RBrace)
 
-Statement : NonBlockExpression token(Semicolon) | BlockExpression
+Statement : Label | NonBlockExpression token(Semicolon) | BlockExpression
+
+Label: token(Symbol) token(Colon)
 
 Expression : BlockExpression | NonBlockExpression
 
@@ -162,7 +163,9 @@ FnCallExpression : PrimaryExpression token(LParen) list(Expression, token(Comma)
 
 PrefixOp : token(Not) | token(Dash) | token(Tilde)
 
-PrimaryExpression : token(Number) | token(String) | token(Unreachable) | GroupedExpression | token(Symbol)
+PrimaryExpression : token(Number) | token(String) | token(Unreachable) | GroupedExpression | token(Symbol) | Goto
+
+Goto: token(Goto) token(Symbol)
 
 GroupedExpression : token(LParen) Expression token(RParen)
 ```