From 9f641e8bef2b60ef42b59d73c0b34d5f1259a4c4 Mon Sep 17 00:00:00 2001 From: Motiejus Date: Sat, 7 Mar 2026 21:39:01 +0000 Subject: [PATCH] sema: port struct_init_field_type, enum field lookup (num_passing=5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- build.zig | 2 + stage0/corpus.zig | 2 +- stage0/sema.c | 320 ++++++++++++++++++++++++++++++++++++++++++++-- stage0/zcu.h | 28 ++++ 4 files changed, 337 insertions(+), 15 deletions(-) diff --git a/build.zig b/build.zig index 1a0972c5cd..a07b813f3e 100644 --- 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 index 767f00a0d5..99f0340e81 100644 --- 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 index b8db2bd474..7a52277e8d 100644 --- 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 index 68ebd2ec28..03d0aa2dd1 100644 --- 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;