Commit 0c71d2fdc1

Andrew Kelley <andrew@ziglang.org>
2021-04-29 07:43:26
stage2: implement semantic analysis for functions and global vars
* AstGen: add missing `break_inline` for comptime blocks. * Module: call getTree() in byteOffset(). This generates the AST when using cached ZIR and compile errors need to be reported. * Scope.File: distinguish between successful ZIR generation and AIR generation (when Decls in scope have been scanned). - `semaFile` correctly avoids doing work twice. * Implement first pass at `lookupInNamespace`. It has various TODOs left, such as `usingnamespace`, and setting up Decl dependencies.
1 parent 3462193
src/AstGen.zig
@@ -3035,7 +3035,10 @@ fn comptimeDecl(
     };
     defer decl_block.instructions.deinit(gpa);
 
-    _ = try expr(&decl_block, &decl_block.base, .none, body_node);
+    const block_result = try expr(&decl_block, &decl_block.base, .none, body_node);
+    if (decl_block.instructions.items.len == 0 or !decl_block.refIsNoReturn(block_result)) {
+        _ = try decl_block.addBreak(.break_inline, block_inst, .void_value);
+    }
     try decl_block.setBlockBody(block_inst);
 
     try wip_decls.payload.ensureUnusedCapacity(gpa, 6);
