commit 83e972b87f0dd87119733f39286b36f12659cde3 (tree)
parent 58eb5f6f3c3e7a286ac85bf65c3d9472624ae2f3
Author: Motiejus <motiejus@jakstys.lt>
Date: Fri, 27 Feb 2026 01:06:38 +0000
sema: add resolveStructLayoutC to match Zig's resolveUnionLayout ordering
In the Zig compiler, resolveUnionLayout calls resolveStructLayout for each
struct-typed field BEFORE resolveUnionFully's field loop creates init values.
This means type entries (like ?u64) are created during layout resolution,
while init entries (like opt_null) come later in resolveStructFully.
Split resolveStructFullyC into resolveStructLayoutC (field types only) and
resolveStructFullyC (inits + recursive). Call resolveStructLayoutC in
Phase 2b of resolveUnionFullyC after tag values, matching the Zig compiler's
ordering.
Also add resolveTypeFullyC and resolveStructFieldTypesFully for recursive
type resolution matching Zig's Type.resolveFully.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
| M | stage0/sema.c | | | 310 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- |
1 file changed, 272 insertions(+), 38 deletions(-)
diff --git a/stage0/sema.c b/stage0/sema.c
@@ -3888,10 +3888,246 @@ static uint32_t findNamespaceForType(InternPoolIndex type_ip) {
return UINT32_MAX;
}
+// Forward declarations for mutually recursive resolution.
+static bool resolveStructLayoutC(uint32_t nav_idx);
+static void resolveTypeFullyC(InternPoolIndex ip_idx);
+static void resolveStructFullyC(uint32_t nav_idx);
+static void resolveUnionFullyC(uint32_t nav_idx);
+
+// --- findNavForIPIndex ---
+// Search all namespaces for a nav whose resolved_type matches ip_idx.
+// Returns the nav index, or UINT32_MAX if not found.
+static uint32_t findNavForIPIndex(InternPoolIndex ip_idx) {
+ for (uint32_t nsi = 0; nsi < s_num_namespaces; nsi++) {
+ const SemaNamespace* ns = &s_namespaces[nsi];
+ for (uint32_t k = 0; k < ns->pub_nav_count; k++) {
+ if (ipGetNav(ns->pub_navs[k])->resolved_type == ip_idx)
+ return ns->pub_navs[k];
+ }
+ for (uint32_t k = 0; k < ns->priv_nav_count; k++) {
+ if (ipGetNav(ns->priv_navs[k])->resolved_type == ip_idx)
+ return ns->priv_navs[k];
+ }
+ }
+ return UINT32_MAX;
+}
+
+// --- resolveTypeFullyC ---
+// Recursively resolve a type, matching Zig's Type.resolveFully.
+// For struct/union types: calls resolveStructFullyC/resolveUnionFullyC.
+// For compound types (pointer, optional, array, slice): recurses on child.
+// For simple types (int, float, bool, void, etc.): no-op.
+static void resolveTypeFullyC(InternPoolIndex ip_idx) {
+ if (ip_idx == IP_INDEX_NONE || ip_idx >= s_module_ip->items_len)
+ return;
+ InternPoolKeyTag tag = s_module_ip->items[ip_idx].tag;
+ switch (tag) {
+ case IP_KEY_STRUCT_TYPE: {
+ uint32_t nav = findNavForIPIndex(ip_idx);
+ if (nav != UINT32_MAX)
+ resolveStructFullyC(nav);
+ break;
+ }
+ case IP_KEY_UNION_TYPE: {
+ uint32_t nav = findNavForIPIndex(ip_idx);
+ if (nav != UINT32_MAX)
+ resolveUnionFullyC(nav);
+ break;
+ }
+ case IP_KEY_PTR_TYPE:
+ resolveTypeFullyC(s_module_ip->items[ip_idx].data.ptr_type.child);
+ break;
+ case IP_KEY_OPT_TYPE:
+ resolveTypeFullyC(s_module_ip->items[ip_idx].data.opt_type);
+ break;
+ case IP_KEY_ARRAY_TYPE:
+ resolveTypeFullyC(s_module_ip->items[ip_idx].data.array_type.child);
+ break;
+ case IP_KEY_SLICE:
+ // Slice wraps a pointer type; resolve the pointer's child.
+ resolveTypeFullyC(s_module_ip->items[ip_idx].data.slice);
+ break;
+ default:
+ break;
+ }
+}
+
+// --- resolveStructFieldTypesFully ---
+// After struct field types have been resolved, recursively resolve each
+// field type fully. Matches Zig's resolveStructFully loop:
+// for (0..struct_type.field_types.len) |i|
+// field_ty.resolveFully(pt);
+// Re-parses the struct's ZIR fields to get the resolved type refs, then
+// calls resolveTypeFullyC on each.
+static void resolveStructFieldTypesFully(const Zir* zir, uint32_t struct_inst,
+ uint32_t struct_ns, uint32_t file_idx) {
+ if (zir->inst_tags[struct_inst] != ZIR_INST_EXTENDED)
+ return;
+ if (zir->inst_datas[struct_inst].extended.opcode != ZIR_EXT_STRUCT_DECL)
+ return;
+
+ uint16_t ssmall = zir->inst_datas[struct_inst].extended.small;
+ uint32_t soperand = zir->inst_datas[struct_inst].extended.operand;
+
+ bool s_has_captures = (ssmall & (1 << 0)) != 0;
+ bool s_has_fields = (ssmall & (1 << 1)) != 0;
+ bool s_has_decls = (ssmall & (1 << 2)) != 0;
+ bool s_has_backing_int = (ssmall & (1 << 3)) != 0;
+
+ uint32_t sei = soperand + 6;
+
+ uint32_t s_captures_len = 0;
+ uint32_t s_fields_len = 0;
+ uint32_t s_decls_len = 0;
+ if (s_has_captures)
+ s_captures_len = zir->extra[sei++];
+ if (s_has_fields)
+ s_fields_len = zir->extra[sei++];
+ if (s_has_decls)
+ s_decls_len = zir->extra[sei++];
+
+ if (s_fields_len == 0 || s_fields_len > 64)
+ return;
+
+ sei += s_captures_len * 2;
+ if (s_has_backing_int) {
+ uint32_t bi_body = zir->extra[sei++];
+ sei += (bi_body == 0) ? 1 : bi_body;
+ }
+ sei += s_decls_len;
+
+ uint32_t bags_start = sei;
+ uint32_t bags_count = (s_fields_len + 7) / 8;
+ sei += bags_count;
+
+ bool any_default_inits = (ssmall & (1 << 10)) != 0;
+
+ // First pass: read field headers (same layout as
+ // resolveStructFieldTypesC).
+ typedef struct {
+ uint32_t type_body_len;
+ uint32_t align_body_len;
+ uint32_t init_body_len;
+ bool has_type_body;
+ uint32_t type_ref;
+ } FieldInfo;
+ FieldInfo finfo[64];
+
+ for (uint32_t fi = 0; fi < s_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;
+
+ sei++; // skip field_name
+
+ finfo[fi].has_type_body = f_has_type_body;
+ finfo[fi].type_body_len = 0;
+ finfo[fi].align_body_len = 0;
+ finfo[fi].init_body_len = 0;
+
+ if (f_has_type_body) {
+ finfo[fi].type_body_len = zir->extra[sei];
+ finfo[fi].type_ref = 0;
+ } else {
+ finfo[fi].type_ref = zir->extra[sei];
+ }
+ sei++;
+
+ if (f_has_align) {
+ finfo[fi].align_body_len = zir->extra[sei];
+ sei++;
+ }
+ if (f_has_init && any_default_inits) {
+ finfo[fi].init_body_len = zir->extra[sei];
+ sei++;
+ }
+ }
+
+ // Second pass: resolve each field type and call resolveTypeFullyC.
+ for (uint32_t fi = 0; fi < s_fields_len; fi++) {
+ InternPoolIndex resolved = IP_INDEX_NONE;
+ if (finfo[fi].has_type_body && finfo[fi].type_body_len > 0) {
+ uint32_t tbl = finfo[fi].type_body_len;
+ uint32_t last_zi = zir->extra[sei + tbl - 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;
+ resolved = resolveZirTypeRef(zir, op, struct_ns, file_idx);
+ }
+ } else if (!finfo[fi].has_type_body
+ && finfo[fi].type_ref >= ZIR_REF_START_INDEX) {
+ resolved = resolveZirTypeRef(
+ zir, finfo[fi].type_ref, struct_ns, file_idx);
+ }
+ sei += finfo[fi].type_body_len;
+ sei += finfo[fi].align_body_len;
+ sei += finfo[fi].init_body_len;
+
+ if (resolved != IP_INDEX_NONE)
+ resolveTypeFullyC(resolved);
+ }
+}
+
+// --- resolveStructLayoutC ---
+// Resolve struct field types (without inits or recursive resolution).
+// Matches Zig's resolveStructLayout which is called during resolveUnionLayout.
+// This creates type entries (like ?u64) during union layout resolution,
+// before field init values (like opt_null) are created later in
+// resolveStructFullyC.
+static bool s_struct_layout_resolved[4096];
+static bool resolveStructLayoutC(uint32_t nav_idx) {
+ const Nav* nav = ipGetNav(nav_idx);
+ if (nav->resolved_type == IP_INDEX_NONE)
+ return false;
+ if (s_module_ip->items[nav->resolved_type].tag != IP_KEY_STRUCT_TYPE)
+ return false;
+ if (nav_idx < 4096 && s_struct_layout_resolved[nav_idx])
+ return true;
+ if (nav_idx < 4096)
+ s_struct_layout_resolved[nav_idx] = true;
+
+ // Find the struct's namespace.
+ uint32_t ns_idx = findNamespaceForType(nav->resolved_type);
+ if (ns_idx == UINT32_MAX)
+ return false;
+
+ // Find the struct_decl ZIR instruction.
+ uint32_t file_idx = s_namespaces[ns_idx].file_idx;
+ if (!s_loaded_modules[file_idx].has_zir)
+ return false;
+ const Zir* zir = &s_loaded_modules[file_idx].zir;
+
+ const uint32_t* vbody = NULL;
+ uint32_t vbody_len = 0;
+ getValueBodyFromZir(zir, nav->zir_index, &vbody, &vbody_len);
+ if (!vbody)
+ return false;
+
+ uint32_t struct_inst = UINT32_MAX;
+ for (uint32_t i = 0; i < vbody_len; i++) {
+ uint32_t inst = vbody[i];
+ if (inst < zir->inst_len && zir->inst_tags[inst] == ZIR_INST_EXTENDED
+ && zir->inst_datas[inst].extended.opcode == ZIR_EXT_STRUCT_DECL) {
+ struct_inst = inst;
+ break;
+ }
+ }
+ if (struct_inst == UINT32_MAX)
+ return false;
+
+ // Resolve field types (creates pointer/slice/optional IP entries).
+ resolveStructFieldTypesC(zir, struct_inst, ns_idx, file_idx);
+ return true;
+}
+
// --- resolveStructFullyC ---
-// Second-phase resolution for a struct type: resolve inner declarations,
-// field types, and field default values. Ported from Sema.zig
+// Second-phase resolution for a struct type: resolve field default values
+// and recursively resolve field types fully. Ported from Sema.zig
// resolveStructFully (called after all BuiltinDecl types are created).
+// Assumes resolveStructLayoutC has already been called (field types resolved).
static bool s_struct_fully_resolved[4096];
static void resolveStructFullyC(uint32_t nav_idx) {
const Nav* nav = ipGetNav(nav_idx);
@@ -3904,6 +4140,9 @@ static void resolveStructFullyC(uint32_t nav_idx) {
if (nav_idx < 4096)
s_struct_fully_resolved[nav_idx] = true;
+ // Ensure layout is resolved first (idempotent).
+ resolveStructLayoutC(nav_idx);
+
// Find the struct's namespace.
uint32_t ns_idx = findNamespaceForType(nav->resolved_type);
if (ns_idx == UINT32_MAX)
@@ -3933,11 +4172,12 @@ static void resolveStructFullyC(uint32_t nav_idx) {
if (struct_inst == UINT32_MAX)
return;
- // Resolve field types (creates pointer/slice/optional IP entries).
- resolveStructFieldTypesC(zir, struct_inst, ns_idx, file_idx);
-
// Resolve field default values (creates enum_tag, opt_null, etc.).
resolveStructFieldInitsC(zir, struct_inst, ns_idx, file_idx);
+
+ // Recursively resolve field types (matching Zig's resolveStructFully
+ // field loop that calls field_ty.resolveFully on each field).
+ resolveStructFieldTypesFully(zir, struct_inst, ns_idx, file_idx);
}
// --- resolveUnionFullyC ---
@@ -4016,8 +4256,9 @@ static void resolveUnionFullyC(uint32_t nav_idx) {
extra_index += captures_len * 2; // skip captures
// Phase 1: Resolve union field types in field order.
- uint32_t resolved_struct_navs[64];
- uint32_t num_resolved_structs = 0;
+ // Store all resolved field type IP indices for Phase 3.
+ InternPoolIndex resolved_field_types[128];
+ uint32_t num_resolved_fields = 0;
{
uint32_t field_extra = extra_index + decls_len + body_len;
uint32_t num_bit_bags = (fields_len + 7) / 8;
@@ -4035,32 +4276,8 @@ static void resolveUnionFullyC(uint32_t nav_idx) {
ZirInstRef type_ref = zir->extra[cursor++];
InternPoolIndex resolved
= resolveZirTypeRef(zir, type_ref, ns_idx, file_idx);
- // Track struct-typed fields for deferred resolution in
- // Phase 3. The Zig compiler creates all struct_type +
- // ptr_nav pairs consecutively during union field
- // iteration, then resolves their fields later. We must
- // NOT call resolveStructFullyC here — that would create
- // compound type entries (opt_type, ptr_type, slice)
- // interleaved with the struct entries.
- if (resolved != IP_INDEX_NONE && num_resolved_structs < 64
- && s_module_ip->items[resolved].tag
- == IP_KEY_STRUCT_TYPE) {
- for (uint32_t nsi = 0; nsi < s_num_namespaces; nsi++) {
- const SemaNamespace* sns = &s_namespaces[nsi];
- bool found = false;
- for (uint32_t k = 0; k < sns->pub_nav_count; k++) {
- if (ipGetNav(sns->pub_navs[k])->resolved_type
- == resolved) {
- resolved_struct_navs[num_resolved_structs++]
- = sns->pub_navs[k];
- found = true;
- break;
- }
- }
- if (found)
- break;
- }
- }
+ if (resolved != IP_INDEX_NONE && num_resolved_fields < 128)
+ resolved_field_types[num_resolved_fields++] = resolved;
}
if (ft_has_align)
cursor++;
@@ -4093,11 +4310,28 @@ static void resolveUnionFullyC(uint32_t nav_idx) {
(void)ipIntern(s_module_ip, ek);
}
- // Phase 3: Struct field resolution is deferred to resolveBuiltinDeclTypes
- // which calls resolveStructFullyC for each nested type in the correct
- // order, matching the Zig compiler's processing sequence.
- (void)resolved_struct_navs;
- (void)num_resolved_structs;
+ // Phase 2b: Resolve struct field types for each struct-typed field.
+ // Matches Zig's resolveUnionLayout which calls resolveStructLayout
+ // (and thus resolveStructFieldTypes) for each field type AFTER creating
+ // the tag enum. This creates type entries (like ?u64) during layout
+ // resolution, before field init values are created in Phase 3.
+ for (uint32_t i = 0; i < num_resolved_fields; i++) {
+ if (resolved_field_types[i] < s_module_ip->items_len
+ && s_module_ip->items[resolved_field_types[i]].tag
+ == IP_KEY_STRUCT_TYPE) {
+ uint32_t field_nav = findNavForIPIndex(resolved_field_types[i]);
+ if (field_nav != UINT32_MAX)
+ resolveStructLayoutC(field_nav);
+ }
+ }
+
+ // Phase 3: Recursively resolve each field type fully.
+ // Matches Zig's resolveUnionFully field loop:
+ // for (0..union_obj.field_types.len) |field_index|
+ // field_ty.resolveFully(pt);
+ for (uint32_t i = 0; i < num_resolved_fields; i++) {
+ resolveTypeFullyC(resolved_field_types[i]);
+ }
}
// --- resolveUnionDeclFromZir ---