Commit 1fe1235e14

Andrew Kelley <superjoe30@gmail.com>
2016-01-10 07:49:22
order-independent declarations
code constructs and traverses a dependency graph in a deterministic order.
1 parent 6d9119f
src/analyze.cpp
@@ -14,6 +14,9 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
         TypeTableEntry *expected_type, AstNode *node);
 static TypeTableEntry *eval_const_expr(CodeGen *g, BlockContext *context,
         AstNode *node, AstNodeNumberLiteral *out_number_literal);
+static void collect_type_decl_deps(CodeGen *g, ImportTableEntry *import, AstNode *type_node, DeclNode *decl_node);
+static VariableTableEntry *analyze_variable_declaration(CodeGen *g, ImportTableEntry *import,
+        BlockContext *context, TypeTableEntry *expected_type, AstNode *node);
 
 static AstNode *first_executing_node(AstNode *node) {
     switch (node->type) {
@@ -438,6 +441,9 @@ static TypeTableEntry *resolve_type(CodeGen *g, AstNode *node, ImportTableEntry
             {
                 Buf *name = &node->data.type.primitive_name;
                 auto table_entry = import->type_table.maybe_get(name);
+                if (!table_entry) {
+                    table_entry = g->primitive_type_table.maybe_get(name);
+                }
                 if (table_entry) {
                     type_node->entry = table_entry->value;
                 } else {
@@ -716,10 +722,19 @@ static void resolve_struct_type(CodeGen *g, ImportTableEntry *import, TypeTableE
     }
 }
 
-static void preview_fn_def(CodeGen *g, ImportTableEntry *import, AstNode *node, TypeTableEntry *struct_type) {
-    assert(node->type == NodeTypeFnDef);
-    AstNode *proto_node = node->data.fn_def.fn_proto;
-    assert(proto_node->type == NodeTypeFnProto);
+static void preview_fn_proto(CodeGen *g, ImportTableEntry *import,
+        AstNode *proto_node)
+{
+    AstNode *fn_def_node = proto_node->data.fn_proto.fn_def_node;
+    AstNode *extern_node = proto_node->data.fn_proto.extern_node;
+    AstNode *struct_node = proto_node->data.fn_proto.struct_node;
+    TypeTableEntry *struct_type;
+    if (struct_node) {
+        struct_type = struct_node->codegen_node->data.struct_decl_node.type_entry;
+    } else {
+        struct_type = nullptr;
+    }
+
     Buf *proto_name = &proto_node->data.fn_proto.name;
 
     auto fn_table = struct_type ? &struct_type->data.structure.fn_table : &import->fn_table;
@@ -727,69 +742,74 @@ static void preview_fn_def(CodeGen *g, ImportTableEntry *import, AstNode *node,
     auto entry = fn_table->maybe_get(proto_name);
     bool skip = false;
     bool is_internal = (proto_node->data.fn_proto.visib_mod != VisibModExport);
+    bool is_c_compat = !is_internal || extern_node;
     bool is_pub = (proto_node->data.fn_proto.visib_mod != VisibModPrivate);
     if (entry) {
-        add_node_error(g, node,
+        add_node_error(g, proto_node,
                 buf_sprintf("redefinition of '%s'", buf_ptr(proto_name)));
-        alloc_codegen_node(node);
-        node->codegen_node->data.fn_def_node.skip = true;
+        proto_node->codegen_node->data.fn_proto_node.skip = true;
         skip = true;
     } else if (is_pub) {
         auto entry = fn_table->maybe_get(proto_name);
         if (entry) {
-            add_node_error(g, node,
-                    buf_sprintf("redefinition of '%s'", buf_ptr(proto_name)));
-            alloc_codegen_node(node);
-            node->codegen_node->data.fn_def_node.skip = true;
+            add_node_error(g, proto_node, buf_sprintf("redefinition of '%s'", buf_ptr(proto_name)));
+            proto_node->codegen_node->data.fn_proto_node.skip = true;
             skip = true;
         }
     }
-    if (proto_node->data.fn_proto.is_var_args) {
-        add_node_error(g, node,
+    if (!extern_node && proto_node->data.fn_proto.is_var_args) {
+        add_node_error(g, proto_node,
                 buf_sprintf("variadic arguments only allowed in extern functions"));
     }
-    if (!skip) {
-        FnTableEntry *fn_table_entry = allocate<FnTableEntry>(1);
-        fn_table_entry->import_entry = import;
-        fn_table_entry->proto_node = proto_node;
-        fn_table_entry->fn_def_node = node;
-        fn_table_entry->internal_linkage = is_internal;
-        fn_table_entry->calling_convention = is_internal ? LLVMFastCallConv : LLVMCCallConv;
-        fn_table_entry->label_table.init(8);
-        fn_table_entry->member_of_struct = struct_type;
+    if (skip) {
+        return;
+    }
 
-        if (struct_type) {
-            buf_resize(&fn_table_entry->symbol_name, 0);
-            buf_appendf(&fn_table_entry->symbol_name, "%s_%s",
-                    buf_ptr(&struct_type->name),
-                    buf_ptr(proto_name));
-        } else {
-            buf_init_from_buf(&fn_table_entry->symbol_name, proto_name);
-        }
+    FnTableEntry *fn_table_entry = allocate<FnTableEntry>(1);
+    fn_table_entry->import_entry = import;
+    fn_table_entry->proto_node = proto_node;
+    fn_table_entry->fn_def_node = fn_def_node;
+    fn_table_entry->internal_linkage = !is_c_compat;
+    fn_table_entry->is_extern = extern_node;
+    fn_table_entry->calling_convention = is_c_compat ? LLVMCCallConv : LLVMFastCallConv;
+    fn_table_entry->label_table.init(8);
+    fn_table_entry->member_of_struct = struct_type;
+
+    if (struct_type) {
+        buf_resize(&fn_table_entry->symbol_name, 0);
+        buf_appendf(&fn_table_entry->symbol_name, "%s_%s",
+                buf_ptr(&struct_type->name),
+                buf_ptr(proto_name));
+    } else {
+        buf_init_from_buf(&fn_table_entry->symbol_name, proto_name);
+    }
+
+    g->fn_protos.append(fn_table_entry);
 
-        g->fn_protos.append(fn_table_entry);
+    if (!extern_node) {
         g->fn_defs.append(fn_table_entry);
+    }
 
-        fn_table->put(proto_name, fn_table_entry);
+    fn_table->put(proto_name, fn_table_entry);
 
-        if (!struct_type &&
-            g->bootstrap_import &&
-            import == g->root_import && buf_eql_str(proto_name, "main"))
-        {
-            g->bootstrap_import->fn_table.put(proto_name, fn_table_entry);
-        }
+    if (!struct_type &&
+        g->bootstrap_import &&
+        import == g->root_import && buf_eql_str(proto_name, "main"))
+    {
+        g->bootstrap_import->fn_table.put(proto_name, fn_table_entry);
+    }
 
-        resolve_function_proto(g, proto_node, fn_table_entry, import);
+    resolve_function_proto(g, proto_node, fn_table_entry, import);
 
 
-        alloc_codegen_node(proto_node);
-        proto_node->codegen_node->data.fn_proto_node.fn_table_entry = fn_table_entry;
+    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);
+    if (fn_def_node) {
+        preview_function_labels(g, fn_def_node->data.fn_def.body, fn_table_entry);
     }
 }
 
-static void preview_function_declarations(CodeGen *g, ImportTableEntry *import, AstNode *node) {
+static void resolve_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode *node) {
     switch (node->type) {
         case NodeTypeExternBlock:
             for (int i = 0; i < node->data.extern_block.directives->length; i += 1) {
@@ -803,33 +823,9 @@ static void preview_function_declarations(CodeGen *g, ImportTableEntry *import,
                             buf_sprintf("invalid directive: '%s'", buf_ptr(name)));
                 }
             }
-
-            for (int fn_decl_i = 0; fn_decl_i < node->data.extern_block.fn_decls.length; fn_decl_i += 1) {
-                AstNode *fn_decl = node->data.extern_block.fn_decls.at(fn_decl_i);
-                assert(fn_decl->type == NodeTypeFnDecl);
-                AstNode *fn_proto = fn_decl->data.fn_decl.fn_proto;
-
-                FnTableEntry *fn_table_entry = allocate<FnTableEntry>(1);
-                fn_table_entry->proto_node = fn_proto;
-                fn_table_entry->is_extern = true;
-                fn_table_entry->calling_convention = LLVMCCallConv;
-                fn_table_entry->import_entry = import;
-                fn_table_entry->label_table.init(8);
-
-                buf_init_from_buf(&fn_table_entry->symbol_name, &fn_proto->data.fn_proto.name);
-
-                resolve_function_proto(g, fn_proto, fn_table_entry, import);
-
-                Buf *name = &fn_proto->data.fn_proto.name;
-                g->fn_protos.append(fn_table_entry);
-                import->fn_table.put(name, fn_table_entry);
-
-                alloc_codegen_node(fn_proto);
-                fn_proto->codegen_node->data.fn_proto_node.fn_table_entry = fn_table_entry;
-            }
             break;
-        case NodeTypeFnDef:
-            preview_fn_def(g, import, node, nullptr);
+        case NodeTypeFnProto:
+            preview_fn_proto(g, import, node);
             break;
         case NodeTypeRootExportDecl:
             if (import == g->root_import) {
@@ -881,95 +877,22 @@ static void preview_function_declarations(CodeGen *g, ImportTableEntry *import,
 
                 resolve_struct_type(g, import, type_entry);
 
-                for (int i = 0; i < node->data.struct_decl.fns.length; i += 1) {
-                    AstNode *fn_def_node = node->data.struct_decl.fns.at(i);
-                    preview_fn_def(g, import, fn_def_node, type_entry);
-                }
+                // struct member fns will get resolved independently
                 break;
             }
-        case NodeTypeUse:
         case NodeTypeVariableDeclaration:
-            // nothing to do here
-            break;
-        case NodeTypeDirective:
-        case NodeTypeParamDecl:
-        case NodeTypeFnProto:
-        case NodeTypeType:
-        case NodeTypeFnDecl:
-        case NodeTypeReturnExpr:
-        case NodeTypeRoot:
-        case NodeTypeBlock:
-        case NodeTypeBinOpExpr:
-        case NodeTypeFnCallExpr:
-        case NodeTypeArrayAccessExpr:
-        case NodeTypeSliceExpr:
-        case NodeTypeNumberLiteral:
-        case NodeTypeStringLiteral:
-        case NodeTypeCharLiteral:
-        case NodeTypeUnreachable:
-        case NodeTypeVoid:
-        case NodeTypeBoolLiteral:
-        case NodeTypeNullLiteral:
-        case NodeTypeSymbol:
-        case NodeTypeCastExpr:
-        case NodeTypePrefixOpExpr:
-        case NodeTypeIfBoolExpr:
-        case NodeTypeIfVarExpr:
-        case NodeTypeWhileExpr:
-        case NodeTypeLabel:
-        case NodeTypeGoto:
-        case NodeTypeBreak:
-        case NodeTypeContinue:
-        case NodeTypeAsmExpr:
-        case NodeTypeFieldAccessExpr:
-        case NodeTypeStructField:
-        case NodeTypeStructValueExpr:
-        case NodeTypeStructValueField:
-        case NodeTypeCompilerFnExpr:
-        case NodeTypeCompilerFnType:
-            zig_unreachable();
-    }
-}
-
-static void preview_types(CodeGen *g, ImportTableEntry *import, AstNode *node) {
-    switch (node->type) {
-        case NodeTypeStructDecl:
             {
-                alloc_codegen_node(node);
-                StructDeclNode *struct_codegen = &node->codegen_node->data.struct_decl_node;
-
-                Buf *name = &node->data.struct_decl.name;
-                auto table_entry = import->type_table.maybe_get(name);
-                if (table_entry) {
-                    struct_codegen->type_entry = table_entry->value;
-                    add_node_error(g, node,
-                            buf_sprintf("redefinition of '%s'", buf_ptr(name)));
-                } else {
-                    TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdStruct);
-                    entry->type_ref = LLVMStructCreateNamed(LLVMGetGlobalContext(), buf_ptr(name));
-                    entry->data.structure.decl_node = node;
-                    entry->di_type = LLVMZigCreateReplaceableCompositeType(g->dbuilder,
-                        LLVMZigTag_DW_structure_type(), buf_ptr(&node->data.struct_decl.name),
-                        LLVMZigFileToScope(import->di_file), import->di_file, node->line + 1);
-
-                    buf_init_from_buf(&entry->name, name);
-                    // put off adding the debug type until we do the full struct body
-                    // this type is incomplete until we do another pass
-                    import->type_table.put(&entry->name, entry);
-                    struct_codegen->type_entry = entry;
-                }
+                VariableTableEntry *var = analyze_variable_declaration(g, import, import->block_context,
+                        nullptr, node);
+                g->global_vars.append(var);
                 break;
             }
-        case NodeTypeExternBlock:
-        case NodeTypeFnDef:
-        case NodeTypeRootExportDecl:
         case NodeTypeUse:
-        case NodeTypeVariableDeclaration:
-            // nothing to do
+            // nothing to do here
             break;
+        case NodeTypeFnDef:
         case NodeTypeDirective:
         case NodeTypeParamDecl:
-        case NodeTypeFnProto:
         case NodeTypeType:
         case NodeTypeFnDecl:
         case NodeTypeReturnExpr:
@@ -2552,15 +2475,15 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
 static void analyze_top_level_fn_def(CodeGen *g, ImportTableEntry *import, AstNode *node) {
     assert(node->type == NodeTypeFnDef);
 
-    if (node->codegen_node && node->codegen_node->data.fn_def_node.skip) {
+    AstNode *fn_proto_node = node->data.fn_def.fn_proto;
+    assert(fn_proto_node->type == NodeTypeFnProto);
+
+    if (fn_proto_node->codegen_node->data.fn_proto_node.skip) {
         // we detected an error with this function definition which prevents us
         // from further analyzing it.
         return;
     }
 
-    AstNode *fn_proto_node = node->data.fn_def.fn_proto;
-    assert(fn_proto_node->type == NodeTypeFnProto);
-
     alloc_codegen_node(node);
     BlockContext *context = new_block_context(node, import->block_context);
     node->codegen_node->data.fn_def_node.block_context = context;
@@ -2630,7 +2553,7 @@ static void analyze_top_level_fn_def(CodeGen *g, ImportTableEntry *import, AstNo
     }
 }
 
-static void analyze_top_level_declaration(CodeGen *g, ImportTableEntry *import, AstNode *node) {
+static void analyze_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode *node) {
     switch (node->type) {
         case NodeTypeFnDef:
             analyze_top_level_fn_def(g, import, node);
@@ -2732,16 +2655,332 @@ static void analyze_top_level_declaration(CodeGen *g, ImportTableEntry *import,
                 }
                 break;
             }
+        case NodeTypeVariableDeclaration:
+            // handled in resolve phase
+            break;
+        case NodeTypeDirective:
+        case NodeTypeParamDecl:
+        case NodeTypeFnProto:
+        case NodeTypeType:
+        case NodeTypeFnDecl:
+        case NodeTypeReturnExpr:
+        case NodeTypeRoot:
+        case NodeTypeBlock:
+        case NodeTypeBinOpExpr:
+        case NodeTypeFnCallExpr:
+        case NodeTypeArrayAccessExpr:
+        case NodeTypeSliceExpr:
+        case NodeTypeNumberLiteral:
+        case NodeTypeStringLiteral:
+        case NodeTypeCharLiteral:
+        case NodeTypeUnreachable:
+        case NodeTypeVoid:
+        case NodeTypeBoolLiteral:
+        case NodeTypeNullLiteral:
+        case NodeTypeSymbol:
+        case NodeTypeCastExpr:
+        case NodeTypePrefixOpExpr:
+        case NodeTypeIfBoolExpr:
+        case NodeTypeIfVarExpr:
+        case NodeTypeWhileExpr:
+        case NodeTypeLabel:
+        case NodeTypeGoto:
+        case NodeTypeBreak:
+        case NodeTypeContinue:
+        case NodeTypeAsmExpr:
+        case NodeTypeFieldAccessExpr:
+        case NodeTypeStructField:
+        case NodeTypeStructValueExpr:
+        case NodeTypeStructValueField:
+        case NodeTypeCompilerFnExpr:
+        case NodeTypeCompilerFnType:
+            zig_unreachable();
+    }
+}
+
+static void collect_expr_decl_deps(CodeGen *g, ImportTableEntry *import, AstNode *expr_node,
+        DeclNode *decl_node)
+{
+    switch (expr_node->type) {
+        case NodeTypeNumberLiteral:
+        case NodeTypeStringLiteral:
+        case NodeTypeCharLiteral:
+        case NodeTypeVoid:
+        case NodeTypeBoolLiteral:
+        case NodeTypeNullLiteral:
+        case NodeTypeUnreachable:
+        case NodeTypeGoto:
+        case NodeTypeBreak:
+        case NodeTypeContinue:
+            // no dependencies on other top level declarations
+            break;
+        case NodeTypeSymbol:
+            decl_node->deps.put(&expr_node->data.symbol, expr_node);
+            break;
+        case NodeTypeBinOpExpr:
+            collect_expr_decl_deps(g, import, expr_node->data.bin_op_expr.op1, decl_node);
+            collect_expr_decl_deps(g, import, expr_node->data.bin_op_expr.op2, decl_node);
+            break;
+        case NodeTypeReturnExpr:
+            collect_expr_decl_deps(g, import, expr_node->data.return_expr.expr, decl_node);
+            break;
+        case NodeTypeCastExpr:
+            collect_expr_decl_deps(g, import, expr_node->data.cast_expr.expr, decl_node);
+            collect_type_decl_deps(g, import, expr_node->data.cast_expr.type, decl_node);
+            break;
+        case NodeTypePrefixOpExpr:
+            collect_expr_decl_deps(g, import, expr_node->data.prefix_op_expr.primary_expr, decl_node);
+            break;
+        case NodeTypeFnCallExpr:
+            collect_expr_decl_deps(g, import, expr_node->data.fn_call_expr.fn_ref_expr, decl_node);
+            for (int i = 0; i < expr_node->data.fn_call_expr.params.length; i += 1) {
+                AstNode *arg_node = expr_node->data.fn_call_expr.params.at(i);
+                collect_expr_decl_deps(g, import, arg_node, decl_node);
+            }
+            break;
+        case NodeTypeArrayAccessExpr:
+            collect_expr_decl_deps(g, import, expr_node->data.array_access_expr.array_ref_expr, decl_node);
+            collect_expr_decl_deps(g, import, expr_node->data.array_access_expr.subscript, decl_node);
+            break;
+        case NodeTypeSliceExpr:
+            collect_expr_decl_deps(g, import, expr_node->data.slice_expr.array_ref_expr, decl_node);
+            collect_expr_decl_deps(g, import, expr_node->data.slice_expr.start, decl_node);
+            if (expr_node->data.slice_expr.end) {
+                collect_expr_decl_deps(g, import, expr_node->data.slice_expr.end, decl_node);
+            }
+            break;
+        case NodeTypeFieldAccessExpr:
+            collect_expr_decl_deps(g, import, expr_node->data.field_access_expr.struct_expr, decl_node);
+            break;
+        case NodeTypeIfBoolExpr:
+            collect_expr_decl_deps(g, import, expr_node->data.if_bool_expr.condition, decl_node);
+            collect_expr_decl_deps(g, import, expr_node->data.if_bool_expr.then_block, decl_node);
+            if (expr_node->data.if_bool_expr.else_node) {
+                collect_expr_decl_deps(g, import, expr_node->data.if_bool_expr.else_node, decl_node);
+            }
+            break;
+        case NodeTypeIfVarExpr:
+            if (expr_node->data.if_var_expr.var_decl.type) {
+                collect_type_decl_deps(g, import, expr_node->data.if_var_expr.var_decl.type, decl_node);
+            }
+            if (expr_node->data.if_var_expr.var_decl.expr) {
+                collect_expr_decl_deps(g, import, expr_node->data.if_var_expr.var_decl.expr, decl_node);
+            }
+            collect_expr_decl_deps(g, import, expr_node->data.if_var_expr.then_block, decl_node);
+            if (expr_node->data.if_bool_expr.else_node) {
+                collect_expr_decl_deps(g, import, expr_node->data.if_var_expr.else_node, decl_node);
+            }
+            break;
+        case NodeTypeWhileExpr:
+            collect_expr_decl_deps(g, import, expr_node->data.while_expr.condition, decl_node);
+            collect_expr_decl_deps(g, import, expr_node->data.while_expr.body, decl_node);
+            break;
+        case NodeTypeBlock:
+            for (int i = 0; i < expr_node->data.block.statements.length; i += 1) {
+                AstNode *stmt = expr_node->data.block.statements.at(i);
+                collect_expr_decl_deps(g, import, stmt, decl_node);
+            }
+            break;
+        case NodeTypeAsmExpr:
+            for (int i = 0; i < expr_node->data.asm_expr.output_list.length; i += 1) {
+                AsmOutput *asm_output = expr_node->data.asm_expr.output_list.at(i);
+                if (asm_output->return_type) {
+                    collect_type_decl_deps(g, import, asm_output->return_type, decl_node);
+                } else {
+                    decl_node->deps.put(&asm_output->variable_name, expr_node);
+                }
+            }
+            for (int i = 0; i < expr_node->data.asm_expr.input_list.length; i += 1) {
+                AsmInput *asm_input = expr_node->data.asm_expr.input_list.at(i);
+                collect_expr_decl_deps(g, import, asm_input->expr, decl_node);
+            }
+            break;
+        case NodeTypeStructValueExpr:
+            collect_type_decl_deps(g, import, expr_node->data.struct_val_expr.type, decl_node);
+            for (int i = 0; i < expr_node->data.struct_val_expr.fields.length; i += 1) {
+                AstNode *field_node = expr_node->data.struct_val_expr.fields.at(i);
+                assert(field_node->type == NodeTypeStructValueField);
+                collect_expr_decl_deps(g, import, field_node->data.struct_val_field.expr, decl_node);
+            }
+            break;
+        case NodeTypeCompilerFnExpr:
+            collect_expr_decl_deps(g, import, expr_node->data.compiler_fn_expr.expr, decl_node);
+            break;
+        case NodeTypeCompilerFnType:
+            collect_type_decl_deps(g, import, expr_node->data.compiler_fn_type.type, decl_node);
+            break;
+        case NodeTypeRoot:
+        case NodeTypeRootExportDecl:
+        case NodeTypeFnProto:
+        case NodeTypeFnDef:
+        case NodeTypeFnDecl:
+        case NodeTypeParamDecl:
+        case NodeTypeType:
+        case NodeTypeExternBlock:
+        case NodeTypeDirective:
+        case NodeTypeVariableDeclaration:
+        case NodeTypeUse:
+        case NodeTypeLabel:
+        case NodeTypeStructDecl:
+        case NodeTypeStructField:
+        case NodeTypeStructValueField:
+            zig_unreachable();
+    }
+}
+
+static void collect_type_decl_deps(CodeGen *g, ImportTableEntry *import, AstNode *type_node, DeclNode *decl_node) {
+    assert(type_node->type == NodeTypeType);
+    switch (type_node->data.type.type) {
+        case AstNodeTypeTypePrimitive:
+            {
+                Buf *name = &type_node->data.type.primitive_name;
+                auto table_entry = g->primitive_type_table.maybe_get(name);
+                if (!table_entry) {
+                    table_entry = import->type_table.maybe_get(name);
+                }
+                if (!table_entry) {
+                    decl_node->deps.put(name, type_node);
+                }
+                break;
+            }
+        case AstNodeTypeTypePointer:
+            collect_type_decl_deps(g, import, type_node->data.type.child_type, decl_node);
+            break;
+        case AstNodeTypeTypeArray:
+            collect_type_decl_deps(g, import, type_node->data.type.child_type, decl_node);
+            if (type_node->data.type.array_size) {
+                collect_expr_decl_deps(g, import, type_node->data.type.array_size, decl_node);
+            }
+            break;
+        case AstNodeTypeTypeMaybe:
+            collect_type_decl_deps(g, import, type_node->data.type.child_type, decl_node);
+            break;
+        case AstNodeTypeTypeCompilerExpr:
+            collect_expr_decl_deps(g, import, type_node->data.type.compiler_expr, decl_node);
+            break;
+    }
+}
+
+static void detect_top_level_decl_deps(CodeGen *g, ImportTableEntry *import, AstNode *node) {
+    switch (node->type) {
+        case NodeTypeStructDecl:
+            {
+                alloc_codegen_node(node);
+                StructDeclNode *struct_codegen = &node->codegen_node->data.struct_decl_node;
+
+                Buf *name = &node->data.struct_decl.name;
+                auto table_entry = import->type_table.maybe_get(name);
+                if (!table_entry) {
+                    table_entry = g->primitive_type_table.maybe_get(name);
+                }
+                if (table_entry) {
+                    struct_codegen->type_entry = table_entry->value;
+                    add_node_error(g, node,
+                            buf_sprintf("redefinition of '%s'", buf_ptr(name)));
+                } else {
+                    TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdStruct);
+                    entry->type_ref = LLVMStructCreateNamed(LLVMGetGlobalContext(), buf_ptr(name));
+                    entry->data.structure.decl_node = node;
+                    entry->di_type = LLVMZigCreateReplaceableCompositeType(g->dbuilder,
+                        LLVMZigTag_DW_structure_type(), buf_ptr(&node->data.struct_decl.name),
+                        LLVMZigFileToScope(import->di_file), import->di_file, node->line + 1);
+
+                    buf_init_from_buf(&entry->name, name);
+                    // put off adding the debug type until we do the full struct body
+                    // this type is incomplete until we do another pass
+                    import->type_table.put(&entry->name, entry);
+                    struct_codegen->type_entry = entry;
+                }
+
+                // determine which other top level declarations this struct depends on.
+                DeclNode *decl_node = &node->codegen_node->decl_node;
+                for (int i = 0; i < node->data.struct_decl.fields.length; i += 1) {
+                    AstNode *field_node = node->data.struct_decl.fields.at(i);
+                    AstNode *type_node = field_node->data.struct_field.type;
+                    collect_type_decl_deps(g, import, type_node, decl_node);
+                }
+                node->codegen_node->decl_node.name = name;
+                node->codegen_node->decl_node.import = import;
+                if (decl_node->deps.size() > 0) {
+                    g->unresolved_top_level_decls.put(name, node);
+                } else {
+                    resolve_top_level_decl(g, import, node);
+                }
+
+                // handle the member function definitions independently
+                for (int i = 0; i < node->data.struct_decl.fns.length; i += 1) {
+                    AstNode *fn_def_node = node->data.struct_decl.fns.at(i);
+                    AstNode *fn_proto_node = fn_def_node->data.fn_def.fn_proto;
+                    fn_proto_node->data.fn_proto.struct_node = node;
+                    detect_top_level_decl_deps(g, import, fn_def_node);
+                }
+
+                break;
+            }
+        case NodeTypeExternBlock:
+            for (int fn_decl_i = 0; fn_decl_i < node->data.extern_block.fn_decls.length; fn_decl_i += 1) {
+                AstNode *fn_decl = node->data.extern_block.fn_decls.at(fn_decl_i);
+                assert(fn_decl->type == NodeTypeFnDecl);
+                AstNode *fn_proto = fn_decl->data.fn_decl.fn_proto;
+                fn_proto->data.fn_proto.extern_node = node;
+                detect_top_level_decl_deps(g, import, fn_proto);
+            }
+            resolve_top_level_decl(g, import, node);
+            break;
+        case NodeTypeFnDef:
+            node->data.fn_def.fn_proto->data.fn_proto.fn_def_node = node;
+            detect_top_level_decl_deps(g, import, node->data.fn_def.fn_proto);
+            break;
         case NodeTypeVariableDeclaration:
             {
-                VariableTableEntry *var = analyze_variable_declaration(g, import, import->block_context,
-                        nullptr, node);
-                g->global_vars.append(var);
+                // determine which other top level declarations this variable declaration depends on.
+                alloc_codegen_node(node);
+                DeclNode *decl_node = &node->codegen_node->decl_node;
+                if (node->data.variable_declaration.type) {
+                    collect_type_decl_deps(g, import, node->data.variable_declaration.type, decl_node);
+                }
+                if (node->data.variable_declaration.expr) {
+                    collect_expr_decl_deps(g, import, node->data.variable_declaration.expr, decl_node);
+                }
+                Buf *name = &node->data.variable_declaration.symbol;
+                node->codegen_node->decl_node.name = name;
+                node->codegen_node->decl_node.import = import;
+                if (decl_node->deps.size() > 0) {
+                    g->unresolved_top_level_decls.put(name, node);
+                } else {
+                    resolve_top_level_decl(g, import, node);
+                }
                 break;
             }
+        case NodeTypeFnProto:
+            {
+                // determine which other top level declarations this function prototype depends on.
+                alloc_codegen_node(node);
+                DeclNode *decl_node = &node->codegen_node->decl_node;
+                for (int i = 0; i < node->data.fn_proto.params.length; i += 1) {
+                    AstNode *param_node = node->data.fn_proto.params.at(i);
+                    assert(param_node->type == NodeTypeParamDecl);
+                    collect_type_decl_deps(g, import, param_node->data.param_decl.type, decl_node);
+                }
+
+                Buf *name = &node->data.fn_proto.name;
+                node->codegen_node->decl_node.name = name;
+                node->codegen_node->decl_node.import = import;
+                if (decl_node->deps.size() > 0) {
+                    g->unresolved_top_level_decls.put(name, node);
+                } else {
+                    resolve_top_level_decl(g, import, node);
+                }
+                break;
+            }
+        case NodeTypeRootExportDecl:
+            resolve_top_level_decl(g, import, node);
+            break;
+        case NodeTypeUse:
+            // nothing to do
+            break;
         case NodeTypeDirective:
         case NodeTypeParamDecl:
-        case NodeTypeFnProto:
         case NodeTypeType:
         case NodeTypeFnDecl:
         case NodeTypeReturnExpr:
@@ -2779,24 +3018,69 @@ static void analyze_top_level_declaration(CodeGen *g, ImportTableEntry *import,
     }
 }
 
-static void find_function_declarations_root(CodeGen *g, ImportTableEntry *import, AstNode *node) {
-    assert(node->type == NodeTypeRoot);
+static void recursive_resolve_decl(CodeGen *g, ImportTableEntry *import, AstNode *node) {
+    auto it = node->codegen_node->decl_node.deps.entry_iterator();
+    for (;;) {
+        auto *entry = it.next();
+        if (!entry)
+            break;
 
-    for (int i = 0; i < node->data.root.top_level_decls.length; i += 1) {
-        AstNode *child = node->data.root.top_level_decls.at(i);
-        preview_function_declarations(g, import, child);
+        auto unresolved_entry = g->unresolved_top_level_decls.maybe_get(entry->key);
+        if (!unresolved_entry) {
+            continue;
+        }
+
+        AstNode *child_node = unresolved_entry->value;
+
+        if (child_node->codegen_node->decl_node.in_current_deps) {
+            zig_panic("TODO infinite top level decl loop");
+        }
+
+        // set temporary flag
+        child_node->codegen_node->decl_node.in_current_deps = true;
+
+        recursive_resolve_decl(g, child_node->codegen_node->decl_node.import, child_node);
+
+        // unset temporary flag
+        child_node->codegen_node->decl_node.in_current_deps = false;
     }
 
+    resolve_top_level_decl(g, import, node);
+    g->unresolved_top_level_decls.remove(node->codegen_node->decl_node.name);
 }
 
-static void preview_types_root(CodeGen *g, ImportTableEntry *import, AstNode *node) {
+static void resolve_top_level_declarations_root(CodeGen *g, ImportTableEntry *import, AstNode *node) {
     assert(node->type == NodeTypeRoot);
 
     for (int i = 0; i < node->data.root.top_level_decls.length; i += 1) {
         AstNode *child = node->data.root.top_level_decls.at(i);
-        preview_types(g, import, child);
+        detect_top_level_decl_deps(g, import, child);
     }
 
+    while (g->unresolved_top_level_decls.size() > 0) {
+        // for the sake of determinism, find the element with the lowest
+        // insert index and resolve that one.
+        AstNode *decl_node = nullptr;
+        auto it = g->unresolved_top_level_decls.entry_iterator();
+        for (;;) {
+            auto *entry = it.next();
+            if (!entry)
+                break;
+
+            AstNode *this_node = entry->value;
+            if (!decl_node || this_node->create_index < decl_node->create_index) {
+                decl_node = this_node;
+            }
+
+        }
+        // set temporary flag
+        decl_node->codegen_node->decl_node.in_current_deps = true;
+
+        recursive_resolve_decl(g, decl_node->codegen_node->decl_node.import, decl_node);
+
+        // unset temporary flag
+        decl_node->codegen_node->decl_node.in_current_deps = false;
+    }
 }
 
 static void analyze_top_level_decls_root(CodeGen *g, ImportTableEntry *import, AstNode *node) {
@@ -2804,7 +3088,7 @@ static void analyze_top_level_decls_root(CodeGen *g, ImportTableEntry *import, A
 
     for (int i = 0; i < node->data.root.top_level_decls.length; i += 1) {
         AstNode *child = node->data.root.top_level_decls.at(i);
-        analyze_top_level_declaration(g, import, child);
+        analyze_top_level_decl(g, import, child);
     }
 }
 
@@ -2817,21 +3101,9 @@ void semantic_analyze(CodeGen *g) {
                 break;
 
             ImportTableEntry *import = entry->value;
-            preview_types_root(g, import, import->root);
-        }
-    }
-    {
-        auto it = g->import_table.entry_iterator();
-        for (;;) {
-            auto *entry = it.next();
-            if (!entry)
-                break;
-
-            ImportTableEntry *import = entry->value;
-            find_function_declarations_root(g, import, import->root);
+            resolve_top_level_declarations_root(g, import, import->root);
         }
     }
-
     {
         auto it = g->import_table.entry_iterator();
         for (;;) {
@@ -2844,7 +3116,6 @@ void semantic_analyze(CodeGen *g) {
         }
     }
 
-
     if (!g->root_out_name) {
         add_node_error(g, g->root_import->root,
                 buf_sprintf("missing export declaration and output name not provided"));
@@ -2857,5 +3128,6 @@ void semantic_analyze(CodeGen *g) {
 void alloc_codegen_node(AstNode *node) {
     assert(!node->codegen_node);
     node->codegen_node = allocate<CodeGenNode>(1);
+    node->codegen_node->decl_node.deps.init(1);
 }
 
src/analyze.hpp
@@ -178,6 +178,10 @@ struct CodeGen {
     HashMap<Buf *, bool, buf_hash, buf_eql_buf> link_table;
     HashMap<Buf *, ImportTableEntry *, buf_hash, buf_eql_buf> import_table;
     HashMap<Buf *, BuiltinFnEntry *, buf_hash, buf_eql_buf> builtin_fn_table;
+    HashMap<Buf *, TypeTableEntry *, buf_hash, buf_eql_buf> primitive_type_table;
+    HashMap<Buf *, AstNode *, buf_hash, buf_eql_buf> unresolved_top_level_decls;
+
+    uint32_t next_unresolved_index;
 
     struct {
         TypeTableEntry *entry_bool;
@@ -270,12 +274,12 @@ struct TypeNode {
 
 struct FnProtoNode {
     FnTableEntry *fn_table_entry;
+    bool skip;
 };
 
 struct FnDefNode {
     TypeTableEntry *implicit_return_type;
     BlockContext *block_context;
-    bool skip;
 };
 
 
@@ -364,6 +368,15 @@ struct FnCallNode {
     BuiltinFnEntry *builtin_fn;
 };
 
+struct DeclNode {
+    HashMap<Buf *, AstNode *, buf_hash, buf_eql_buf> deps;
+    Buf *name;
+    ImportTableEntry *import;
+    // set this flag temporarily to detect infinite loops
+    bool in_current_deps;
+};
+
+// TODO get rid of this structure and put the data directly in the appropriate AST node
 struct CodeGenNode {
     union {
         TypeNode type_node; // for NodeTypeType
@@ -388,6 +401,7 @@ struct CodeGenNode {
         FnCallNode fn_call_node; // for NodeTypeFnCallExpr
     } data;
     ExprNode expr_node; // for all the expression nodes
+    DeclNode decl_node; // for all top level decls
 };
 
 void semantic_analyze(CodeGen *g);
src/codegen.cpp
@@ -23,6 +23,8 @@ CodeGen *codegen_create(Buf *root_source_dir) {
     g->link_table.init(32);
     g->import_table.init(32);
     g->builtin_fn_table.init(32);
+    g->primitive_type_table.init(32);
+    g->unresolved_top_level_decls.init(32);
     g->build_type = CodeGenBuildTypeDebug;
     g->root_source_dir = root_source_dir;
 
@@ -2109,6 +2111,7 @@ static void define_builtin_types(CodeGen *g) {
                 entry->size_in_bits, entry->align_in_bits,
                 LLVMZigEncoding_DW_ATE_unsigned());
         g->builtin_types.entry_bool = entry;
+        g->primitive_type_table.put(&entry->name, entry);
     }
     {
         TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdInt);
@@ -2120,6 +2123,7 @@ static void define_builtin_types(CodeGen *g) {
                 entry->size_in_bits, entry->align_in_bits,
                 LLVMZigEncoding_DW_ATE_unsigned());
         g->builtin_types.entry_u8 = entry;
+        g->primitive_type_table.put(&entry->name, entry);
     }
     {
         TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdInt);
@@ -2132,6 +2136,7 @@ static void define_builtin_types(CodeGen *g) {
                 entry->size_in_bits, entry->align_in_bits,
                 LLVMZigEncoding_DW_ATE_unsigned());
         g->builtin_types.entry_u16 = entry;
+        g->primitive_type_table.put(&entry->name, entry);
     }
     {
         TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdInt);
@@ -2144,6 +2149,7 @@ static void define_builtin_types(CodeGen *g) {
                 entry->size_in_bits, entry->align_in_bits,
                 LLVMZigEncoding_DW_ATE_unsigned());
         g->builtin_types.entry_u32 = entry;
+        g->primitive_type_table.put(&entry->name, entry);
     }
     {
         TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdInt);
@@ -2156,6 +2162,7 @@ static void define_builtin_types(CodeGen *g) {
                 entry->size_in_bits, entry->align_in_bits,
                 LLVMZigEncoding_DW_ATE_unsigned());
         g->builtin_types.entry_u64 = entry;
+        g->primitive_type_table.put(&entry->name, entry);
     }
     g->builtin_types.entry_c_string_literal = get_pointer_to_type(g, g->builtin_types.entry_u8, true, false);
     {
@@ -2169,6 +2176,7 @@ static void define_builtin_types(CodeGen *g) {
                 entry->size_in_bits, entry->align_in_bits,
                 LLVMZigEncoding_DW_ATE_signed());
         g->builtin_types.entry_i8 = entry;
+        g->primitive_type_table.put(&entry->name, entry);
     }
     {
         TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdInt);
@@ -2181,6 +2189,7 @@ static void define_builtin_types(CodeGen *g) {
                 entry->size_in_bits, entry->align_in_bits,
                 LLVMZigEncoding_DW_ATE_signed());
         g->builtin_types.entry_i16 = entry;
+        g->primitive_type_table.put(&entry->name, entry);
     }
     {
         TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdInt);
@@ -2193,6 +2202,7 @@ static void define_builtin_types(CodeGen *g) {
                 entry->size_in_bits, entry->align_in_bits,
                 LLVMZigEncoding_DW_ATE_signed());
         g->builtin_types.entry_i32 = entry;
+        g->primitive_type_table.put(&entry->name, entry);
     }
     {
         TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdInt);
@@ -2205,6 +2215,7 @@ static void define_builtin_types(CodeGen *g) {
                 entry->size_in_bits, entry->align_in_bits,
                 LLVMZigEncoding_DW_ATE_signed());
         g->builtin_types.entry_i64 = entry;
+        g->primitive_type_table.put(&entry->name, entry);
     }
     {
         TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdInt);
@@ -2217,6 +2228,7 @@ static void define_builtin_types(CodeGen *g) {
                 entry->size_in_bits, entry->align_in_bits,
                 LLVMZigEncoding_DW_ATE_signed());
         g->builtin_types.entry_isize = entry;
+        g->primitive_type_table.put(&entry->name, entry);
     }
     {
         TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdInt);
@@ -2229,6 +2241,7 @@ static void define_builtin_types(CodeGen *g) {
                 entry->size_in_bits, entry->align_in_bits,
                 LLVMZigEncoding_DW_ATE_unsigned());
         g->builtin_types.entry_usize = entry;
+        g->primitive_type_table.put(&entry->name, entry);
     }
     {
         TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdFloat);
@@ -2240,6 +2253,7 @@ static void define_builtin_types(CodeGen *g) {
                 entry->size_in_bits, entry->align_in_bits,
                 LLVMZigEncoding_DW_ATE_float());
         g->builtin_types.entry_f32 = entry;
+        g->primitive_type_table.put(&entry->name, entry);
     }
     {
         TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdFloat);
@@ -2251,6 +2265,7 @@ static void define_builtin_types(CodeGen *g) {
                 entry->size_in_bits, entry->align_in_bits,
                 LLVMZigEncoding_DW_ATE_float());
         g->builtin_types.entry_f64 = entry;
+        g->primitive_type_table.put(&entry->name, entry);
     }
     {
         TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdVoid);
@@ -2260,6 +2275,7 @@ static void define_builtin_types(CodeGen *g) {
                 entry->size_in_bits, entry->align_in_bits,
                 LLVMZigEncoding_DW_ATE_unsigned());
         g->builtin_types.entry_void = entry;
+        g->primitive_type_table.put(&entry->name, entry);
     }
     {
         TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdUnreachable);
@@ -2267,6 +2283,7 @@ static void define_builtin_types(CodeGen *g) {
         buf_init_from_str(&entry->name, "unreachable");
         entry->di_type = g->builtin_types.entry_void->di_type;
         g->builtin_types.entry_unreachable = entry;
+        g->primitive_type_table.put(&entry->name, entry);
     }
 }
 
@@ -2506,21 +2523,6 @@ static ImportTableEntry *codegen_add_code(CodeGen *g, Buf *abs_full_path,
     import_entry->path = full_path;
     import_entry->fn_table.init(32);
     import_entry->type_table.init(32);
-    import_entry->type_table.put(&g->builtin_types.entry_bool->name, g->builtin_types.entry_bool);
-    import_entry->type_table.put(&g->builtin_types.entry_u8->name, g->builtin_types.entry_u8);
-    import_entry->type_table.put(&g->builtin_types.entry_u16->name, g->builtin_types.entry_u16);
-    import_entry->type_table.put(&g->builtin_types.entry_u32->name, g->builtin_types.entry_u32);
-    import_entry->type_table.put(&g->builtin_types.entry_u64->name, g->builtin_types.entry_u64);
-    import_entry->type_table.put(&g->builtin_types.entry_i8->name, g->builtin_types.entry_i8);
-    import_entry->type_table.put(&g->builtin_types.entry_i16->name, g->builtin_types.entry_i16);
-    import_entry->type_table.put(&g->builtin_types.entry_i32->name, g->builtin_types.entry_i32);
-    import_entry->type_table.put(&g->builtin_types.entry_i64->name, g->builtin_types.entry_i64);
-    import_entry->type_table.put(&g->builtin_types.entry_isize->name, g->builtin_types.entry_isize);
-    import_entry->type_table.put(&g->builtin_types.entry_usize->name, g->builtin_types.entry_usize);
-    import_entry->type_table.put(&g->builtin_types.entry_f32->name, g->builtin_types.entry_f32);
-    import_entry->type_table.put(&g->builtin_types.entry_f64->name, g->builtin_types.entry_f64);
-    import_entry->type_table.put(&g->builtin_types.entry_void->name, g->builtin_types.entry_void);
-    import_entry->type_table.put(&g->builtin_types.entry_unreachable->name, g->builtin_types.entry_unreachable);
 
     import_entry->root = ast_parse(source_code, tokenization.tokens, import_entry, g->err_color);
     assert(import_entry->root);
src/parser.cpp
@@ -457,6 +457,7 @@ struct ParseContext {
     ZigList<AstNode *> *directive_list;
     ImportTableEntry *owner;
     ErrColor err_color;
+    uint32_t next_create_index;
 };
 
 __attribute__ ((format (printf, 4, 5)))
@@ -512,6 +513,8 @@ static AstNode *ast_create_node_no_line_info(ParseContext *pc, NodeType type) {
     AstNode *node = allocate<AstNode>(1);
     node->type = type;
     node->owner = pc->owner;
+    node->create_index = pc->next_create_index;
+    pc->next_create_index += 1;
     return node;
 }
 
src/parser.hpp
@@ -80,6 +80,16 @@ struct AstNodeFnProto {
     ZigList<AstNode *> params;
     AstNode *return_type;
     bool is_var_args;
+
+    // the extern block this fn proto is inside. can be null.
+    // populated by semantic analyzer.
+    AstNode *extern_node;
+    // the struct decl node this fn proto is inside. can be null.
+    // populated by semantic analyzer.
+    AstNode *struct_node;
+    // the function definition this fn proto is inside. can be null.
+    // populated by semantic analyzer.
+    AstNode *fn_def_node;
 };
 
 struct AstNodeFnDef {
@@ -369,6 +379,7 @@ struct AstNode {
     enum NodeType type;
     int line;
     int column;
+    uint32_t create_index; // for determinism purposes
     CodeGenNode *codegen_node;
     ImportTableEntry *owner;
     union {
test/run_tests.cpp
@@ -1203,7 +1203,7 @@ struct A { a : A, }
 struct A { b : B, }
 struct B { c : C, }
 struct C { a : A, }
-    )SOURCE", 1, ".tmp_source.zig:2:1: error: struct has infinite size");
+    )SOURCE", 1, ".tmp_source.zig:4:1: error: struct has infinite size");
 
     add_compile_fail_case("invalid struct field", R"SOURCE(
 struct A { x : i32, }