src/codegen.zig
@@ -2313,7 +2313,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
         }
 
         fn genDbgStmt(self: *Self, inst: *ir.Inst.DbgStmt) !MCValue {
-            // TODO when reworking tzir memory layout, rework source locations here as
+            // TODO when reworking AIR memory layout, rework source locations here as
             // well to be more efficient, as well as support inlined function calls correctly.
             // For now we convert LazySrcLoc to absolute byte offset, to match what the
             // existing codegen code expects.
src/Compilation.zig
@@ -394,7 +394,7 @@ pub const AllErrors = struct {
         for (notes) |*note, i| {
             const module_note = module_err_msg.notes[i];
             const source = try module_note.src_loc.file_scope.getSource(module.gpa);
-            const byte_offset = try module_note.src_loc.byteOffset();
+            const byte_offset = try module_note.src_loc.byteOffset(module.gpa);
             const loc = std.zig.findLineColumn(source, byte_offset);
             const sub_file_path = module_note.src_loc.file_scope.sub_file_path;
             note.* = .{
@@ -417,7 +417,7 @@ pub const AllErrors = struct {
             return;
         }
         const source = try module_err_msg.src_loc.file_scope.getSource(module.gpa);
-        const byte_offset = try module_err_msg.src_loc.byteOffset();
+        const byte_offset = try module_err_msg.src_loc.byteOffset(module.gpa);
         const loc = std.zig.findLineColumn(source, byte_offset);
         const sub_file_path = module_err_msg.src_loc.file_scope.sub_file_path;
         try errors.append(.{
src/Module.zig
@@ -271,7 +271,7 @@ pub const Decl = struct {
                 const func = payload.data;
                 func.deinit(gpa);
             }
-            if (decl.value_arena) |a| a.promote(gpa).deinit();
+            decl.clearValues(gpa);
         }
         decl.dependants.deinit(gpa);
         decl.dependencies.deinit(gpa);
@@ -284,6 +284,14 @@ pub const Decl = struct {
         }
     }
 
+    pub fn clearValues(decl: *Decl, gpa: *Allocator) void {
+        if (decl.value_arena) |arena_state| {
+            arena_state.promote(gpa).deinit();
+            decl.value_arena = null;
+            decl.has_tv = false;
+        }
+    }
+
     /// This name is relative to the containing namespace of the decl.
     /// The memory is owned by the containing File ZIR.
     pub fn getName(decl: Decl) ?[:0]const u8 {
@@ -319,7 +327,7 @@ pub const Decl = struct {
         return @intToEnum(Zir.Inst.Ref, zir.extra[decl.zir_decl_index + 6]);
     }
 
-    pub fn zirLinkSectionRef(decl: Decl) Zir.Inst.Ref {
+    pub fn zirLinksectionRef(decl: Decl) Zir.Inst.Ref {
         if (!decl.has_linksection) return .none;
         const zir = decl.namespace.file_scope.zir;
         const extra_index = decl.zir_decl_index + 6 + @boolToInt(decl.has_align);
@@ -727,10 +735,11 @@ pub const Scope = struct {
         base: Scope = Scope{ .tag = base_tag },
         status: enum {
             never_loaded,
+            retryable_failure,
             parse_failure,
             astgen_failure,
-            retryable_failure,
-            success,
+            success_zir,
+            success_air,
         },
         source_loaded: bool,
         tree_loaded: bool,
@@ -2043,7 +2052,7 @@ pub const SrcLoc = struct {
         return @bitCast(ast.Node.Index, offset + @bitCast(i32, src_loc.parent_decl_node));
     }
 
-    pub fn byteOffset(src_loc: SrcLoc) !u32 {
+    pub fn byteOffset(src_loc: SrcLoc, gpa: *Allocator) !u32 {
         switch (src_loc.lazy) {
             .unneeded => unreachable,
             .entire_file => return 0,
@@ -2051,30 +2060,31 @@ pub const SrcLoc = struct {
             .byte_abs => |byte_index| return byte_index,
 
             .token_abs => |tok_index| {
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const token_starts = tree.tokens.items(.start);
                 return token_starts[tok_index];
             },
             .node_abs => |node| {
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const token_starts = tree.tokens.items(.start);
                 const tok_index = tree.firstToken(node);
                 return token_starts[tok_index];
             },
             .byte_offset => |byte_off| {
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const token_starts = tree.tokens.items(.start);
                 return token_starts[src_loc.declSrcToken()] + byte_off;
             },
             .token_offset => |tok_off| {
                 const tok_index = src_loc.declSrcToken() + tok_off;
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const token_starts = tree.tokens.items(.start);
                 return token_starts[tok_index];
             },
             .node_offset, .node_offset_bin_op => |node_off| {
                 const node = src_loc.declRelativeToNodeIndex(node_off);
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
+                assert(src_loc.file_scope.tree_loaded);
                 const main_tokens = tree.nodes.items(.main_token);
                 const tok_index = main_tokens[node];
                 const token_starts = tree.tokens.items(.start);
@@ -2082,14 +2092,14 @@ pub const SrcLoc = struct {
             },
             .node_offset_back2tok => |node_off| {
                 const node = src_loc.declRelativeToNodeIndex(node_off);
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const tok_index = tree.firstToken(node) - 2;
                 const token_starts = tree.tokens.items(.start);
                 return token_starts[tok_index];
             },
             .node_offset_var_decl_ty => |node_off| {
                 const node = src_loc.declRelativeToNodeIndex(node_off);
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_tags = tree.nodes.items(.tag);
                 const full = switch (node_tags[node]) {
                     .global_var_decl => tree.globalVarDecl(node),
@@ -2108,7 +2118,7 @@ pub const SrcLoc = struct {
                 return token_starts[tok_index];
             },
             .node_offset_builtin_call_arg0 => |node_off| {
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_datas = tree.nodes.items(.data);
                 const node_tags = tree.nodes.items(.tag);
                 const node = src_loc.declRelativeToNodeIndex(node_off);
@@ -2123,7 +2133,7 @@ pub const SrcLoc = struct {
                 return token_starts[tok_index];
             },
             .node_offset_builtin_call_arg1 => |node_off| {
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_datas = tree.nodes.items(.data);
                 const node_tags = tree.nodes.items(.tag);
                 const node = src_loc.declRelativeToNodeIndex(node_off);
@@ -2138,7 +2148,7 @@ pub const SrcLoc = struct {
                 return token_starts[tok_index];
             },
             .node_offset_array_access_index => |node_off| {
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_datas = tree.nodes.items(.data);
                 const node_tags = tree.nodes.items(.tag);
                 const node = src_loc.declRelativeToNodeIndex(node_off);
@@ -2148,7 +2158,7 @@ pub const SrcLoc = struct {
                 return token_starts[tok_index];
             },
             .node_offset_slice_sentinel => |node_off| {
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_datas = tree.nodes.items(.data);
                 const node_tags = tree.nodes.items(.tag);
                 const node = src_loc.declRelativeToNodeIndex(node_off);
@@ -2164,7 +2174,7 @@ pub const SrcLoc = struct {
                 return token_starts[tok_index];
             },
             .node_offset_call_func => |node_off| {
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_datas = tree.nodes.items(.data);
                 const node_tags = tree.nodes.items(.tag);
                 const node = src_loc.declRelativeToNodeIndex(node_off);
@@ -2190,7 +2200,7 @@ pub const SrcLoc = struct {
                 return token_starts[tok_index];
             },
             .node_offset_field_name => |node_off| {
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_datas = tree.nodes.items(.data);
                 const node_tags = tree.nodes.items(.tag);
                 const node = src_loc.declRelativeToNodeIndex(node_off);
@@ -2202,7 +2212,7 @@ pub const SrcLoc = struct {
                 return token_starts[tok_index];
             },
             .node_offset_deref_ptr => |node_off| {
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_datas = tree.nodes.items(.data);
                 const node_tags = tree.nodes.items(.tag);
                 const node = src_loc.declRelativeToNodeIndex(node_off);
@@ -2211,7 +2221,7 @@ pub const SrcLoc = struct {
                 return token_starts[tok_index];
             },
             .node_offset_asm_source => |node_off| {
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_datas = tree.nodes.items(.data);
                 const node_tags = tree.nodes.items(.tag);
                 const node = src_loc.declRelativeToNodeIndex(node_off);
@@ -2226,7 +2236,7 @@ pub const SrcLoc = struct {
                 return token_starts[tok_index];
             },
             .node_offset_asm_ret_ty => |node_off| {
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_datas = tree.nodes.items(.data);
                 const node_tags = tree.nodes.items(.tag);
                 const node = src_loc.declRelativeToNodeIndex(node_off);
@@ -2243,7 +2253,7 @@ pub const SrcLoc = struct {
 
             .node_offset_for_cond, .node_offset_if_cond => |node_off| {
                 const node = src_loc.declRelativeToNodeIndex(node_off);
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_tags = tree.nodes.items(.tag);
                 const src_node = switch (node_tags[node]) {
                     .if_simple => tree.ifSimple(node).ast.cond_expr,
@@ -2262,7 +2272,7 @@ pub const SrcLoc = struct {
             },
             .node_offset_bin_lhs => |node_off| {
                 const node = src_loc.declRelativeToNodeIndex(node_off);
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_datas = tree.nodes.items(.data);
                 const src_node = node_datas[node].lhs;
                 const main_tokens = tree.nodes.items(.main_token);
@@ -2272,7 +2282,7 @@ pub const SrcLoc = struct {
             },
             .node_offset_bin_rhs => |node_off| {
                 const node = src_loc.declRelativeToNodeIndex(node_off);
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_datas = tree.nodes.items(.data);
                 const src_node = node_datas[node].rhs;
                 const main_tokens = tree.nodes.items(.main_token);
@@ -2283,7 +2293,7 @@ pub const SrcLoc = struct {
 
             .node_offset_switch_operand => |node_off| {
                 const node = src_loc.declRelativeToNodeIndex(node_off);
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_datas = tree.nodes.items(.data);
                 const src_node = node_datas[node].lhs;
                 const main_tokens = tree.nodes.items(.main_token);
@@ -2294,7 +2304,7 @@ pub const SrcLoc = struct {
 
             .node_offset_switch_special_prong => |node_off| {
                 const switch_node = src_loc.declRelativeToNodeIndex(node_off);
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_datas = tree.nodes.items(.data);
                 const node_tags = tree.nodes.items(.tag);
                 const main_tokens = tree.nodes.items(.main_token);
@@ -2320,7 +2330,7 @@ pub const SrcLoc = struct {
 
             .node_offset_switch_range => |node_off| {
                 const switch_node = src_loc.declRelativeToNodeIndex(node_off);
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_datas = tree.nodes.items(.data);
                 const node_tags = tree.nodes.items(.tag);
                 const main_tokens = tree.nodes.items(.main_token);
@@ -2349,7 +2359,7 @@ pub const SrcLoc = struct {
             },
 
             .node_offset_fn_type_cc => |node_off| {
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_datas = tree.nodes.items(.data);
                 const node_tags = tree.nodes.items(.tag);
                 const node = src_loc.declRelativeToNodeIndex(node_off);
@@ -2368,7 +2378,7 @@ pub const SrcLoc = struct {
             },
 
             .node_offset_fn_type_ret_ty => |node_off| {
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_datas = tree.nodes.items(.data);
                 const node_tags = tree.nodes.items(.tag);
                 const node = src_loc.declRelativeToNodeIndex(node_off);
@@ -2387,7 +2397,7 @@ pub const SrcLoc = struct {
             },
 
             .node_offset_anyframe_type => |node_off| {
-                const tree = src_loc.file_scope.tree;
+                const tree = try src_loc.file_scope.getTree(gpa);
                 const node_datas = tree.nodes.items(.data);
                 const node_tags = tree.nodes.items(.tag);
                 const parent_node = src_loc.declRelativeToNodeIndex(node_off);
@@ -2912,7 +2922,7 @@ pub fn astGenFile(mod: *Module, file: *Scope.File, prog_node: *std.Progress.Node
             file.stat_size = header.stat_size;
             file.stat_inode = header.stat_inode;
             file.stat_mtime = header.stat_mtime;
-            file.status = .success;
+            file.status = .success_zir;
             log.debug("AstGen cached success: {s}", .{file.sub_file_path});
 
             // TODO don't report compile errors until Sema @importFile
@@ -2927,7 +2937,7 @@ pub fn astGenFile(mod: *Module, file: *Scope.File, prog_node: *std.Progress.Node
             }
             return;
         },
-        .parse_failure, .astgen_failure, .success => {
+        .parse_failure, .astgen_failure, .success_zir, .success_air => {
             const unchanged_metadata =
                 stat.size == file.stat_size and
                 stat.mtime == file.stat_mtime and
@@ -3024,7 +3034,7 @@ pub fn astGenFile(mod: *Module, file: *Scope.File, prog_node: *std.Progress.Node
 
     file.zir = try AstGen.generate(gpa, file);
     file.zir_loaded = true;
-    file.status = .success;
+    file.status = .success_zir;
     log.debug("AstGen fresh success: {s}", .{file.sub_file_path});
 
     const safety_buffer = if (data_has_safety_tag)
@@ -3197,11 +3207,13 @@ pub fn semaPkg(mod: *Module, pkg: *Package) !void {
 }
 
 pub fn semaFile(mod: *Module, file: *Scope.File) InnerError!void {
+    if (file.status == .success_air) return;
+
     const tracy = trace(@src());
     defer tracy.end();
 
     assert(file.zir_loaded);
-    assert(!file.zir.hasCompileErrors());
+    assert(file.status == .success_zir);
 
     const gpa = mod.gpa;
     var decl_arena = std.heap.ArenaAllocator.init(gpa);
@@ -3279,6 +3291,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) InnerError!void {
         if (dep == struct_decl) continue;
         _ = try mod.declareDeclDependency(struct_decl, dep);
     }
+    file.status = .success_air;
 }
 
 /// Returns `true` if the Decl type changed.
@@ -3319,26 +3332,128 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
     };
     defer block_scope.instructions.deinit(gpa);
 
+    const zir_datas = zir.instructions.items(.data);
+    const zir_tags = zir.instructions.items(.tag);
+
     const zir_block_index = decl.zirBlockIndex();
-    const inst_data = zir.instructions.items(.data)[zir_block_index].pl_node;
+    const inst_data = zir_datas[zir_block_index].pl_node;
     const extra = zir.extraData(Zir.Inst.Block, inst_data.payload_index);
     const body = zir.extra[extra.end..][0..extra.data.body_len];
     const break_index = try sema.analyzeBody(&block_scope, body);
+    const result_ref = zir_datas[break_index].@"break".operand;
+    const decl_tv = try sema.resolveInstConst(&block_scope, inst_data.src(), result_ref);
+    const align_val = blk: {
+        const align_ref = decl.zirAlignRef();
+        if (align_ref == .none) break :blk Value.initTag(.null_value);
+        break :blk (try sema.resolveInstConst(&block_scope, inst_data.src(), align_ref)).val;
+    };
+    const linksection_val = blk: {
+        const linksection_ref = decl.zirLinksectionRef();
+        if (linksection_ref == .none) break :blk Value.initTag(.null_value);
+        break :blk (try sema.resolveInstConst(&block_scope, inst_data.src(), linksection_ref)).val;
+    };
 
-    if (decl.zirAlignRef() != .none) {
-        @panic("TODO implement decl align");
-    }
-    if (decl.zirLinkSectionRef() != .none) {
-        @panic("TODO implement decl linksection");
-    }
+    // We need the memory for the Type to go into the arena for the Decl
+    var decl_arena = std.heap.ArenaAllocator.init(gpa);
+    errdefer decl_arena.deinit();
+    const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State);
+
+    if (decl_tv.val.tag() == .function) {
+        var prev_type_has_bits = false;
+        var prev_is_inline = false;
+        var type_changed = true;
+
+        if (decl.has_tv) {
+            prev_type_has_bits = decl.ty.hasCodeGenBits();
+            type_changed = !decl.ty.eql(decl_tv.ty);
+            if (decl.val.castTag(.function)) |payload| {
+                const prev_func = payload.data;
+                prev_is_inline = prev_func.state == .inline_only;
+                prev_func.deinit(gpa);
+            }
+            decl.clearValues(gpa);
+        }
 
-    decl.analysis = .complete;
-    decl.generation = mod.generation;
+        decl.ty = try decl_tv.ty.copy(&decl_arena.allocator);
+        decl.val = try decl_tv.val.copy(&decl_arena.allocator);
+        decl.align_val = try align_val.copy(&decl_arena.allocator);
+        decl.linksection_val = try linksection_val.copy(&decl_arena.allocator);
+        decl.has_tv = true;
+        decl_arena_state.* = decl_arena.state;
+        decl.value_arena = decl_arena_state;
+        decl.analysis = .complete;
+        decl.generation = mod.generation;
+
+        const is_inline = decl_tv.ty.fnCallingConvention() == .Inline;
+        if (!is_inline and decl_tv.ty.hasCodeGenBits()) {
+            // We don't fully codegen the decl until later, but we do need to reserve a global
+            // offset table index for it. This allows us to codegen decls out of dependency order,
+            // increasing how many computations can be done in parallel.
+            try mod.comp.bin_file.allocateDeclIndexes(decl);
+            try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl });
+            if (type_changed and mod.emit_h != null) {
+                try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl });
+            }
+        } else if (!prev_is_inline and prev_type_has_bits) {
+            mod.comp.bin_file.freeDecl(decl);
+        }
+
+        if (decl.is_exported) {
+            const export_src = inst_data.src(); // TODO make this point at `export` token
+            if (is_inline) {
+                return mod.fail(&block_scope.base, export_src, "export of inline function", .{});
+            }
+            // The scope needs to have the decl in it.
+            try mod.analyzeExport(&block_scope.base, export_src, mem.spanZ(decl.name), decl);
+        }
+        return type_changed or is_inline != prev_is_inline;
+    } else {
+        const is_mutable = zir_tags[zir_block_index] == .block_inline_var;
 
-    // TODO inspect the type and return a proper type_changed result
-    @breakpoint();
+        var is_threadlocal = false; // TODO implement threadlocal variables
+        var is_extern = false; // TODO implement extern variables
 
-    return true;
+        if (is_mutable and !decl_tv.ty.isValidVarType(is_extern)) {
+            return mod.fail(
+                &block_scope.base,
+                inst_data.src(), // TODO point at the mut token
+                "variable of type '{}' must be const",
+                .{decl_tv.ty},
+            );
+        }
+
+        var type_changed = true;
+        if (decl.has_tv) {
+            type_changed = !decl.ty.eql(decl_tv.ty);
+            decl.clearValues(gpa);
+        }
+
+        const new_variable = try decl_arena.allocator.create(Var);
+        new_variable.* = .{
+            .owner_decl = decl,
+            .init = try decl_tv.val.copy(&decl_arena.allocator),
+            .is_extern = is_extern,
+            .is_mutable = is_mutable,
+            .is_threadlocal = is_threadlocal,
+        };
+
+        decl.ty = try decl_tv.ty.copy(&decl_arena.allocator);
+        decl.val = try Value.Tag.variable.create(&decl_arena.allocator, new_variable);
+        decl.align_val = try align_val.copy(&decl_arena.allocator);
+        decl.linksection_val = try linksection_val.copy(&decl_arena.allocator);
+        decl.has_tv = true;
+        decl_arena_state.* = decl_arena.state;
+        decl.value_arena = decl_arena_state;
+        decl.analysis = .complete;
+        decl.generation = mod.generation;
+
+        if (decl.is_exported) {
+            const export_src = inst_data.src(); // TODO point to the export token
+            // The scope needs to have the decl in it.
+            try mod.analyzeExport(&block_scope.base, export_src, mem.spanZ(decl.name), decl);
+        }
+        return type_changed;
+    }
 }
 
 /// Returns the depender's index of the dependee.
@@ -4160,6 +4275,7 @@ fn getNextAnonNameIndex(mod: *Module) usize {
 
 /// This looks up a bare identifier in the given scope. This will walk up the tree of namespaces
 /// in scope and check each one for the identifier.
+/// TODO emit a compile error if more than one decl would be matched.
 pub fn lookupIdentifier(mod: *Module, scope: *Scope, ident_name: []const u8) ?*Decl {
     var namespace = scope.namespace();
     while (true) {
@@ -4179,13 +4295,14 @@ pub fn lookupInNamespace(
     ident_name: []const u8,
     only_pub_usingnamespaces: bool,
 ) ?*Decl {
-    @panic("TODO lookupInNamespace");
+    // TODO the decl doing the looking up needs to create a decl dependency
+    // TODO implement usingnamespace
+    if (namespace.decls.get(ident_name)) |decl| {
+        return decl;
+    }
+    return null;
     //// TODO handle decl collision with usingnamespace
-    //// TODO the decl doing the looking up needs to create a decl dependency
     //// on each usingnamespace decl here.
-    //if (mod.decl_table.get(name_hash)) |decl| {
-    //    return decl;
-    //}
     //{
     //    var it = namespace.usingnamespace_set.iterator();
     //    while (it.next()) |entry| {
@@ -4198,7 +4315,6 @@ pub fn lookupInNamespace(
     //        }
     //    }
     //}
-    //return null;
 }
 
 pub fn makeIntType(arena: *Allocator, signedness: std.builtin.Signedness, bits: u16) !Type {
@@ -4659,7 +4775,7 @@ pub fn optimizeMode(mod: Module) std.builtin.Mode {
 
 fn lockAndClearFileCompileError(mod: *Module, file: *Scope.File) void {
     switch (file.status) {
-        .success, .retryable_failure => {},
+        .success_zir, .success_air, .retryable_failure => {},
         .never_loaded, .parse_failure, .astgen_failure => {
             const lock = mod.comp.mutex.acquire();
             defer lock.release();
src/Sema.zig
@@ -607,7 +607,7 @@ fn resolveInt(
     return val.toUnsignedInt();
 }
 
-fn resolveInstConst(
+pub fn resolveInstConst(
     sema: *Sema,
     block: *Scope.Block,
     src: LazySrcLoc,
@@ -1850,7 +1850,9 @@ fn zirDbgStmtNode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerE
     const src: LazySrcLoc = .{ .node_offset = src_node };
 
     const src_loc = src.toSrcLoc(&block.base);
-    const abs_byte_off = try src_loc.byteOffset();
+    const abs_byte_off = src_loc.byteOffset(sema.gpa) catch |err| {
+        return sema.mod.fail(&block.base, src, "TODO modify dbg_stmt ZIR instructions to have line/column rather than node indexes. {s}", .{@errorName(err)});
+    };
     _ = try block.addDbgStmt(src, abs_byte_off);
 }
 
@@ -2738,6 +2740,10 @@ fn funcCommon(
     const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = src_node_offset };
     const return_type = try sema.resolveType(block, ret_ty_src, zir_return_type);
 
+    if (body.len == 0) {
+        return sema.mod.fail(&block.base, src, "TODO: Sema: implement func with body", .{});
+    }
+
     // Hot path for some common function types.
     if (zir_param_types.len == 0 and !var_args) {
         if (return_type.zigTypeTag() == .NoReturn and cc == .Unspecified) {
@@ -4098,6 +4104,7 @@ fn zirImport(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerError!
             return mod.fail(&block.base, src, "unable to open '{s}': {s}", .{ operand, @errorName(err) });
         },
     };
+    try mod.semaFile(result.file);
     return mod.constType(sema.arena, src, result.file.namespace.ty);
 }
 
src/Zir.zig
@@ -44,8 +44,8 @@ pub const Header = extern struct {
     string_bytes_len: u32,
     extra_len: u32,
 
-    stat_size: u64,
     stat_inode: std.fs.File.INode,
+    stat_size: u64,
     stat_mtime: i128,
 };
 
@@ -4121,7 +4121,8 @@ const Writer = struct {
             .parent_decl_node = self.parent_decl_node,
             .lazy = src,
         };
-        const abs_byte_off = try src_loc.byteOffset();
+        // Caller must ensure AST tree is loaded.
+        const abs_byte_off = src_loc.byteOffset(self.gpa) catch unreachable;
         const delta_line = std.zig.findLineColumn(tree.source, abs_byte_off);
         try stream.print("{s}:{d}:{d}", .{
             @tagName(src), delta_line.line + 1, delta_line.column + 1,
BRANCH_TODO
@@ -1,3 +1,6 @@
+ * modify dbg_stmt ZIR instructions to have line/column rather than node indexes
+ * AstGen threadlocal
+ * extern "foo" for vars and for functions
  * namespace decls table can't reference ZIR memory because it can get modified on updates
    - change it for astgen worker to compare old and new ZIR, updating existing
      namespaces & decls, and creating a changelist.
@@ -65,49 +68,7 @@ fn getAnonTypeName(mod: *Module, scope: *Scope, base_token: std.zig.ast.TokenInd
 /// Returns `true` if this is the first time analyzing the Decl.
 /// Returns `false` otherwise.
 fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool {
-    const tracy = trace(@src());
-    defer tracy.end();
-
-    const tree = try mod.getAstTree(decl.namespace.file_scope);
-    const node_tags = tree.nodes.items(.tag);
-    const node_datas = tree.nodes.items(.data);
-    const decl_node = decl.src_node;
     switch (node_tags[decl_node]) {
-        .fn_decl => {
-            const fn_proto = node_datas[decl_node].lhs;
-            const body = node_datas[decl_node].rhs;
-            switch (node_tags[fn_proto]) {
-                .fn_proto_simple => {
-                    var params: [1]ast.Node.Index = undefined;
-                    return mod.astgenAndSemaFn(decl, tree.*, body, tree.fnProtoSimple(&params, fn_proto));
-                },
-                .fn_proto_multi => return mod.astgenAndSemaFn(decl, tree.*, body, tree.fnProtoMulti(fn_proto)),
-                .fn_proto_one => {
-                    var params: [1]ast.Node.Index = undefined;
-                    return mod.astgenAndSemaFn(decl, tree.*, body, tree.fnProtoOne(&params, fn_proto));
-                },
-                .fn_proto => return mod.astgenAndSemaFn(decl, tree.*, body, tree.fnProto(fn_proto)),
-                else => unreachable,
-            }
-        },
-        .fn_proto_simple => {
-            var params: [1]ast.Node.Index = undefined;
-            return mod.astgenAndSemaFn(decl, tree.*, 0, tree.fnProtoSimple(&params, decl_node));
-        },
-        .fn_proto_multi => return mod.astgenAndSemaFn(decl, tree.*, 0, tree.fnProtoMulti(decl_node)),
-        .fn_proto_one => {
-            var params: [1]ast.Node.Index = undefined;
-            return mod.astgenAndSemaFn(decl, tree.*, 0, tree.fnProtoOne(&params, decl_node));
-        },
-        .fn_proto => return mod.astgenAndSemaFn(decl, tree.*, 0, tree.fnProto(decl_node)),
-
-        .global_var_decl => return mod.astgenAndSemaVarDecl(decl, tree.*, tree.globalVarDecl(decl_node)),
-        .local_var_decl => return mod.astgenAndSemaVarDecl(decl, tree.*, tree.localVarDecl(decl_node)),
-        .simple_var_decl => return mod.astgenAndSemaVarDecl(decl, tree.*, tree.simpleVarDecl(decl_node)),
-        .aligned_var_decl => return mod.astgenAndSemaVarDecl(decl, tree.*, tree.alignedVarDecl(decl_node)),
-
-        .@"comptime" => {
-        },
         .@"usingnamespace" => {
             decl.analysis = .in_progress;
 
@@ -135,128 +96,6 @@ fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool {
     }
 }
 
-fn astgenAndSemaFn(
-    mod: *Module,
-    decl: *Decl,
-    tree: ast.Tree,
-    body_node: ast.Node.Index,
-    fn_proto: ast.full.FnProto,
-) !bool {
-    const is_inline = fn_type.fnCallingConvention() == .Inline;
-    const anal_state: Fn.Analysis = if (is_inline) .inline_only else .queued;
-
-    new_func.* = .{
-        .state = anal_state,
-        .zir = fn_zir,
-        .body = undefined,
-        .owner_decl = decl,
-    };
-    fn_payload.* = .{
-        .base = .{ .tag = .function },
-        .data = new_func,
-    };
-
-    var prev_type_has_bits = false;
-    var prev_is_inline = false;
-    var type_changed = true;
-
-    if (decl.typedValueManaged()) |tvm| {
-        prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits();
-        type_changed = !tvm.typed_value.ty.eql(fn_type);
-        if (tvm.typed_value.val.castTag(.function)) |payload| {
-            const prev_func = payload.data;
-            prev_is_inline = prev_func.state == .inline_only;
-            prev_func.deinit(mod.gpa);
-        }
-
-        tvm.deinit(mod.gpa);
-    }
-
-    decl_arena_state.* = decl_arena.state;
-    decl.typed_value = .{
-        .most_recent = .{
-            .typed_value = .{
-                .ty = fn_type,
-                .val = Value.initPayload(&fn_payload.base),
-            },
-            .arena = decl_arena_state,
-        },
-    };
-    decl.analysis = .complete;
-    decl.generation = mod.generation;
-
-    if (!is_inline and fn_type.hasCodeGenBits()) {
-        // We don't fully codegen the decl until later, but we do need to reserve a global
-        // offset table index for it. This allows us to codegen decls out of dependency order,
-        // increasing how many computations can be done in parallel.
-        try mod.comp.bin_file.allocateDeclIndexes(decl);
-        try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl });
-        if (type_changed and mod.emit_h != null) {
-            try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl });
-        }
-    } else if (!prev_is_inline and prev_type_has_bits) {
-        mod.comp.bin_file.freeDecl(decl);
-    }
-
-    if (fn_proto.extern_export_token) |maybe_export_token| {
-        if (token_tags[maybe_export_token] == .keyword_export) {
-            if (is_inline) {
-                return mod.failTok(
-                    &block_scope.base,
-                    maybe_export_token,
-                    "export of inline function",
-                    .{},
-                );
-            }
-            const export_src = decl.tokSrcLoc(maybe_export_token);
-            const name = tree.tokenSlice(fn_proto.name_token.?); // TODO identifierTokenString
-            // The scope needs to have the decl in it.
-            try mod.analyzeExport(&block_scope.base, export_src, name, decl);
-        }
-    }
-    return type_changed or is_inline != prev_is_inline;
-}
-        log.debug("extern fn symbol expected in lib '{s}'", .{lib_name_str});
-        mod.comp.stage1AddLinkLib(lib_name_str) catch |err| {
-            return mod.failTok(
-                &fn_type_scope.base,
-                lib_name_token,
-                "unable to add link lib '{s}': {s}",
-                .{ lib_name_str, @errorName(err) },
-            );
-        };
-        const target = mod.comp.getTarget();
-        if (target_util.is_libc_lib_name(target, lib_name_str)) {
-            if (!mod.comp.bin_file.options.link_libc) {
-                return mod.failTok(
-                    &fn_type_scope.base,
-                    lib_name_token,
-                    "dependency on libc must be explicitly specified in the build command",
-                    .{},
-                );
-            }
-            break :blk;
-        }
-        if (target_util.is_libcpp_lib_name(target, lib_name_str)) {
-            if (!mod.comp.bin_file.options.link_libcpp) {
-                return mod.failTok(
-                    &fn_type_scope.base,
-                    lib_name_token,
-                    "dependency on libc++ must be explicitly specified in the build command",
-                    .{},
-                );
-            }
-            break :blk;
-        }
-        if (!target.isWasm() and !mod.comp.bin_file.options.pic) {
-            return mod.failTok(
-                &fn_type_scope.base,
-                lib_name_token,
-                "dependency on dynamic library '{s}' requires enabling Position Independent Code. Fixed by `-l{s}` or `-fPIC`.",
-                .{ lib_name_str, lib_name_str },
-            );
-        }
-
     if (mod.lookupIdentifier(scope, ident_name)) |decl| {
         const msg = msg: {
             const msg = try mod.errMsg(
@@ -273,58 +112,6 @@ fn astgenAndSemaFn(
     }
 
 
-    var type_changed = true;
-    if (decl.typedValueManaged()) |tvm| {
-        type_changed = !tvm.typed_value.ty.eql(var_info.ty);
-
-        tvm.deinit(mod.gpa);
-    }
-
-    const new_variable = try decl_arena.allocator.create(Var);
-    new_variable.* = .{
-        .owner_decl = decl,
-        .init = var_info.val orelse undefined,
-        .is_extern = is_extern,
-        .is_mutable = is_mutable,
-        .is_threadlocal = is_threadlocal,
-    };
-    const var_val = try Value.Tag.variable.create(&decl_arena.allocator, new_variable);
-
-    decl_arena_state.* = decl_arena.state;
-    decl.typed_value = .{
-        .most_recent = .{
-            .typed_value = .{
-                .ty = var_info.ty,
-                .val = var_val,
-            },
-            .arena = decl_arena_state,
-        },
-    };
-    decl.analysis = .complete;
-    decl.generation = mod.generation;
-
-
-
-    if (is_mutable and !var_info.ty.isValidVarType(is_extern)) {
-        return mod.failTok(
-            &decl_scope.base,
-            var_decl.ast.mut_token,
-            "variable of type '{}' must be const",
-            .{var_info.ty},
-        );
-    }
-
-    if (var_decl.extern_export_token) |maybe_export_token| {
-        if (token_tags[maybe_export_token] == .keyword_export) {
-            const export_src = decl.tokSrcLoc(maybe_export_token);
-            const name_token = var_decl.ast.mut_token + 1;
-            const name = tree.tokenSlice(name_token); // TODO identifierTokenString
-            // The scope needs to have the decl in it.
-            try mod.analyzeExport(&decl_scope.base, export_src, name, decl);
-        }
-    }
-
-
     const error_set = try arena.create(Module.ErrorSet);
     error_set.* = .{
         .owner_decl = astgen.decl,
@@ -397,14 +184,6 @@ pub fn analyzeNamespace(
     };
 }
 
-        if (align_inst != .none) {
-            return mod.fail(&namespace.base, .{ .node_abs = decl_node }, "TODO: implement decls with align()", .{});
-        }
-        if (section_inst != .none) {
-            return mod.fail(&namespace.base, .{ .node_abs = decl_node }, "TODO: implement decls with linksection()", .{});
-        }
-
-
 /// Trailing:
 ///  0. `EmitH` if `module.emit_h != null`.
 ///  1. A per-Decl link object. Represents the position of the code in the output file.
@@ -442,25 +221,61 @@ pub fn analyzeNamespace(
     /// This memory is managed with gpa, must be freed when the function is freed.
     zir: Zir,
 
-pub fn root(sema: *Sema, root_block: *Scope.Block) !Zir.Inst.Index {
-    const inst_data = sema.code.instructions.items(.data)[0].pl_node;
-    const extra = sema.code.extraData(Zir.Inst.Block, inst_data.payload_index);
-    const root_body = sema.code.extra[extra.end..][0..extra.data.body_len];
-    return sema.analyzeBody(root_block, root_body);
-}
 
-pub fn rootAsRef(sema: *Sema, root_block: *Scope.Block) !Zir.Inst.Ref {
-    const break_inst = try sema.root(root_block);
-    return sema.code.instructions.items(.data)[break_inst].@"break".operand;
-}
+    if (fn_proto.lib_name) |lib_name_token| blk: {
+        log.debug("extern fn symbol expected in lib '{s}'", .{lib_name_str});
+        mod.comp.stage1AddLinkLib(lib_name_str) catch |err| {
+            return mod.failTok(
+                &fn_type_scope.base,
+                lib_name_token,
+                "unable to add link lib '{s}': {s}",
+                .{ lib_name_str, @errorName(err) },
+            );
+        };
+        const target = mod.comp.getTarget();
+        if (target_util.is_libc_lib_name(target, lib_name_str)) {
+            if (!mod.comp.bin_file.options.link_libc) {
+                return mod.failTok(
+                    &fn_type_scope.base,
+                    lib_name_token,
+                    "dependency on libc must be explicitly specified in the build command",
+                    .{},
+                );
+            }
+            break :blk;
+        }
+        if (target_util.is_libcpp_lib_name(target, lib_name_str)) {
+            if (!mod.comp.bin_file.options.link_libcpp) {
+                return mod.failTok(
+                    &fn_type_scope.base,
+                    lib_name_token,
+                    "dependency on libc++ must be explicitly specified in the build command",
+                    .{},
+                );
+            }
+            break :blk;
+        }
+        if (!target.isWasm() and !mod.comp.bin_file.options.pic) {
+            return mod.failTok(
+                &fn_type_scope.base,
+                lib_name_token,
+                "dependency on dynamic library '{s}' requires enabling Position Independent Code. Fixed by `-l{s}` or `-fPIC`.",
+                .{ lib_name_str, lib_name_str },
+            );
+        }
+    }
 
-/// Assumes that `root_block` ends with `break_inline`.
-pub fn rootAsType(sema: *Sema, root_block: *Scope.Block) !Type {
-    assert(root_block.is_comptime);
-    const zir_inst_ref = try sema.rootAsRef(root_block);
-    // Source location is unneeded because resolveConstValue must have already
-    // been successfully called when coercing the value to a type, from the
-    // result location.
-    return sema.resolveType(root_block, .unneeded, zir_inst_ref);
-}
+        const is_inline = decl_tv.ty.fnCallingConvention() == .Inline;
+        const anal_state: Fn.Analysis = if (is_inline) .inline_only else .queued;
+
+        new_func.* = .{
+            .state = anal_state,
+            .zir = fn_zir,
+            .body = undefined,
+            .owner_decl = decl,
+        };
+        fn_payload.* = .{
+            .base = .{ .tag = .function },
+            .data = new_func,
+        };