re-introduce goto

see #44
This commit is contained in:
Andrew Kelley 2016-04-09 16:41:17 -07:00
parent fdf6a18461
commit 21eca6478f
5 changed files with 159 additions and 9 deletions

View File

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

View File

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

View File

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

View File

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

View File

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