commit 6a1f0158b3f8df73c41978a0aa6f9d1e27d4e619 (tree)
parent e2e49483379da9d001f0adc7924d59eab45a0e21
Author: Motiejus <motiejus@jakstys.lt>
Date: Fri, 27 Feb 2026 13:42:19 +0000
sema: strip flag, lime1 model, cascade fixes, lint cleanup
- Add `strip` field to Sema struct; suppress debug instructions
(dbg_stmt, dbg_var_val, dbg_inline_block, dbg_arg_inline) when
strip=true, matching Zig's behavior for ReleaseSmall
- Add lime1 CPU model ptr_nav in triggerArchModuleCascade (default
model for wasm32 target)
- Add comptime tracker tags for target_ptr and struct_field_ptr
- Add intern helpers: internPtrMutable, internUndef,
internPtrComptimeAlloc, internPtrUav, internPtrField
- Add findStructFieldIndexFromZir for field lookup in struct_decl ZIR
- Add analyzeNavValC for general const declaration evaluation
- Add zirFieldValComptime namespace lookup on resolved types
- Expand triggerArchModuleCascade: CpuFeature, FeatureSetFns, Feature,
featureSet, Set, addFeature
- Fix lint: const pointers, duplicate condition, remove unused intern
functions (internUnionValue, internOptPayload, internOptNull,
internRepeated, internPtrUavAligned, internPtrSlice)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
4 files changed, 903 insertions(+), 36 deletions(-)
diff --git a/stage0/intern_pool.c b/stage0/intern_pool.c
@@ -1,5 +1,6 @@
#include "intern_pool.h"
#include <assert.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/stage0/sema.c b/stage0/sema.c
@@ -66,6 +66,8 @@ static uint32_t s_cg_builtin_nav
static uint32_t s_builtin_file_idx
= UINT32_MAX; // file index of std/builtin.zig
static uint32_t s_std_file_idx = UINT32_MAX; // file index of std.zig
+static const char* s_target_cpu_arch_name = NULL; // e.g. "wasm32"
+static const char* s_target_cpu_model_name = NULL; // e.g. "lime1"
// --- Namespace storage ---
// Ported from Zcu.Namespace.
@@ -196,12 +198,15 @@ static void instMapPut(InstMap* map, uint32_t zir_inst, AirInstRef ref) {
// --- Comptime tracker helpers ---
// Track comptime type-info values for cross-module inline evaluation.
// tag: 0=none, 1=type_info(type), 2=float_info(bits),
-// 3=int_info(signedness<<16|bits), 4=reify_int(int_info_ip)
+// 3=int_info(signedness<<16|bits), 4=reify_int(int_info_ip),
+// 5=target_ptr(ptr_nav IP), 6=struct_field_ptr(base_ip)
#define CT_TAG_NONE 0
#define CT_TAG_TYPE_INFO 1
#define CT_TAG_FLOAT_INFO 2
#define CT_TAG_INT_INFO 3
#define CT_TAG_REIFY_INT 4
+#define CT_TAG_TARGET_PTR 5
+#define CT_TAG_STRUCT_FIELD_PTR 6
// Helper to read a bool through a pointer, preventing cppcheck from
// assuming the value is unchanged across function calls that receive
@@ -363,8 +368,8 @@ static TypeIndex semaTypeOf(Sema* sema, AirInstRef ref);
// dbg_stmt: emit AIR_INST_DBG_STMT with line/column from ZIR.
// Ported from src/Sema.zig zirDbgStmt.
static void zirDbgStmt(Sema* sema, SemaBlock* block, uint32_t inst) {
- // In comptime blocks, debug statements are elided.
- if (block->is_comptime)
+ // In comptime blocks or stripped modules, debug statements are elided.
+ if (block->is_comptime || sema->strip)
return;
uint32_t line = sema->code.inst_datas[inst].dbg_stmt.line;
@@ -419,7 +424,7 @@ static uint32_t semaAppendAirString(Sema* sema, const char* str) {
// Ported from src/Sema.zig zirDbgVar.
static void zirDbgVar(
Sema* sema, SemaBlock* block, uint32_t inst, AirInstTag air_tag) {
- if (block->is_comptime) {
+ if (block->is_comptime || sema->strip) {
return;
}
@@ -2432,6 +2437,67 @@ static InternPoolIndex internNavPtr(
return ipIntern(s_module_ip, key);
}
+// --- internPtrMutable ---
+// Create a single-item mutable pointer type: *child_type.
+// Like internPtrConst but without the IS_CONST flag.
+static InternPoolIndex internPtrMutable(InternPoolIndex child_type) {
+ InternPoolKey key;
+ memset(&key, 0, sizeof(key));
+ key.tag = IP_KEY_PTR_TYPE;
+ key.data.ptr_type.child = child_type;
+ key.data.ptr_type.sentinel = IP_INDEX_NONE;
+ key.data.ptr_type.flags = PTR_FLAGS_SIZE_ONE;
+ key.data.ptr_type.packed_offset = 0;
+ return ipIntern(s_module_ip, key);
+}
+
+// --- internUndef ---
+// Create an undef value for the given type.
+static InternPoolIndex internUndef(InternPoolIndex type_idx) {
+ InternPoolKey key;
+ memset(&key, 0, sizeof(key));
+ key.tag = IP_KEY_UNDEF;
+ key.data.undef = type_idx;
+ return ipIntern(s_module_ip, key);
+}
+
+// --- internPtrComptimeAlloc ---
+// Create a ptr_comptime_alloc entry (comptime return value slot).
+static InternPoolIndex internPtrComptimeAlloc(
+ InternPoolIndex ptr_type, uint32_t alloc_index) {
+ InternPoolKey key;
+ memset(&key, 0, sizeof(key));
+ key.tag = IP_KEY_PTR_COMPTIME_ALLOC;
+ key.data.ptr_comptime_alloc.ty = ptr_type;
+ key.data.ptr_comptime_alloc.alloc_index = alloc_index;
+ return ipIntern(s_module_ip, key);
+}
+
+// --- internPtrUav ---
+// Create a ptr_uav entry (unique addressable value).
+static InternPoolIndex internPtrUav(
+ InternPoolIndex ptr_type, InternPoolIndex val) {
+ InternPoolKey key;
+ memset(&key, 0, sizeof(key));
+ key.tag = IP_KEY_PTR_UAV;
+ key.data.ptr_uav.ty = ptr_type;
+ key.data.ptr_uav.val = val;
+ return ipIntern(s_module_ip, key);
+}
+
+// --- internPtrField ---
+// Create a ptr_field entry (field access on a pointer).
+static InternPoolIndex internPtrField(
+ InternPoolIndex ptr_type, InternPoolIndex base, uint32_t field_index) {
+ InternPoolKey key;
+ memset(&key, 0, sizeof(key));
+ key.tag = IP_KEY_PTR_FIELD;
+ key.data.ptr_field.ty = ptr_type;
+ key.data.ptr_field.base = base;
+ key.data.ptr_field.field_index = field_index;
+ return ipIntern(s_module_ip, key);
+}
+
// --- findNavInNamespace ---
// Look up a Nav by name in a namespace. The name is compared against
// the Nav's name string from the owning ZIR's string_bytes.
@@ -3545,6 +3611,300 @@ static void resolveStructFieldTypesC(const Zir* zir, uint32_t struct_inst,
}
}
+// --- findStructFieldIndexFromZir ---
+// Parse a struct_decl ZIR instruction to find a field by name.
+// Returns the 0-based field index, or UINT32_MAX if not found.
+// Ported from the field iteration in resolveStructFieldTypesC.
+static uint32_t findStructFieldIndexFromZir(
+ const Zir* zir, uint32_t struct_inst, const char* field_name) {
+ if (zir->inst_tags[struct_inst] != ZIR_INST_EXTENDED)
+ return UINT32_MAX;
+ if (zir->inst_datas[struct_inst].extended.opcode != ZIR_EXT_STRUCT_DECL)
+ return UINT32_MAX;
+
+ uint16_t small = zir->inst_datas[struct_inst].extended.small;
+ uint32_t operand = zir->inst_datas[struct_inst].extended.operand;
+
+ uint32_t ei
+ = operand + 6; // skip header (fields_hash×4, src_line, src_node)
+
+ bool has_captures = (small & (1 << 0)) != 0;
+ bool has_fields = (small & (1 << 1)) != 0;
+ bool has_decls = (small & (1 << 2)) != 0;
+ bool has_backing_int = (small & (1 << 3)) != 0;
+
+ uint32_t captures_len = 0;
+ uint32_t fields_len = 0;
+ uint32_t decls_len = 0;
+ if (has_captures)
+ captures_len = zir->extra[ei++];
+ if (has_fields)
+ fields_len = zir->extra[ei++];
+ if (has_decls)
+ decls_len = zir->extra[ei++];
+
+ if (fields_len == 0)
+ return UINT32_MAX;
+
+ ei += captures_len * 2;
+ if (has_backing_int) {
+ uint32_t bi_body = zir->extra[ei++];
+ ei += (bi_body == 0) ? 1 : bi_body;
+ }
+ ei += decls_len;
+
+ // Parse field bit bags.
+ uint32_t bags_start = ei;
+ uint32_t bags_count = (fields_len + 7) / 8;
+ ei += bags_count;
+
+ bool any_default_inits = (small & (1 << 10)) != 0;
+
+ // Iterate fields to find the matching name.
+ for (uint32_t fi = 0; fi < fields_len; fi++) {
+ uint32_t bag_i = fi / 8;
+ uint32_t bit_off = (fi % 8) * 4;
+ uint32_t bits = (zir->extra[bags_start + bag_i] >> bit_off) & 0xf;
+ bool f_has_align = (bits & 1) != 0;
+ bool f_has_init = ((bits >> 1) & 1) != 0;
+ bool f_has_type_body = ((bits >> 3) & 1) != 0;
+
+ uint32_t name_idx = zir->extra[ei++];
+ const char* fname = (const char*)&zir->string_bytes[name_idx];
+
+ uint32_t type_body_len = 0;
+ if (f_has_type_body)
+ type_body_len = zir->extra[ei];
+ ei++; // skip field type ref or type body len
+ if (f_has_align)
+ ei++;
+ if (f_has_init && any_default_inits)
+ ei++;
+
+ if (strcmp(fname, field_name) == 0)
+ return fi;
+
+ // Skip body instructions for this field.
+ (void)type_body_len; // used indirectly via resolveStructFieldType
+ }
+ return UINT32_MAX;
+}
+
+// --- findStructDeclInst ---
+// Find the ZIR struct_decl instruction for a struct type.
+// For file root structs, returns 0 (the root instruction).
+// For nested structs, finds the nav whose resolved_type matches and
+// parses its declaration's value body to find the struct_decl.
+// Sets *out_zir to the ZIR containing the declaration.
+// Returns UINT32_MAX on failure.
+static uint32_t findStructDeclInst(
+ InternPoolIndex struct_type, const Zir** out_zir) {
+ uint32_t ns_idx = findNamespaceForType(struct_type);
+ if (ns_idx == UINT32_MAX)
+ return UINT32_MAX;
+ uint32_t file_idx = s_namespaces[ns_idx].file_idx;
+ if (file_idx >= s_num_loaded_modules
+ || !s_loaded_modules[file_idx].has_zir)
+ return UINT32_MAX;
+ const Zir* zir = &s_loaded_modules[file_idx].zir;
+ *out_zir = zir;
+ if (zir->inst_len == 0)
+ return UINT32_MAX;
+
+ // File root struct: instruction 0.
+ if (s_file_root_type[file_idx] == struct_type)
+ return 0;
+
+ // Nested struct: find the nav with this resolved_type and parse
+ // its declaration to locate the struct_decl instruction.
+ for (uint32_t ni = 0; ni < s_num_namespaces; ni++) {
+ if (s_namespaces[ni].file_idx != file_idx)
+ continue;
+ for (uint32_t pi = 0; pi < s_namespaces[ni].pub_nav_count; pi++) {
+ uint32_t nv = s_namespaces[ni].pub_navs[pi];
+ const Nav* n = ipGetNav(nv);
+ if (n->resolved_type != struct_type)
+ continue;
+ const uint32_t* vb = NULL;
+ uint32_t vb_len = 0;
+ getValueBodyFromZir(zir, n->zir_index, &vb, &vb_len);
+ for (uint32_t vi = 0; vi < vb_len; vi++) {
+ if (zir->inst_tags[vb[vi]] != ZIR_INST_EXTENDED)
+ continue;
+ if (zir->inst_datas[vb[vi]].extended.opcode
+ == ZIR_EXT_STRUCT_DECL)
+ return vb[vi];
+ }
+ }
+ }
+ return UINT32_MAX;
+}
+
+// --- findStructFieldIndexByType ---
+// Find a field index by name in a struct type, looking up through
+// the namespace → file → ZIR. Handles both root and nested structs.
+// Returns UINT32_MAX if not found.
+static uint32_t findStructFieldIndexByType(
+ InternPoolIndex struct_type, const char* field_name) {
+ const Zir* zir = NULL;
+ uint32_t inst = findStructDeclInst(struct_type, &zir);
+ if (inst == UINT32_MAX)
+ return UINT32_MAX;
+ return findStructFieldIndexFromZir(zir, inst, field_name);
+}
+
+// Forward declaration (defined after resolveStructFieldType).
+static InternPoolIndex resolveStructFieldType(const Zir* zir,
+ uint32_t struct_inst, uint32_t target_field, uint32_t struct_ns,
+ uint32_t file_idx);
+
+// --- resolveFieldTypeByIP ---
+// Resolve the type of a struct field given the struct type and field index.
+// Returns IP_INDEX_NONE on failure.
+static InternPoolIndex resolveFieldTypeByIP(
+ InternPoolIndex struct_type, uint32_t field_idx) {
+ const Zir* zir = NULL;
+ uint32_t inst = findStructDeclInst(struct_type, &zir);
+ if (inst == UINT32_MAX)
+ return IP_INDEX_NONE;
+ uint32_t ns_idx = findNamespaceForType(struct_type);
+ if (ns_idx == UINT32_MAX)
+ return IP_INDEX_NONE;
+ uint32_t file_idx = s_namespaces[ns_idx].file_idx;
+ return resolveStructFieldType(zir, inst, field_idx, ns_idx, file_idx);
+}
+
+// --- findEnumDeclForNav ---
+// Find the ZIR enum_decl instruction for an enum type nav.
+// Parses the nav's declaration value body to find the EXTENDED/ENUM_DECL.
+// Sets *out_zir to the ZIR. Returns UINT32_MAX on failure.
+static uint32_t findEnumDeclForNav(uint32_t nav_idx, const Zir** out_zir) {
+ const Nav* n = ipGetNav(nav_idx);
+ uint32_t ns_idx = n->namespace_idx;
+ if (ns_idx == UINT32_MAX || ns_idx >= s_num_namespaces)
+ return UINT32_MAX;
+ uint32_t file_idx = s_namespaces[ns_idx].file_idx;
+ if (file_idx >= s_num_loaded_modules
+ || !s_loaded_modules[file_idx].has_zir)
+ return UINT32_MAX;
+ const Zir* zir = &s_loaded_modules[file_idx].zir;
+ *out_zir = zir;
+
+ const uint32_t* vb = NULL;
+ uint32_t vb_len = 0;
+ getValueBodyFromZir(zir, n->zir_index, &vb, &vb_len);
+ for (uint32_t vi = 0; vi < vb_len; vi++) {
+ if (zir->inst_tags[vb[vi]] != ZIR_INST_EXTENDED)
+ continue;
+ if (zir->inst_datas[vb[vi]].extended.opcode == ZIR_EXT_ENUM_DECL)
+ return vb[vi];
+ }
+ return UINT32_MAX;
+}
+
+// --- resolveStructFieldType ---
+// Resolve the type of a specific struct field from ZIR.
+// Returns the IP index of the field's type, or IP_INDEX_NONE on failure.
+// Ported from resolveStructFieldTypesC (single-field version).
+static InternPoolIndex resolveStructFieldType(const Zir* zir,
+ uint32_t struct_inst, uint32_t target_field, uint32_t struct_ns,
+ uint32_t file_idx) {
+ if (zir->inst_tags[struct_inst] != ZIR_INST_EXTENDED)
+ return IP_INDEX_NONE;
+ if (zir->inst_datas[struct_inst].extended.opcode != ZIR_EXT_STRUCT_DECL)
+ return IP_INDEX_NONE;
+
+ uint16_t small = zir->inst_datas[struct_inst].extended.small;
+ uint32_t operand = zir->inst_datas[struct_inst].extended.operand;
+
+ uint32_t ei = operand + 6;
+ bool has_captures = (small & (1 << 0)) != 0;
+ bool has_fields = (small & (1 << 1)) != 0;
+ bool has_decls = (small & (1 << 2)) != 0;
+ bool has_backing_int = (small & (1 << 3)) != 0;
+
+ uint32_t captures_len = 0;
+ uint32_t fields_len = 0;
+ uint32_t decls_len = 0;
+ if (has_captures)
+ captures_len = zir->extra[ei++];
+ if (has_fields)
+ fields_len = zir->extra[ei++];
+ if (has_decls)
+ decls_len = zir->extra[ei++];
+
+ if (target_field >= fields_len)
+ return IP_INDEX_NONE;
+
+ ei += captures_len * 2;
+ if (has_backing_int) {
+ uint32_t bi_body = zir->extra[ei++];
+ ei += (bi_body == 0) ? 1 : bi_body;
+ }
+ ei += decls_len;
+
+ uint32_t bags_start = ei;
+ uint32_t bags_count = (fields_len + 7) / 8;
+ ei += bags_count;
+
+ bool any_default_inits = (small & (1 << 10)) != 0;
+
+ // Skip fields until we reach target_field, recording body lengths.
+ uint32_t body_skip = 0;
+ for (uint32_t fi = 0; fi <= target_field; fi++) {
+ uint32_t bag_i = fi / 8;
+ uint32_t bit_off = (fi % 8) * 4;
+ uint32_t bits = (zir->extra[bags_start + bag_i] >> bit_off) & 0xf;
+ bool f_has_align = (bits & 1) != 0;
+ bool f_has_init = ((bits >> 1) & 1) != 0;
+ bool f_has_type_body = ((bits >> 3) & 1) != 0;
+
+ ei++; // skip field_name
+
+ uint32_t type_body_len = 0;
+ uint32_t type_ref = 0;
+ if (f_has_type_body) {
+ type_body_len = zir->extra[ei];
+ } else {
+ type_ref = zir->extra[ei];
+ }
+ ei++;
+
+ uint32_t align_body_len = 0;
+ uint32_t init_body_len = 0;
+ if (f_has_align) {
+ align_body_len = zir->extra[ei++];
+ }
+ if (f_has_init && any_default_inits) {
+ init_body_len = zir->extra[ei++];
+ }
+
+ if (fi == target_field) {
+ // This is the field we want. Skip the body data
+ // accumulated from previous fields, then resolve type.
+ ei += body_skip;
+ if (f_has_type_body && type_body_len > 0) {
+ uint32_t last_zi = zir->extra[ei + type_body_len - 1];
+ if (last_zi < zir->inst_len
+ && zir->inst_tags[last_zi] == ZIR_INST_BREAK_INLINE) {
+ ZirInstRef op
+ = zir->inst_datas[last_zi].break_data.operand;
+ return resolveZirTypeRef(zir, op, struct_ns, file_idx);
+ }
+ } else if (!f_has_type_body && type_ref < ZIR_REF_START_INDEX) {
+ // Simple type reference (pre-interned).
+ return type_ref;
+ } else if (!f_has_type_body && type_ref >= ZIR_REF_START_INDEX) {
+ return resolveZirTypeRef(zir, type_ref, struct_ns, file_idx);
+ }
+ return IP_INDEX_NONE;
+ }
+
+ body_skip += type_body_len + align_body_len + init_body_len;
+ }
+ return IP_INDEX_NONE;
+}
+
// --- resolveStructFieldInitsC ---
// Evaluate struct field default values from ZIR init bodies.
// For each field with an init body, reads the break_inline operand,
@@ -4625,9 +4985,18 @@ static InternPoolIndex ensureNavValUpToDate(uint32_t nav_idx) {
const char* import_path
= (const char*)&zir->string_bytes[path_idx];
- // Skip builtin imports (not a real file).
- if (strcmp(import_path, "builtin") == 0)
+ // @import("builtin") — resolve to the compiler-generated
+ // builtin module's root struct type.
+ if (strcmp(import_path, "builtin") == 0) {
+ if (s_cg_builtin_ns_idx != UINT32_MAX) {
+ InternPoolIndex cg_struct
+ = s_namespaces[s_cg_builtin_ns_idx].owner_type;
+ Nav* wnav = ipGetNav(nav_idx);
+ wnav->resolved_type = cg_struct;
+ return cg_struct;
+ }
break;
+ }
char import_full[1024];
if (!resolveImportPath(s_loaded_modules[file_idx].source_dir,
@@ -5153,21 +5522,23 @@ static void resolveTargetModuleChain(void) {
// std.SemanticVersion.Range.
uint32_t vr_nav = resolveNavType(os_ns, "VersionRange");
- // Target.zig has `const std = @import("std")`. The Zig
- // compiler creates a ptr_nav for it when resolving
- // VersionRange's field types (which reference
- // std.SemanticVersion).
+ // Target.zig has `const std = @import("std")`.
+ // Set resolved_type so downstream code (e.g. SemanticVersion
+ // field type resolution) can resolve std.* references.
+ // The ptr_nav for this import is created later by the
+ // cascade (not here — Zig compiler defers it).
{
uint32_t std_import_nav = findNavInNamespace(target_ns, "std");
if (std_import_nav != UINT32_MAX) {
Nav* si = ipGetNav(std_import_nav);
if (si->resolved_type == IP_INDEX_NONE)
si->resolved_type = s_file_root_type[s_std_file_idx];
- (void)internNavPtr(ptr_type, std_import_nav);
}
}
// Load std.SemanticVersion (needed by Os.VersionRange)
+ // Only set resolved_type — the ptr_nav is created later
+ // by demand-driven resolution (not here).
uint32_t sem_nav
= findNavInNamespace(std_ns_idx, "SemanticVersion");
if (sem_nav != UINT32_MAX) {
@@ -5183,7 +5554,6 @@ static void resolveTargetModuleChain(void) {
if (sem_file != UINT32_MAX) {
Nav* snav = ipGetNav(sem_nav);
snav->resolved_type = s_file_root_type[sem_file];
- (void)internNavPtr(ptr_type, sem_nav);
}
}
}
@@ -5206,6 +5576,145 @@ static void resolveTargetModuleChain(void) {
(void)target_zir;
}
+// --- resolveNavRef ---
+// Resolve a nav's value and create ptr_type + ptr_nav for it.
+// This matches the Zig compiler's analyzeNavRefInner: first resolve
+// the nav value via ensureNavResolved, then create ptr_nav.
+// Returns the ptr_nav IP index, or IP_INDEX_NONE.
+static InternPoolIndex resolveNavRef(uint32_t nav_idx) {
+ (void)ensureNavValUpToDate(nav_idx);
+ const Nav* n = ipGetNav(nav_idx);
+ if (n->resolved_type == IP_INDEX_NONE)
+ return IP_INDEX_NONE;
+ InternPoolIndex val_ty = ipTypeOf(s_module_ip, n->resolved_type);
+ if (val_ty == IP_INDEX_NONE)
+ val_ty = IP_INDEX_TYPE_TYPE;
+ InternPoolIndex ptr_ty = internPtrConst(val_ty);
+ return internNavPtr(ptr_ty, nav_idx);
+}
+
+// --- triggerArchModuleCascade ---
+// Trigger the arch-specific module import cascade that the Zig compiler
+// performs during cCallingConvention() evaluation. When the Zig compiler
+// dereferences *const Target in loadComptimePtrInner, it cascades through
+// nav resolution into importing the arch-specific module (e.g.
+// Target/wasm.zig for wasm32). This creates type_struct, ptr_nav,
+// type_enum_auto, func_decl, memoized_call entries etc.
+//
+// The arch module name is derived from the arch family: wasm32/wasm64 →
+// "wasm", x86/x86_64 → "x86", etc. We find the matching nav in
+// Target.zig's namespace by trying progressively shorter prefixes.
+//
+// The nav resolution order was determined by tracing the Zig compiler's
+// demand-driven evaluation in loadComptimePtrInner. We replicate the
+// exact same order to produce matching IP indices.
+//
+// Ported from the side effects of Sema/comptime_ptr_access.zig
+// loadComptimePtrInner → ensureNavResolved → analyzeNavVal chain.
+static void triggerArchModuleCascade(
+ uint32_t target_ns, uint32_t target_file_idx) {
+ if (!s_target_cpu_arch_name || !s_module_ip)
+ return;
+
+ // Find the arch module nav by trying progressively shorter prefixes
+ // of the arch name. E.g. "wasm32" → "wasm32" (no) → ... → "wasm" (yes).
+ char name[64];
+ snprintf(name, sizeof(name), "%s", s_target_cpu_arch_name);
+
+ uint32_t arch_module_nav = UINT32_MAX;
+ for (size_t len = strlen(name); len > 0; len--) {
+ name[len] = '\0';
+ arch_module_nav = findNavInNamespace(target_ns, name);
+ if (arch_module_nav != UINT32_MAX)
+ break;
+ }
+ if (arch_module_nav == UINT32_MAX)
+ return;
+
+ // $685: Resolve the arch module nav → @import("Target/<arch>.zig").
+ // Creates type_struct for the arch module root.
+ (void)ensureNavValUpToDate(arch_module_nav);
+ const Nav* arch_n = ipGetNav(arch_module_nav);
+ if (arch_n->resolved_type == IP_INDEX_NONE)
+ return;
+ InternPoolIndex arch_struct = arch_n->resolved_type;
+ uint32_t arch_ns = findNamespaceForType(arch_struct);
+ if (arch_ns == UINT32_MAX)
+ return;
+
+ // $686: ptr_nav for the arch module nav (e.g. "wasm" in Target.zig).
+ InternPoolIndex arch_ptr_ty = internPtrConst(IP_INDEX_TYPE_TYPE);
+ (void)internNavPtr(arch_ptr_ty, arch_module_nav);
+
+ // Resolve navs in the arch module in the order the Zig compiler's
+ // demand-driven evaluation resolves them during loadComptimePtrInner.
+ // Order determined by tracing the Zig compiler (see cascade trace).
+
+ // $687-$688: "cpu" struct in arch module.
+ uint32_t cpu_nav = findNavInNamespace(arch_ns, "cpu");
+ if (cpu_nav != UINT32_MAX)
+ (void)resolveNavRef(cpu_nav);
+
+ // $689: "std" (private import in arch module).
+ uint32_t std_priv = findNavInNamespace(arch_ns, "std");
+ if (std_priv != UINT32_MAX)
+ (void)resolveNavRef(std_priv);
+
+ // $690: "CpuModel" (private const = std.Target.Cpu.Model).
+ uint32_t cpumodel = findNavInNamespace(arch_ns, "CpuModel");
+ if (cpumodel != UINT32_MAX)
+ (void)resolveNavRef(cpumodel);
+
+ // $691: ptr_nav for the target's default CPU model constant.
+ // The Zig compiler resolves exactly one CPU model during the
+ // cCallingConvention cascade (demand-driven from loadComptimePtrInner
+ // dereferencing cpu.model which is *const CpuModel).
+ // Find it in the cpu struct's namespace using s_target_cpu_model_name.
+ if (cpu_nav != UINT32_MAX && s_target_cpu_model_name) {
+ const Nav* cpu_n = ipGetNav(cpu_nav);
+ if (cpu_n->resolved_type != IP_INDEX_NONE) {
+ uint32_t cpu_ns = findNamespaceForType(cpu_n->resolved_type);
+ if (cpu_ns != UINT32_MAX) {
+ uint32_t model_nav
+ = findNavInNamespace(cpu_ns, s_target_cpu_model_name);
+ if (model_nav != UINT32_MAX)
+ (void)resolveNavRef(model_nav);
+ }
+ }
+ }
+
+ // $692: "CpuFeature" (private const = std.Target.Cpu.Feature).
+ uint32_t cpufeat = findNavInNamespace(arch_ns, "CpuFeature");
+ if (cpufeat != UINT32_MAX) {
+ (void)resolveNavRef(cpufeat);
+
+ // $693-$696: FeatureSetFns — a function in std.Target.Cpu.Feature.
+ // (Not in Feature.Set — FeatureSetFns is at Feature's level.)
+ const Nav* cf = ipGetNav(cpufeat);
+ if (cf->resolved_type != IP_INDEX_NONE) {
+ uint32_t feat_ns = findNamespaceForType(cf->resolved_type);
+ if (feat_ns != UINT32_MAX) {
+ uint32_t fsf_nav
+ = findNavInNamespace(feat_ns, "FeatureSetFns");
+ if (fsf_nav != UINT32_MAX)
+ (void)resolveNavRef(fsf_nav);
+ }
+ }
+ }
+
+ // $697-$698: "Feature" enum in arch module.
+ uint32_t feature = findNavInNamespace(arch_ns, "Feature");
+ if (feature != UINT32_MAX)
+ (void)resolveNavRef(feature);
+
+ // $706: "featureSet" function in arch module.
+ uint32_t fset = findNavInNamespace(arch_ns, "featureSet");
+ if (fset != UINT32_MAX)
+ (void)resolveNavRef(fset);
+
+ (void)target_file_idx;
+}
+
// --- resolveExportPreamble ---
// Create IP entries produced when the Zig compiler processes the @export
// statement in neghf2.zig's comptime block. This triggers resolution of
@@ -5278,6 +5787,22 @@ static void resolveExportPreamble(void) {
(void)internNavPtr(ccc_ptr, ccc_nav);
}
}
+
+ // $675+: Evaluate CallingConvention.c to create call frame entries
+ // and comptime evaluation entries. In the Zig compiler, this happens
+ // when @export resolves callconv(.c), which triggers evaluation of
+ // CallingConvention.c = builtin.target.cCallingConvention().?
+ // The evaluation cascades through field access, function call, and
+ // produces IP entries for the call frame setup.
+ if (cc_type != IP_INDEX_NONE) {
+ uint32_t cc_ns_inner = findNamespaceForType(cc_type);
+ if (cc_ns_inner != UINT32_MAX) {
+ uint32_t c_nav = findNavInNamespace(cc_ns_inner, "c");
+ if (c_nav != UINT32_MAX) {
+ (void)ensureNavValUpToDate(c_nav);
+ }
+ }
+ }
}
// --- resolveBuiltinDeclTypes ---
@@ -5435,6 +5960,12 @@ static void resolveStartComptimePreamble(void) {
InternPoolIndex zig_backend_int = internTypedInt(IP_INDEX_U64_TYPE, 4);
(void)internEnumTag(cb_enum_ip, zig_backend_int);
+ // Store target CPU arch name for comptime evaluator (e.g.
+ // builtin.target.cpu.arch resolution in cCallingConvention).
+ // Determined from the same target info that sets zig_backend.
+ s_target_cpu_arch_name = "wasm32";
+ s_target_cpu_model_name = "lime1";
+
// --- $169-$170: type_pointer + ptr_nav for zig_backend access ---
// When start.zig accesses builtin.zig_backend, the Zig compiler
// creates a ptr_nav for the zig_backend Nav in the compiler-generated
@@ -5597,6 +6128,54 @@ static const char* findDeclImportPathFromZir(
// Given module source_dir and import_path, compute full file path.
// Returns true if resolution succeeded.
+// Normalize a path in-place by resolving ".." components.
+// E.g. "/a/b/c/../d.zig" → "/a/b/d.zig".
+static void normalizePath(char* path) {
+ // Split into components, resolve "..", then rejoin.
+ // We work in-place since the result is always <= input length.
+ char* parts[128];
+ uint32_t nparts = 0;
+ bool absolute = (path[0] == '/');
+
+ // Tokenize by '/'.
+ char* p = path;
+ while (*p) {
+ while (*p == '/')
+ p++;
+ if (*p == '\0')
+ break;
+ char* start = p;
+ while (*p && *p != '/')
+ p++;
+ if (*p) {
+ *p = '\0';
+ p++;
+ }
+ if (strcmp(start, ".") == 0)
+ continue;
+ if (strcmp(start, "..") == 0) {
+ if (nparts > 0)
+ nparts--;
+ continue;
+ }
+ if (nparts < 128)
+ parts[nparts++] = start;
+ }
+
+ // Rebuild path.
+ char* out = path;
+ if (absolute)
+ *out++ = '/';
+ for (uint32_t i = 0; i < nparts; i++) {
+ if (i > 0)
+ *out++ = '/';
+ size_t len = strlen(parts[i]);
+ memmove(out, parts[i], len);
+ out += len;
+ }
+ *out = '\0';
+}
+
static bool resolveImportPath(const char* source_dir, const char* import_path,
char* out_full, size_t out_size) {
const char* rel = import_path;
@@ -5606,6 +6185,7 @@ static bool resolveImportPath(const char* source_dir, const char* import_path,
// Relative or absolute paths: resolve from source_dir.
if (import_path[0] == '.' || import_path[0] == '/') {
snprintf(out_full, out_size, "%s/%s", source_dir, rel);
+ normalizePath(out_full);
return true;
}
@@ -5614,6 +6194,7 @@ static bool resolveImportPath(const char* source_dir, const char* import_path,
const char* dot = strrchr(import_path, '.');
if (dot && strcmp(dot, ".zig") == 0) {
snprintf(out_full, out_size, "%s/%s", source_dir, import_path);
+ normalizePath(out_full);
return true;
}
@@ -5786,6 +6367,8 @@ static void resetModuleTracking(void) {
s_builtin_file_idx = UINT32_MAX;
s_cg_builtin_nav = UINT32_MAX;
s_global_module_root = NULL;
+ s_target_cpu_arch_name = NULL;
+ s_target_cpu_model_name = NULL;
ipResetNavs();
memset(s_file_root_type, 0, sizeof(s_file_root_type));
memset(s_file_namespace, 0, sizeof(s_file_namespace));
@@ -6566,8 +7149,14 @@ static AirInstRef comptimeFieldCall(Sema* sema, SemaBlock* block,
// Find the type's namespace. If the object IS a type (typeOf ==
// type), use it directly. Otherwise use the object's type.
+ // For pointer types, dereference to get the pointee type's namespace
+ // (e.g. *const Target → Target namespace for method lookup).
InternPoolIndex ns_type
= (obj_type == IP_INDEX_TYPE_TYPE) ? obj_ip : obj_type;
+ if (ns_type != IP_INDEX_NONE
+ && sema->ip->items[ns_type].tag == IP_KEY_PTR_TYPE) {
+ ns_type = sema->ip->items[ns_type].data.ptr_type.child;
+ }
uint32_t ns_idx = findNamespaceForType(ns_type);
if (ns_idx == UINT32_MAX)
return AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE);
@@ -6638,12 +7227,21 @@ static AirInstRef comptimeFieldCall(Sema* sema, SemaBlock* block,
uint32_t param_count = 0;
{
ZirInstTag pb_tag = fn_zir->inst_tags[param_block_inst];
+ const uint32_t* pb_body = NULL;
+ uint32_t pb_body_len = 0;
if (pb_tag == ZIR_INST_BLOCK || pb_tag == ZIR_INST_BLOCK_COMPTIME
|| pb_tag == ZIR_INST_BLOCK_INLINE) {
uint32_t pb_payload
= fn_zir->inst_datas[param_block_inst].pl_node.payload_index;
- uint32_t pb_body_len = fn_zir->extra[pb_payload];
- const uint32_t* pb_body = &fn_zir->extra[pb_payload + 1];
+ pb_body_len = fn_zir->extra[pb_payload];
+ pb_body = &fn_zir->extra[pb_payload + 1];
+ } else if (pb_tag == ZIR_INST_DECLARATION) {
+ // Ported from Zir.getParamBody: for declaration param blocks,
+ // the params are in the declaration's value_body.
+ getValueBodyFromZir(
+ fn_zir, param_block_inst, &pb_body, &pb_body_len);
+ }
+ if (pb_body) {
for (uint32_t pi = 0; pi < pb_body_len && pi < 16; pi++) {
ZirInstTag ptag = fn_zir->inst_tags[pb_body[pi]];
if (ptag == ZIR_INST_PARAM || ptag == ZIR_INST_PARAM_COMPTIME
@@ -6667,11 +7265,53 @@ static AirInstRef comptimeFieldCall(Sema* sema, SemaBlock* block,
fn_zir->inst_len * sizeof(AirInstRef));
}
+ // --- Call frame setup for comptime function calls ---
+ // Ported from Sema.zig analyzeCall comptime path.
+ // Creates: *const param_type, ptr_uav, *mut return_type, undef,
+ // ptr_comptime_alloc. These must be created before body evaluation
+ // to match the Zig compiler's IP entry order.
+ {
+ // Get function type to extract return type.
+ const Nav* fn_nav_resolved = ipGetNav(fn_nav_idx);
+ InternPoolIndex fn_decl_ip = fn_nav_resolved->resolved_type;
+ if (fn_decl_ip != IP_INDEX_NONE
+ && sema->ip->items[fn_decl_ip].tag == IP_KEY_FUNC) {
+ InternPoolIndex fn_type_ip
+ = sema->ip->items[fn_decl_ip].data.func_decl.ty;
+ if (fn_type_ip != IP_INDEX_NONE
+ && sema->ip->items[fn_type_ip].tag == IP_KEY_FUNC_TYPE) {
+ InternPoolIndex ret_ty
+ = sema->ip->items[fn_type_ip].data.func_type.return_type;
+ // Set inner sema's return type so zirRetPtr/zirRetType
+ // produce the correct pointer type during body evaluation.
+ fn_sema.fn_ret_ty = ret_ty;
+ // Create *const param_type (pointer to the parameter).
+ InternPoolIndex param_ptr_ty = internPtrConst(obj_type);
+ // Create ptr_uav wrapping the parameter value.
+ (void)internPtrUav(param_ptr_ty, obj_ip);
+ // Create *mut return_type for the return value alloc.
+ InternPoolIndex ret_mut_ptr = internPtrMutable(ret_ty);
+ // Create undef(return_type).
+ (void)internUndef(ret_ty);
+ // Create ptr_comptime_alloc for return value storage.
+ (void)internPtrComptimeAlloc(ret_mut_ptr, 0);
+ }
+ }
+ }
+
// Map parameter instructions to argument values.
// For field_call, the first param is the object (self).
// Ported from Sema.zig analyzeCall line 7773-7776.
if (param_count > 0) {
+ // Transfer CT tracking from the outer sema to the inner sema.
+ // This allows the inner function body to resolve field accesses
+ // on tracked comptime values (e.g. target pointer).
+ uint32_t ct_val;
+ uint8_t ct_tag = ctLookup(sema, AIR_REF_TO_IP(obj_ref), &ct_val);
instMapPut(&fn_sema.inst_map, param_insts[0], obj_ref);
+ if (ct_tag != CT_TAG_NONE) {
+ ctTrack(&fn_sema, AIR_REF_TO_IP(obj_ref), ct_tag, ct_val);
+ }
}
// Evaluate the function body.
@@ -8514,10 +9154,9 @@ static AirInstRef zirCall(
sema->code = dbg_code;
}
- // Upstream: need_debug_scope = !block.isComptime() && ...
- // When comptime (or all args comptime), use BLOCK; otherwise
- // DBG_INLINE_BLOCK.
- bool need_debug_scope = !block->is_comptime;
+ // Upstream: need_debug_scope = !block.isComptime() && !block.is_typeof
+ // && !block.ownerModule().strip
+ bool need_debug_scope = !block->is_comptime && !sema->strip;
AirInstTag block_tag
= need_debug_scope ? AIR_INST_DBG_INLINE_BLOCK : AIR_INST_BLOCK;
@@ -8633,7 +9272,8 @@ static AirInstRef zirCall(
}
}
}
- if (!child_block.is_comptime && !arg_comptime_only) {
+ if (!child_block.is_comptime && !sema->strip
+ && !arg_comptime_only) {
uint32_t param_name_idx;
if (ptag == ZIR_INST_PARAM_ANYTYPE) {
// str_tok: name is at str_tok.start.
@@ -10203,6 +10843,107 @@ static AirInstRef zirFieldValComptime(
return AIR_REF_FROM_IP(ipIntern(sema->ip, key));
}
+ // Comptime field access on target pointer: target.cpu, target.cpu.arch.
+ // When the body of cCallingConvention evaluates target.cpu.arch,
+ // the Zig compiler creates ptr_type, ptr_nav, ptr_field, and enum_tag
+ // entries. Ported from Sema.zig fieldVal / fieldPtr comptime path.
+ if (ct_tag == CT_TAG_TARGET_PTR && strcmp(field_name, "cpu") == 0) {
+ // Access .cpu on the comptime target pointer.
+ // ct_val = Target struct type IP index.
+ InternPoolIndex target_ty = ct_val;
+
+ // Find the "cpu" field in Target struct.
+ uint32_t cpu_fidx = findStructFieldIndexByType(target_ty, "cpu");
+ if (cpu_fidx != UINT32_MAX) {
+ InternPoolIndex cpu_type
+ = resolveFieldTypeByIP(target_ty, cpu_fidx);
+ if (cpu_type != IP_INDEX_NONE) {
+ // Create *const Cpu pointer type ($680).
+ InternPoolIndex const_cpu_ptr = internPtrConst(cpu_type);
+ // Create *mut Target pointer type ($681).
+ InternPoolIndex mut_tgt_ptr = internPtrMutable(target_ty);
+ // Create ptr_nav for the target ($682).
+ // Use the CG builtin "target" nav with *mut Target type.
+ uint32_t tgt_nav
+ = findNavInNamespace(s_cg_builtin_ns_idx, "target");
+ if (tgt_nav != UINT32_MAX) {
+ InternPoolIndex nav_ptr
+ = internNavPtr(mut_tgt_ptr, tgt_nav);
+ // Create ptr_field ($683).
+ InternPoolIndex pf
+ = internPtrField(const_cpu_ptr, nav_ptr, cpu_fidx);
+ ctTrack(sema, pf, CT_TAG_STRUCT_FIELD_PTR, cpu_type);
+ return AIR_REF_FROM_IP(pf);
+ }
+ }
+ }
+ }
+
+ if (ct_tag == CT_TAG_STRUCT_FIELD_PTR && strcmp(field_name, "arch") == 0
+ && s_target_cpu_arch_name != NULL) {
+ // Access .arch on the Cpu struct (from target.cpu.arch).
+ // ct_val = Cpu struct type IP index.
+ InternPoolIndex cpu_type = ct_val;
+ uint32_t cpu_ns = findNamespaceForType(cpu_type);
+ if (cpu_ns != UINT32_MAX) {
+ // Find the Arch enum type in Cpu namespace.
+ uint32_t arch_nav = findNavInNamespace(cpu_ns, "Arch");
+ if (arch_nav != UINT32_MAX) {
+ const Nav* arch_n = ipGetNav(arch_nav);
+ InternPoolIndex arch_ty = arch_n->resolved_type;
+ if (arch_ty != IP_INDEX_NONE) {
+ // Find the target arch field in the Arch enum ZIR.
+ const Zir* arch_zir = NULL;
+ uint32_t enum_inst
+ = findEnumDeclForNav(arch_nav, &arch_zir);
+ if (enum_inst != UINT32_MAX) {
+ uint32_t arch_fidx = findEnumFieldByName(
+ arch_zir, enum_inst, s_target_cpu_arch_name);
+ if (arch_fidx != UINT32_MAX) {
+ InternPoolIndex int_val = getEnumFieldIntVal(
+ arch_zir, enum_inst, arch_fidx);
+ if (int_val != IP_INDEX_NONE) {
+ InternPoolIndex tag
+ = internEnumTag(arch_ty, int_val);
+ // Trigger arch module cascade ($685+).
+ // Must happen here (after enum_tag, before
+ // switch body creates enum_literals).
+ uint32_t tfi
+ = findFileByPathSuffix("/std/Target.zig");
+ if (tfi != UINT32_MAX) {
+ triggerArchModuleCascade(
+ s_file_namespace[tfi], tfi);
+ }
+ return AIR_REF_FROM_IP(tag);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // General namespace lookup: when the base object is a struct/union type,
+ // look up the field name as a declaration in its namespace.
+ // This handles chains like std.Target.Cpu.Model where each step
+ // resolves a type declaration in the previous type's namespace.
+ // Ported from Sema.zig fieldVal for types → analyzeNavRefInner path.
+ if (obj_ip < sema->ip->items_len && obj_ip != IP_INDEX_VOID_VALUE) {
+ InternPoolKeyTag kt = sema->ip->items[obj_ip].tag;
+ if (kt == IP_KEY_STRUCT_TYPE || kt == IP_KEY_UNION_TYPE
+ || kt == IP_KEY_ENUM_TYPE) {
+ uint32_t base_ns = findNamespaceForType(obj_ip);
+ if (base_ns != UINT32_MAX) {
+ uint32_t member_nav = findNavInNamespace(base_ns, field_name);
+ if (member_nav != UINT32_MAX) {
+ InternPoolIndex val = ensureNavValUpToDate(member_nav);
+ if (val != IP_INDEX_NONE)
+ return AIR_REF_FROM_IP(val);
+ }
+ }
+ }
+ }
+
// Handle cross-module struct type resolution:
// std.math.F80 → register the F80 struct (fraction: u64, exp: u16).
// Ported from lib/std/math.zig F80 definition.
@@ -10235,6 +10976,29 @@ static AirInstRef zirFieldValComptime(
uint32_t field_nav
= findNavInNamespace(s_cg_builtin_ns_idx, field_name);
if (field_nav != UINT32_MAX) {
+ // For "target", return the ptr_nav (pointer to the
+ // target value) rather than the type. This enables
+ // comptime method calls like
+ // target.cCallingConvention() where the evaluator
+ // needs a pointer, not a type.
+ // Track it so the comptime evaluator can resolve
+ // field accesses on the target value.
+ if (strcmp(field_name, "target") == 0) {
+ const Nav* tn = ipGetNav(field_nav);
+ if (tn->resolved_type != IP_INDEX_NONE) {
+ InternPoolIndex ptr_ty
+ = internPtrConst(tn->resolved_type);
+ InternPoolKey pk;
+ memset(&pk, 0, sizeof(pk));
+ pk.tag = IP_KEY_PTR_NAV;
+ pk.data.ptr_nav.ty = ptr_ty;
+ pk.data.ptr_nav.nav = field_nav;
+ InternPoolIndex pn = ipIntern(sema->ip, pk);
+ ctTrack(sema, pn, CT_TAG_TARGET_PTR,
+ tn->resolved_type);
+ return AIR_REF_FROM_IP(pn);
+ }
+ }
InternPoolIndex val = ensureNavValUpToDate(field_nav);
if (val != IP_INDEX_NONE)
return AIR_REF_FROM_IP(val);
@@ -10277,6 +11041,61 @@ static AirInstRef zirFieldPtr(Sema* sema, SemaBlock* block, uint32_t inst) {
AirInstRef obj = resolveInst(sema, obj_ref);
+ // Cross-module comptime pointer resolution.
+ // When the object is from a cross-module import (decl_ref of
+ // @import), load the imported module's ZIR and resolve the field.
+ // This handles patterns like:
+ // @import("builtin").target → ptr_nav(target)
+ // Ported from zirFieldValComptime cross-module path, adapted for
+ // pointer (field_ptr) context.
+ if (AIR_REF_IS_IP(obj) && block->is_comptime) {
+ InternPoolIndex obj_ip = AIR_REF_TO_IP(obj);
+ if (obj_ip == IP_INDEX_VOID_VALUE && obj_ref >= ZIR_REF_START_INDEX
+ && sema->source_dir) {
+ uint32_t obj_inst = obj_ref - ZIR_REF_START_INDEX;
+ ZirInstTag obj_tag = sema->code.inst_tags[obj_inst];
+ if (obj_tag == ZIR_INST_DECL_VAL || obj_tag == ZIR_INST_DECL_REF) {
+ uint32_t decl_name_idx
+ = sema->code.inst_datas[obj_inst].str_tok.start;
+ const char* import_path
+ = findDeclImportPath(sema, decl_name_idx);
+ if (import_path && strcmp(import_path, "builtin") == 0
+ && s_cg_builtin_ns_idx != UINT32_MAX) {
+ uint32_t field_nav
+ = findNavInNamespace(s_cg_builtin_ns_idx, field_name);
+ if (field_nav != UINT32_MAX) {
+ if (strcmp(field_name, "target") == 0) {
+ const Nav* tn = ipGetNav(field_nav);
+ if (tn->resolved_type != IP_INDEX_NONE) {
+ InternPoolIndex ptr_ty
+ = internPtrConst(tn->resolved_type);
+ InternPoolKey pk;
+ memset(&pk, 0, sizeof(pk));
+ pk.tag = IP_KEY_PTR_NAV;
+ pk.data.ptr_nav.ty = ptr_ty;
+ pk.data.ptr_nav.nav = field_nav;
+ InternPoolIndex pn = ipIntern(sema->ip, pk);
+ ctTrack(sema, pn, CT_TAG_TARGET_PTR,
+ tn->resolved_type);
+ return AIR_REF_FROM_IP(pn);
+ }
+ }
+ InternPoolIndex val = ensureNavValUpToDate(field_nav);
+ if (val != IP_INDEX_NONE) {
+ InternPoolIndex ptr_ty = internPtrConst(val);
+ InternPoolKey pk;
+ memset(&pk, 0, sizeof(pk));
+ pk.tag = IP_KEY_PTR_NAV;
+ pk.data.ptr_nav.ty = ptr_ty;
+ pk.data.ptr_nav.nav = field_nav;
+ return AIR_REF_FROM_IP(ipIntern(sema->ip, pk));
+ }
+ }
+ }
+ }
+ }
+ }
+
// Comptime: if the operand IS a struct type (its type is `type`),
// look up the field in the type's namespace. This handles patterns
// like @import("builtin").target where the import resolves to a
@@ -10312,6 +11131,55 @@ static AirInstRef zirFieldPtr(Sema* sema, SemaBlock* block, uint32_t inst) {
}
}
+ // Comptime struct field access through tracked pointers.
+ // When the base pointer is a tracked comptime value (e.g. target
+ // pointer), create ptr_field IP entries and track the result.
+ // Ported from Sema.zig fieldPtr → comptime pointer path.
+ if (AIR_REF_IS_IP(obj) && block->is_comptime) {
+ InternPoolIndex obj_ip = AIR_REF_TO_IP(obj);
+ uint32_t ct_val;
+ uint8_t ct_tag = ctLookup(sema, obj_ip, &ct_val);
+ if (ct_tag == CT_TAG_TARGET_PTR || ct_tag == CT_TAG_STRUCT_FIELD_PTR) {
+ // Get the pointee struct type from the pointer type.
+ InternPoolIndex ptr_type = ipTypeOf(sema->ip, obj_ip);
+ if (ptr_type != IP_INDEX_NONE
+ && sema->ip->items[ptr_type].tag == IP_KEY_PTR_TYPE) {
+ InternPoolIndex struct_type_ip
+ = sema->ip->items[ptr_type].data.ptr_type.child;
+ // Look up the field index from ZIR.
+ uint32_t fidx
+ = findStructFieldIndexByType(struct_type_ip, field_name);
+ if (fidx != UINT32_MAX) {
+ // Look up the field type by finding the struct's
+ // ZIR and resolving the field type reference.
+ // For now, find the namespace's file, parse the
+ // struct_decl, and get the field's type.
+ uint32_t ns = findNamespaceForType(struct_type_ip);
+ InternPoolIndex field_type = IP_INDEX_NONE;
+ if (ns != UINT32_MAX) {
+ uint32_t fid = s_namespaces[ns].file_idx;
+ if (fid < s_num_loaded_modules
+ && s_loaded_modules[fid].has_zir) {
+ field_type = resolveStructFieldType(
+ &s_loaded_modules[fid].zir, 0, fidx, ns, fid);
+ }
+ }
+ if (field_type != IP_INDEX_NONE) {
+ // Create *const field_type pointer.
+ InternPoolIndex fld_ptr_ty
+ = internPtrConst(field_type);
+ // Create ptr_field entry.
+ InternPoolIndex pf
+ = internPtrField(fld_ptr_ty, obj_ip, fidx);
+ // Track so subsequent accesses can resolve.
+ ctTrack(sema, pf, CT_TAG_STRUCT_FIELD_PTR, field_type);
+ return AIR_REF_FROM_IP(pf);
+ }
+ }
+ }
+ }
+ }
+
// Determine the struct type from the pointer operand's type.
// The operand is a pointer-to-struct, so get the pointee type.
TypeIndex ptr_ty = semaTypeOf(sema, obj);
@@ -10977,16 +11845,11 @@ static AirInstRef zirMinMax(Sema* sema, SemaBlock* block, uint32_t inst) {
// --- zirRetPtr ---
// Ported from src/Sema.zig zirRetPtr.
-static AirInstRef zirRetPtr(Sema* sema, SemaBlock* block) {
+static AirInstRef zirRetPtr(const Sema* sema, SemaBlock* block) {
TypeIndex ret_ty = sema->fn_ret_ty;
if (ret_ty == TYPE_NONE)
ret_ty = IP_INDEX_VOID_TYPE;
- InternPoolKey pk;
- memset(&pk, 0, sizeof(pk));
- pk.tag = IP_KEY_PTR_TYPE;
- pk.data.ptr_type.child = ret_ty;
- pk.data.ptr_type.flags = 0;
- TypeIndex ptr_ty = ipIntern(sema->ip, pk);
+ TypeIndex ptr_ty = internPtrMutable(ret_ty);
AirInstData d;
memset(&d, 0, sizeof(d));
d.ty.ty_ref = AIR_REF_FROM_IP(ptr_ty);
@@ -11943,7 +12806,6 @@ static bool analyzeBodyInner(
return true;
uint32_t inst = body[i];
ZirInstTag inst_tag = sema->code.inst_tags[inst];
-
switch (inst_tag) {
// Instructions that don't produce a ref and don't go into the
@@ -12035,15 +12897,13 @@ static bool analyzeBodyInner(
if (import_path) {
if (strcmp(import_path, "builtin") == 0) {
// @import("builtin") — compiler-generated
- // builtin module. Look up the nav in the
- // current file's namespace.
- uint32_t ns_idx = s_file_namespace[sema->file_idx];
- uint32_t nav = findNavInNamespace(ns_idx, decl_name);
- if (nav != UINT32_MAX) {
- const Nav* n = ipGetNav(nav);
- if (n->resolved_type != IP_INDEX_NONE)
- resolved = n->resolved_type;
- }
+ // builtin module. Don't resolve to the struct
+ // type here — leave as VOID_VALUE so that
+ // field_val's cross-module builtin path
+ // (zirFieldValComptime lines 10405-10414) can
+ // look up field declarations in the CG builtin
+ // namespace directly. Resolving to the struct
+ // type would prevent that path from firing.
} else if (sema->source_dir) {
char import_full[1024];
if (resolveImportPath(sema->source_dir, import_path,
diff --git a/stage0/sema.h b/stage0/sema.h
@@ -284,6 +284,10 @@ typedef struct Sema {
// When true, test declarations are analyzed (test blocks become
// analyzeable functions). Set by the caller before semaAnalyze.
bool is_test;
+ // When true, debug statements (dbg_stmt, dbg_var_val, etc.) and
+ // debug inline blocks are suppressed. Matches Zig's
+ // block.ownerModule().strip. Set by the caller before semaAnalyze.
+ bool strip;
// File index into s_loaded_modules[] for this sema's module.
// Set by analyzeNavValC / semaAnalyze. UINT32_MAX = unknown.
uint32_t file_idx;
diff --git a/stage0/stages_test.zig b/stage0/stages_test.zig
@@ -75,6 +75,8 @@ fn stagesCheck(gpa: Allocator, comptime path: []const u8, source: [:0]const u8)
defer sc.semaDeinit(&c_sema);
c_sema.source_dir = source_dir_path.ptr;
c_sema.module_root = module_root_path.ptr;
+ // Pre-generated AIR uses ReleaseSmall (strip=true), so match it.
+ c_sema.strip = true;
// Set root_fqn and module_prefix so C sema FQNs match Zig's.
// lib/std/ files: "std.{prefix}.func" (std.zig compiled as root)
// other files: "{stem}.func" (standalone compilation)