Commit 21eca6478f
Changed files (5)
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;
+
+
+