commit 0e416874dc6fb975b54c5fb049b5b830239e9161 (tree)
parent 676079fac22fc8f509b000ef9403683898f5d999
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Wed, 25 Feb 2026 19:09:47 +0000
sema: lazy module struct types with recursive import scanning
When DECL_VAL encounters a declaration whose value is an @import,
create a struct type IP entry for the imported module. Additionally,
scan the imported module's ZIR for its own imports and recursively
create struct types for those too. This matches the Zig compiler's
ensureFileAnalyzed → semaFile → createFileRootStruct → scanNamespace
sequence, where importing a file triggers analysis of that file which
discovers further imports.
The struct type creation is triggered lazily from the DECL_VAL handler
during analysis (not eagerly upfront), matching the Zig compiler's
demand-driven processing order.
For neghf2.zig (num_passing=4), the IP index gap shrinks from 862 to
607 as ~255 struct type entries are created for transitively imported
modules.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat:
| M | stage0/sema.c | | | 175 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
1 file changed, 169 insertions(+), 6 deletions(-)
diff --git a/stage0/sema.c b/stage0/sema.c
@@ -1735,7 +1735,8 @@ static FuncZirInfo parseFuncZir(Sema* sema, uint32_t inst) {
// Forward declaration (defined later, used by findDeclImportPath et al).
static uint32_t findDeclInstByNameInZir(const Zir* zir, const char* decl_name);
// Forward declaration (defined after loadStdImportZir).
-static InternPoolIndex ensureModuleStructType(const char* full_path);
+static InternPoolIndex ensureModuleStructType(
+ const char* full_path, const char* source_dir, const char* module_root);
// findDeclImportPath: given a declaration name index, check if the
// declaration's value body contains a ZIR_INST_IMPORT. If so, return
@@ -1860,7 +1861,9 @@ static Zir loadImportZirFromPath(const char* full_path, Ast* out_ast) {
// Lazily create a struct type for this module (matches the Zig
// compiler's ensureFileAnalyzed → createFileRootStruct flow).
- (void)ensureModuleStructType(full_path);
+ // Pass NULL for source_dir — recursive scanning is handled by
+ // the DECL_VAL handler's call to ensureModuleStructType.
+ (void)ensureModuleStructType(full_path, NULL, NULL);
// Parse.
*out_ast = astParse(src, (uint32_t)read_len);
@@ -2180,13 +2183,22 @@ static InternPoolIndex createModuleStructType(void) {
return ipIntern(s_module_ip, key);
}
+// Forward declaration — scans a ZIR for @import declarations and
+// recursively ensures struct types exist for imported modules.
+static void scanZirImportsRecursive(
+ const Zir* zir, const char* source_dir, const char* module_root);
+
// Lazily ensure a struct type IP entry exists for a module identified
// by its filesystem path. Called when an import is first resolved
// during semantic analysis, matching the Zig compiler's demand-driven
// ensureFileAnalyzed / createFileRootStruct sequence.
+// Also scans the module's ZIR for imports and recursively processes
+// them — matching the Zig compiler's scanNamespace which discovers
+// imports when a file is first analyzed.
// Returns the IP index of the struct type, or IP_INDEX_NONE if the
-// module table is full.
-static InternPoolIndex ensureModuleStructType(const char* full_path) {
+// module table is full or already tracked.
+static InternPoolIndex ensureModuleStructType(
+ const char* full_path, const char* source_dir, const char* module_root) {
if (!s_module_ip)
return IP_INDEX_NONE;
// Check if already tracked.
@@ -2199,7 +2211,123 @@ static InternPoolIndex ensureModuleStructType(const char* full_path) {
// Record module and create struct type.
LoadedModule* mod = &s_loaded_modules[s_num_loaded_modules++];
snprintf(mod->path, sizeof(mod->path), "%s", full_path);
- return createModuleStructType();
+ InternPoolIndex idx = createModuleStructType();
+
+ // Load the module's ZIR and scan for imports, matching the Zig
+ // compiler's scanNamespace which processes declarations and
+ // discovers transitively imported modules.
+ if (source_dir) {
+ Ast scan_ast;
+ memset(&scan_ast, 0, sizeof(scan_ast));
+ Zir scan_zir = loadImportZirFromPath(full_path, &scan_ast);
+ if (scan_zir.inst_len > 0) {
+ scanZirImportsRecursive(&scan_zir, source_dir, module_root);
+ zirDeinit(&scan_zir);
+ }
+ if (scan_ast.source != NULL)
+ astDeinit(&scan_ast);
+ }
+ return idx;
+}
+
+// Scan a ZIR module's declarations for @import instructions and
+// recursively ensure struct types exist for each imported module.
+// Matches the Zig compiler's scanNamespace which discovers imports.
+static void scanZirImportsRecursive(
+ const Zir* zir, const char* source_dir, const char* module_root) {
+ if (zir->inst_len == 0 || zir->inst_tags[0] != ZIR_INST_EXTENDED)
+ return;
+ if (zir->inst_datas[0].extended.opcode != ZIR_EXT_STRUCT_DECL)
+ return;
+
+ uint16_t small = zir->inst_datas[0].extended.small;
+ uint32_t operand = zir->inst_datas[0].extended.operand;
+ uint32_t extra_index = operand + 6;
+
+ uint32_t captures_len = 0;
+ if (small & (1 << 0)) {
+ captures_len = zir->extra[extra_index++];
+ }
+ if (small & (1 << 1))
+ extra_index++;
+ uint32_t decls_len = 0;
+ if (small & (1 << 2)) {
+ decls_len = zir->extra[extra_index++];
+ }
+ extra_index += captures_len * 2;
+ if (small & (1 << 3)) {
+ uint32_t backing_int_body_len = zir->extra[extra_index++];
+ extra_index += (backing_int_body_len == 0) ? 1 : backing_int_body_len;
+ }
+
+ for (uint32_t d = 0; d < decls_len; d++) {
+ uint32_t di_inst = zir->extra[extra_index + d];
+ if (zir->inst_tags[di_inst] != ZIR_INST_DECLARATION)
+ continue;
+ uint32_t payload = zir->inst_datas[di_inst].declaration.payload_index;
+ uint32_t flags_1 = zir->extra[payload + 5];
+ uint32_t id = (flags_1 >> 27) & 0x1F;
+ uint32_t di = payload + 6;
+ if (declIdHasName(id))
+ di++;
+ if (declIdHasLibName(id))
+ di++;
+ uint32_t type_body_len = 0;
+ if (declIdHasTypeBody(id)) {
+ type_body_len = zir->extra[di++];
+ }
+ uint32_t align_body_len = 0, linksection_body_len = 0;
+ uint32_t addrspace_body_len = 0;
+ if (declIdHasSpecialBodies(id)) {
+ align_body_len = zir->extra[di];
+ linksection_body_len = zir->extra[di + 1];
+ addrspace_body_len = zir->extra[di + 2];
+ di += 3;
+ }
+ uint32_t value_body_len = 0;
+ if (declIdHasValueBody(id)) {
+ value_body_len = zir->extra[di++];
+ }
+ di += type_body_len + align_body_len + linksection_body_len
+ + addrspace_body_len;
+
+ const uint32_t* value_body = &zir->extra[di];
+ for (uint32_t v = 0; v < value_body_len; v++) {
+ if (zir->inst_tags[value_body[v]] != ZIR_INST_IMPORT)
+ continue;
+ uint32_t pl = zir->inst_datas[value_body[v]].pl_tok.payload_index;
+ uint32_t path_idx = zir->extra[pl + 1];
+ const char* import_path
+ = (const char*)&zir->string_bytes[path_idx];
+ if (strcmp(import_path, "builtin") == 0)
+ continue;
+
+ // Resolve import path.
+ char import_full_path[1024];
+ const char* rel = import_path;
+ if (rel[0] == '.' && rel[1] == '/')
+ rel += 2;
+ snprintf(import_full_path, sizeof(import_full_path), "%s/%s",
+ source_dir, rel);
+ {
+ FILE* check = fopen(import_full_path, "r");
+ if (!check && module_root && import_path[0] != '.'
+ && import_path[0] != '/') {
+ snprintf(import_full_path, sizeof(import_full_path),
+ "%s/lib/%s/%s.zig", module_root, import_path,
+ import_path);
+ }
+ if (check)
+ fclose(check);
+ }
+
+ char import_source_dir[1024];
+ computeSourceDir(module_root, source_dir, import_path,
+ import_source_dir, sizeof(import_source_dir));
+ ensureModuleStructType(
+ import_full_path, import_source_dir, module_root);
+ }
+ }
}
// Reset module tracking state.
@@ -7931,12 +8059,47 @@ static bool analyzeBodyInner(
// decl_val / decl_ref: reference to a module-level declaration.
// Maps to void for now; the actual resolution happens in zirCall
// when the callee is a decl_val/decl_ref.
+ // When the declaration is an @import, lazily create a struct
+ // type for the imported module and recursively process its
+ // imports (matching Zig's ensureFileAnalyzed / scanNamespace).
case ZIR_INST_DECL_VAL:
- case ZIR_INST_DECL_REF:
+ case ZIR_INST_DECL_REF: {
+ uint32_t decl_name_idx = sema->code.inst_datas[inst].str_tok.start;
+ if (sema->source_dir && decl_name_idx != 0) {
+ const char* import_path
+ = findDeclImportPath(sema, decl_name_idx);
+ if (import_path && strcmp(import_path, "builtin") != 0) {
+ // Resolve import path.
+ char import_full[1024];
+ const char* rel = import_path;
+ if (rel[0] == '.' && rel[1] == '/')
+ rel += 2;
+ snprintf(import_full, sizeof(import_full), "%s/%s",
+ sema->source_dir, rel);
+ {
+ FILE* check = fopen(import_full, "r");
+ if (!check && sema->module_root
+ && import_path[0] != '.'
+ && import_path[0] != '/') {
+ snprintf(import_full, sizeof(import_full),
+ "%s/lib/%s/%s.zig", sema->module_root,
+ import_path, import_path);
+ }
+ if (check)
+ fclose(check);
+ }
+ char import_dir[1024];
+ computeSourceDir(sema->module_root, sema->source_dir,
+ import_path, import_dir, sizeof(import_dir));
+ ensureModuleStructType(
+ import_full, import_dir, sema->module_root);
+ }
+ }
instMapPut(
&sema->inst_map, inst, AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE));
i++;
continue;
+ }
// call / field_call: function call.
// Handles inline function calls from the same module.