commit 9f641e8bef2b60ef42b59d73c0b34d5f1259a4c4 (tree)
parent 8a809099b072a22b74c1e95db61d2201e4e70bfb
Author: Motiejus <motiejus@jakstys.lt>
Date: Sat, 7 Mar 2026 21:39:01 +0000
sema: port struct_init_field_type, enum field lookup (num_passing=5)
Port upstream struct_init_field_type (zirStructInitFieldType → fieldType)
for resolving field types in struct/union init expressions like @Type(.{
.int = .{ .signedness = .unsigned, .bits = 32 } }).
- Add lookupStructFieldTypeFromZir: re-parses struct ZIR declarations to
find field types by name (handles nested types like Type.Int)
- Add union_fields table in Zcu for union field name/type lookup
- Add enum_fields table in Zcu for enum field name/tag value lookup
- Extend zirDeclLiteralComptime with enum field value resolution
(fieldVal → enumFieldIndex → enumValueFieldIndex)
- Fix build.zig: track header files as inputs for gcc/clang/tcc builds
so struct layout changes in .h files properly invalidate cached .o files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
4 files changed, 337 insertions(+), 15 deletions(-)
diff --git a/build.zig b/build.zig
@@ -1871,6 +1871,8 @@ fn addZig0CSources(
});
cc1.addArg("-c");
cc1.addFileArg(b.path(b.fmt("stage0/{s}", .{cfile})));
+ // Track headers as extra inputs so changes invalidate the cache.
+ for (zig0_headers) |h| cc1.addFileInput(b.path(b.fmt("stage0/{s}", .{h})));
cc1.addArg("-o");
mod.addObjectFile(cc1.addOutputFileArg(b.fmt("{s}.o", .{cfile[0 .. cfile.len - 2]})));
}
diff --git a/stage0/corpus.zig b/stage0/corpus.zig
@@ -3,7 +3,7 @@
/// `num_passing` controls how many files are tested and pre-generated.
/// Both build.zig and stages_test.zig import this file.
/// To enable more tests: just increment `num_passing`.
-pub const num_passing: usize = 4;
+pub const num_passing: usize = 5;
pub const files = [_][]const u8{
"stage0/sema_tests/empty.zig",
diff --git a/stage0/sema.c b/stage0/sema.c
@@ -3672,14 +3672,32 @@ InternPoolIndex resolveEnumDeclFromZir(
// 1. Resolve value ref → comptime_int IP index
// 2. Coerce to tag type → int_small IP index
// Order: comptime_int first (if new entry), then int_small.
+ // Set up enum field tracking for zirDeclLiteralComptime.
+ uint32_t ef_idx = sema->zcu->num_enum_fields;
+ bool track_ef
+ = ef_idx < ZCU_MAX_ENUM_TYPES && fields_len <= ZCU_MAX_ENUM_FIELDS;
+ if (track_ef) {
+ sema->zcu->enum_fields[ef_idx].enum_ip = enum_ip;
+ sema->zcu->enum_fields[ef_idx].field_count = fields_len;
+ }
+
uint64_t auto_val = 0;
for (uint32_t fi = 0; fi < fields_len; fi++) {
uint32_t bag_idx = fi / 32;
uint32_t bit_idx = fi % 32;
bool has_value = (zir->extra[bit_bags_start + bag_idx] >> bit_idx) & 1;
- extra_index++; // skip field_name
+ // Read field name for enum field tracking.
+ uint32_t field_name_zir = zir->extra[extra_index];
+ extra_index++;
+ if (track_ef && fi < ZCU_MAX_ENUM_FIELDS) {
+ const char* fname
+ = (const char*)&zir->string_bytes[field_name_zir];
+ sema->zcu->enum_fields[ef_idx].field_names[fi]
+ = ipGetOrPutString(sema->ip, fname);
+ }
+ InternPoolIndex tag_int_ip = IP_INDEX_NONE;
if (has_value) {
uint32_t val_ref = zir->extra[extra_index++];
@@ -3697,7 +3715,8 @@ InternPoolIndex resolveEnumDeclFromZir(
// Step 2: coerce to tag type.
if (int_tag_type != IP_INDEX_NONE)
- (void)coerceIntToTagType(sema, comptime_ip, int_tag_type);
+ tag_int_ip
+ = coerceIntToTagType(sema, comptime_ip, int_tag_type);
} else {
// Auto-increment: intern as typed int directly.
if (int_tag_type != IP_INDEX_NONE && auto_val > 0) {
@@ -3718,14 +3737,21 @@ InternPoolIndex resolveEnumDeclFromZir(
ct_ip = ipIntern(sema->ip, ct);
}
// Then coerce to tag type.
- (void)coerceIntToTagType(sema, ct_ip, int_tag_type);
+ tag_int_ip = coerceIntToTagType(sema, ct_ip, int_tag_type);
} else if (int_tag_type != IP_INDEX_NONE) {
// Value 0: comptime_int(0) is pre-interned.
- (void)coerceIntToTagType(sema, IP_INDEX_ZERO, int_tag_type);
+ tag_int_ip
+ = coerceIntToTagType(sema, IP_INDEX_ZERO, int_tag_type);
}
auto_val++;
}
+
+ // Record the coerced tag int value for enum field lookup.
+ if (track_ef && fi < ZCU_MAX_ENUM_FIELDS)
+ sema->zcu->enum_fields[ef_idx].field_tag_ints[fi] = tag_int_ip;
}
+ if (track_ef)
+ sema->zcu->num_enum_fields++;
// Set the Nav's resolved type and create ptr_nav.
if (nav_idx != UINT32_MAX) {
@@ -4377,7 +4403,161 @@ static void resolveStructFieldTypesC(Sema* sema, const Zir* zir,
}
}
-// --- findStructFieldIndexFromZir ---
+// --- lookupStructFieldTypeFromZir ---
+// Find a struct field by name and return its resolved IP type.
+// Re-parses the struct's ZIR declaration to find field names and types.
+// Returns IP_INDEX_NONE if the struct or field is not found.
+// Ported from Sema.zig fieldType → loadStructType → nameIndex.
+static InternPoolIndex lookupStructFieldTypeFromZir(
+ Sema* sema, InternPoolIndex struct_ip, const char* field_name) {
+ // Find the nav that owns this struct type.
+ uint32_t nav_idx = findNavForIPIndex(sema, struct_ip);
+ if (nav_idx == UINT32_MAX)
+ return IP_INDEX_NONE;
+ const Nav* nav = ipGetNav(sema->ip, nav_idx);
+
+ // Find the struct's ZIR.
+ uint32_t ns_idx = nav->namespace_idx;
+ if (ns_idx == UINT32_MAX || ns_idx >= sema->zcu->num_namespaces)
+ return IP_INDEX_NONE;
+ uint32_t file_idx = sema->zcu->namespaces[ns_idx].file_idx;
+ if (file_idx >= sema->zcu->num_files
+ || !sema->zcu->files[file_idx].has_zir)
+ return IP_INDEX_NONE;
+ const Zir* zir = &sema->zcu->files[file_idx].zir;
+
+ // Find the struct_decl instruction.
+ const uint32_t* vbody = NULL;
+ uint32_t vbody_len = 0;
+ getValueBodyFromZir(zir, nav->zir_index, &vbody, &vbody_len);
+ if (!vbody)
+ return IP_INDEX_NONE;
+
+ 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 IP_INDEX_NONE;
+
+ // Parse struct_decl to find field entries.
+ uint16_t ssmall = zir->inst_datas[struct_inst].extended.small;
+ uint32_t soperand = zir->inst_datas[struct_inst].extended.operand;
+ uint32_t sei = soperand + 6; // skip header
+
+ 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 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)
+ return IP_INDEX_NONE;
+
+ 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;
+
+ // Parse field bit bags and entries to find the matching field.
+ 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;
+
+ uint32_t struct_ns = findNamespaceForType(sema, struct_ip);
+
+ // The ZIR struct_decl layout has all field headers first, then all
+ // field bodies concatenated after. We need two passes: first scan
+ // headers to find the matching field and accumulate body offsets,
+ // then resolve the type from the bodies section.
+ uint32_t match_type_ref = 0;
+ uint32_t match_type_body_len = 0;
+ bool match_has_type_body = false;
+ bool found_match = false;
+ uint32_t body_offset_before_match = 0;
+ uint32_t total_bodies_len = 0;
+
+ 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;
+
+ // Read field name.
+ uint32_t fname_zir = zir->extra[sei++];
+ const char* fname = (const char*)&zir->string_bytes[fname_zir];
+ bool is_match = strcmp(fname, field_name) == 0;
+
+ // Read type ref or body len.
+ uint32_t type_body_len = 0;
+ uint32_t type_ref = 0;
+ if (f_has_type_body) {
+ type_body_len = zir->extra[sei];
+ } else {
+ type_ref = zir->extra[sei];
+ }
+ sei++;
+
+ uint32_t align_body_len = 0;
+ if (f_has_align)
+ align_body_len = zir->extra[sei++];
+ uint32_t init_body_len = 0;
+ if (f_has_init && any_default_inits)
+ init_body_len = zir->extra[sei++];
+
+ if (is_match) {
+ match_type_ref = type_ref;
+ match_type_body_len = type_body_len;
+ match_has_type_body = f_has_type_body;
+ body_offset_before_match = total_bodies_len;
+ found_match = true;
+ }
+
+ total_bodies_len += type_body_len;
+ total_bodies_len += align_body_len;
+ total_bodies_len += init_body_len;
+ }
+
+ if (!found_match)
+ return IP_INDEX_NONE;
+
+ // sei now points to the start of all bodies.
+ if (match_has_type_body && match_type_body_len > 0) {
+ uint32_t body_start = sei + body_offset_before_match;
+ uint32_t last_zi = zir->extra[body_start + match_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(sema, zir, op, struct_ns, file_idx);
+ }
+ } else if (!match_has_type_body && match_type_ref >= ZIR_REF_START_INDEX) {
+ return resolveZirTypeRef(
+ sema, zir, match_type_ref, struct_ns, file_idx);
+ } else if (!match_has_type_body && match_type_ref < ZIR_REF_START_INDEX) {
+ return match_type_ref; // pre-interned type
+ }
+ return IP_INDEX_NONE;
+}
+
// --- 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.
@@ -5129,8 +5309,18 @@ static void resolveUnionFullyC(Sema* sema, uint32_t nav_idx) {
// Phase 1: Resolve union field types in field order.
// Store all resolved field type IP indices for Phase 3.
+ // Also record field names and types in union_fields table for
+ // zirStructInitFieldType (matches Zig's loadUnionType().field_types).
InternPoolIndex resolved_field_types[128];
+ memset(resolved_field_types, 0, sizeof(resolved_field_types));
uint32_t num_resolved_fields = 0;
+ uint32_t uf_idx = sema->zcu->num_union_fields;
+ bool track_uf
+ = uf_idx < ZCU_MAX_UNION_TYPES && fields_len <= ZCU_MAX_UNION_FIELDS;
+ if (track_uf) {
+ sema->zcu->union_fields[uf_idx].union_ip = nav->resolved_type;
+ sema->zcu->union_fields[uf_idx].field_count = fields_len;
+ }
{
uint32_t field_extra = extra_index + decls_len + body_len;
uint32_t num_bit_bags = (fields_len + 7) / 8;
@@ -5143,20 +5333,39 @@ static void resolveUnionFullyC(Sema* sema, uint32_t nav_idx) {
bool ft_has_type = (bits & 1) != 0;
bool ft_has_align = (bits & 2) != 0;
bool ft_has_value = (bits & 4) != 0;
- cursor++; // skip field_name
+
+ // Read and record field name.
+ uint32_t field_name_zir = zir->extra[cursor];
+ cursor++;
+ if (track_uf && fi < ZCU_MAX_UNION_FIELDS) {
+ const char* fname
+ = (const char*)&zir->string_bytes[field_name_zir];
+ sema->zcu->union_fields[uf_idx].field_names[fi]
+ = ipGetOrPutString(sema->ip, fname);
+ }
+
+ InternPoolIndex field_type_ip = IP_INDEX_NONE;
if (ft_has_type) {
ZirInstRef type_ref = zir->extra[cursor++];
- InternPoolIndex resolved
+ field_type_ip
= resolveZirTypeRef(sema, zir, type_ref, ns_idx, file_idx);
- if (resolved != IP_INDEX_NONE && num_resolved_fields < 128)
- resolved_field_types[num_resolved_fields++] = resolved;
+ if (field_type_ip != IP_INDEX_NONE
+ && num_resolved_fields < 128)
+ resolved_field_types[num_resolved_fields++]
+ = field_type_ip;
}
+ if (track_uf && fi < ZCU_MAX_UNION_FIELDS)
+ sema->zcu->union_fields[uf_idx].field_types[fi]
+ = field_type_ip;
+
if (ft_has_align)
cursor++;
if (ft_has_value)
cursor++;
}
}
+ if (track_uf)
+ sema->zcu->num_union_fields++;
// Phase 2: Tag values and tag enum for tagged unions.
if (auto_enum_tag || has_tag_type) {
@@ -10664,13 +10873,14 @@ static AirInstRef zirDeclLiteralComptime(Sema* sema, uint32_t inst) {
const char* field_name
= (const char*)&sema->code.string_bytes[field_name_start];
- // General case: resolve the LHS type and look up the field name
- // in its namespace. Ported from Sema.zig zirDeclLiteral →
- // namespaceLookupVal which evaluates the declaration.
+ // Resolve the LHS type. Ported from Sema.zig zirDeclLiteral →
+ // resolveTypeOrPoison → fieldVal.
AirInstRef lhs_result = resolveInst(sema, lhs_ref);
if (AIR_REF_IS_IP(lhs_result)) {
InternPoolIndex lhs_ip = AIR_REF_TO_IP(lhs_result);
- // The LHS should be a type. Find its namespace.
+
+ // Try namespace lookup first (for declarations within the type).
+ // Ported from Sema.zig fieldVal → namespaceLookupVal.
uint32_t ns = findNamespaceForType(sema, lhs_ip);
if (ns != UINT32_MAX) {
uint32_t nav = findNavInNamespace(sema, ns, field_name);
@@ -10680,6 +10890,34 @@ static AirInstRef zirDeclLiteralComptime(Sema* sema, uint32_t inst) {
return AIR_REF_FROM_IP(val);
}
}
+
+ // For enum types: look up field name as enum tag value.
+ // Ported from Sema.zig fieldVal .enum case →
+ // enumFieldIndex + enumValueFieldIndex.
+ InternPoolKey lhs_key = sema->ip->items[lhs_ip];
+ if (lhs_key.tag == IP_KEY_ENUM_TYPE) {
+ uint32_t name_ip = ipGetOrPutString(sema->ip, field_name);
+ for (uint32_t ei = 0; ei < sema->zcu->num_enum_fields; ei++) {
+ if (sema->zcu->enum_fields[ei].enum_ip != lhs_ip)
+ continue;
+ for (uint32_t fi = 0;
+ fi < sema->zcu->enum_fields[ei].field_count; fi++) {
+ if (sema->zcu->enum_fields[ei].field_names[fi] != name_ip)
+ continue;
+ // Found the field. Create enum_tag entry.
+ // Ported from PerThread.zig enumValueFieldIndex.
+ InternPoolIndex tag_int
+ = sema->zcu->enum_fields[ei].field_tag_ints[fi];
+ InternPoolKey ek;
+ memset(&ek, 0, sizeof(ek));
+ ek.tag = IP_KEY_ENUM_TAG;
+ ek.data.enum_tag.ty = lhs_ip;
+ ek.data.enum_tag.int_val = tag_int;
+ return AIR_REF_FROM_IP(ipIntern(sema->ip, ek));
+ }
+ break; // found enum but not the field
+ }
+ }
}
UNIMPLEMENTED("zirDeclLiteralComptime: field lookup failed");
@@ -13233,12 +13471,66 @@ bool analyzeBodyInner(
// Validation-only struct init instructions: no-op.
case ZIR_INST_VALIDATE_STRUCT_INIT_TY:
case ZIR_INST_VALIDATE_STRUCT_INIT_RESULT_TY:
- case ZIR_INST_STRUCT_INIT_FIELD_TYPE:
instMapPut(
&sema->inst_map, inst, AIR_REF_FROM_IP(IP_INDEX_VOID_TYPE));
i++;
continue;
+ // struct_init_field_type: resolve the type of a struct field.
+ // Ported from Sema.zig zirStructInitFieldType → fieldType.
+ case ZIR_INST_STRUCT_INIT_FIELD_TYPE: {
+ uint32_t pi = sema->code.inst_datas[inst].pl_node.payload_index;
+ ZirInstRef container_ref = sema->code.extra[pi];
+ uint32_t name_start = sema->code.extra[pi + 1];
+ const char* fname
+ = (const char*)&sema->code.string_bytes[name_start];
+ AirInstRef ctype = resolveInst(sema, container_ref);
+ AirInstRef result = AIR_REF_FROM_IP(IP_INDEX_VOID_TYPE);
+ if (AIR_REF_IS_IP(ctype)) {
+ InternPoolIndex cip = AIR_REF_TO_IP(ctype);
+ InternPoolKeyTag ctag = sema->ip->items[cip].tag;
+ if (ctag == IP_KEY_STRUCT_TYPE) {
+ // Struct: try sema->struct_info first, then ZIR.
+ uint32_t fidx = 0;
+ const StructFieldInfo* si
+ = lookupStructField(sema, cip, fname, &fidx);
+ if (si && si->fields[fidx].type != IP_INDEX_NONE) {
+ result = AIR_REF_FROM_IP(si->fields[fidx].type);
+ } else {
+ InternPoolIndex ft
+ = lookupStructFieldTypeFromZir(sema, cip, fname);
+ if (ft != IP_INDEX_NONE)
+ result = AIR_REF_FROM_IP(ft);
+ }
+ } else if (ctag == IP_KEY_UNION_TYPE) {
+ // Union field type lookup.
+ uint32_t name_ip = ipGetOrPutString(sema->ip, fname);
+ for (uint32_t ui = 0; ui < sema->zcu->num_union_fields;
+ ui++) {
+ if (sema->zcu->union_fields[ui].union_ip != cip)
+ continue;
+ for (uint32_t uf = 0;
+ uf < sema->zcu->union_fields[ui].field_count;
+ uf++) {
+ if (sema->zcu->union_fields[ui].field_names[uf]
+ != name_ip)
+ continue;
+ InternPoolIndex ft
+ = sema->zcu->union_fields[ui].field_types[uf];
+ if (ft != IP_INDEX_NONE)
+ result = AIR_REF_FROM_IP(ft);
+ goto sift_done;
+ }
+ break;
+ }
+ }
+ }
+ sift_done:
+ instMapPut(&sema->inst_map, inst, result);
+ i++;
+ continue;
+ }
+
case ZIR_INST_VALIDATE_PTR_STRUCT_INIT:
if (block->is_comptime) {
zirValidatePtrStructInit(sema, inst);
diff --git a/stage0/zcu.h b/stage0/zcu.h
@@ -100,6 +100,34 @@ typedef struct Zcu {
// Zig's sharded IP where preamble and main shards use different nav IDs).
bool preamble_skip_ptr_nav;
+ // --- Union field info (C-specific) ---
+ // Stores field names and types for resolved union types so that
+ // zirStructInitFieldType can look up union field types by name.
+ // Ported equivalent: Zig's loadUnionType().field_types.
+#define ZCU_MAX_UNION_TYPES 64
+#define ZCU_MAX_UNION_FIELDS 256
+ struct {
+ InternPoolIndex union_ip; // union type IP index
+ uint32_t field_count;
+ uint32_t field_names[ZCU_MAX_UNION_FIELDS]; // IP string indices
+ InternPoolIndex field_types[ZCU_MAX_UNION_FIELDS]; // field type IPs
+ } union_fields[ZCU_MAX_UNION_TYPES];
+ uint32_t num_union_fields;
+
+ // --- Enum field info (C-specific) ---
+ // Stores field names and tag values for resolved enum types so that
+ // zirDeclLiteralComptime can look up enum fields by name.
+ // Ported equivalent: Zig's loadEnumType().nameIndex() + values.
+#define ZCU_MAX_ENUM_TYPES 256
+#define ZCU_MAX_ENUM_FIELDS 64
+ struct {
+ InternPoolIndex enum_ip; // enum type IP index
+ uint32_t field_count;
+ uint32_t field_names[ZCU_MAX_ENUM_FIELDS]; // IP string indices
+ InternPoolIndex field_tag_ints[ZCU_MAX_ENUM_FIELDS];
+ } enum_fields[ZCU_MAX_ENUM_TYPES];
+ uint32_t num_enum_fields;
+
// --- Compilation config ---
Compilation* comp; // back-pointer; matches Zcu.comp in Zig
} Zcu;