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>
This commit is contained in:
2026-03-07 21:39:01 +00:00
parent 8a809099b0
commit 9f641e8bef
4 changed files with 337 additions and 15 deletions

View File

@@ -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]})));
}

View File

@@ -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",

View File

@@ -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);

View File

@@ -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;