add labels and goto

This commit is contained in:
Andrew Kelley 2015-12-03 00:47:35 -07:00
parent c89f77dd8e
commit f8ca6c70c7
14 changed files with 270 additions and 27 deletions

View File

@ -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)
```

View File

@ -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

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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) {

View File

@ -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";

View File

@ -27,6 +27,7 @@ enum TokenId {
TokenIdKeywordVoid,
TokenIdKeywordIf,
TokenIdKeywordElse,
TokenIdKeywordGoto,
TokenIdLParen,
TokenIdRParen,
TokenIdComma,

View File

@ -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,

View File

@ -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.

View File

@ -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) {