parent
fdf6a18461
commit
21eca6478f
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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;
|
||||
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user