diff --git a/stage0/CLAUDE.md b/stage0/CLAUDE.md index 5a20812f58..f7a8fd3016 100644 --- a/stage0/CLAUDE.md +++ b/stage0/CLAUDE.md @@ -56,6 +56,15 @@ Everything else must be computed by porting the upstream functions that produce the values. Do not hand-code enum tags, field indices, type indices, or any values that come from running upstream logic. +### No workarounds — always port + +When a test fails, the only correct response is to port the upstream +implementation that makes it pass. Never work around a failure by +rearranging tests, skipping tests, adjusting test ordering, adding +special-case hacks, or any other technique that increases `num_passing` +without fixing the underlying missing functionality. Every `num_passing` +increment must represent genuinely ported upstream behavior. + ### When you see a gap When a test mismatch shows the C sema has fewer InternPool entries than @@ -122,9 +131,6 @@ C and Zig AIR must match byte-by-byte except: ## Cleaning up -Update `stage0/CLAUDE.md` with brief current status (num_passing, next -blocker) when meaningful progress is made. Keep it concise. - Before committing, ensure the branch stays green: 1. Ensure `num_passing` in `stage0/corpus.zig` only covers tests that actually @@ -170,36 +176,3 @@ Go back and fix it — never commit with fewer passing tests than before. ``` Compare C sema IP entries against the Zig compiler's dump above. -## Current status (2026-03-01) - -**num_passing = 5.** Tests 0-4 pass (empty, const_decl, empty_void_function, -type_identity_fn, reify_int). - -**Next blocker: return_integer.zig** (index 5). `export fn f() u32 { return 42; }` -interns 42:u32 at IP index 216 in the Zig compiler (func_ip=217). C sema -creates only 153 entries, putting 42:u32 at index 152 (gap = 64). - -**IP dump command (must match test's strip mode):** -``` -./zig-out/bin/zig build-obj stage0/sema_tests/return_integer.zig \ - --verbose-intern-pool -target wasm32-wasi -fno-emit-bin -OReleaseSafe -fstrip -``` - -**IP alignment progress:** Gap reduced from 64 to 3 entries. -- $124-$134: match (module loading + memoized_call) -- $135-$143: module chain (C sema creates 9 entries with 4 struct_types; - reference creates 7 entries with 3 struct_types interleaved with ptr_navs) -- $144+: analyzeMemoizedStateC resolves Signedness through SourceLocation - (first 6 builtins), creating CallingConvention enum + values and other - builtin type entries. memoized_limit=6 gives closest match. -- Remaining gap: 3 entries (C sema 42:u32 at IP 213, reference at IP 216) - -**Fixed:** CG builtin namespace collision — std/builtin.zig and CG builtin -shared same namespace index; added `has_zir` check in `ensureNavValUpToDate` -to distinguish them. - -**Next steps:** Close the remaining 3-entry gap. The module chain creates -1 extra struct_type in the C sema (4 vs 3 in reference). The reference -interleaves struct_type/ptr_nav pairs while C sema batches them. Fix the -module loading order in `resolveModuleDeclImports` and the root module -creation sequence to match the reference interleaving. diff --git a/stage0/corpus.zig b/stage0/corpus.zig index 610b3ee541..6fa6f3ad92 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 = 21; +pub const num_passing: usize = 29; pub const files = [_][]const u8{ "stage0/sema_tests/empty.zig", diff --git a/stage0/sema.c b/stage0/sema.c index 6708c5aced..d2b713b97a 100644 --- a/stage0/sema.c +++ b/stage0/sema.c @@ -682,6 +682,12 @@ static AirInstRef semaCoerceIntRef( return air_ref; // already the right type // Create a new IP entry with the target type but same value. + // Use ipForceIntern to skip deduplication: the Zig compiler's + // sharded IP creates separate entries in different shards (e.g. + // CallingConvention enum values in the memoized shard vs function + // body values in the function shard). The C sema's single IP + // would incorrectly deduplicate, returning an index from the + // memoized state instead of creating a new function-local entry. InternPoolKey new_key; memset(&new_key, 0, sizeof(new_key)); new_key.tag = IP_KEY_INT; @@ -689,7 +695,7 @@ static AirInstRef semaCoerceIntRef( new_key.data.int_val.value_lo = key.data.int_val.value_lo; new_key.data.int_val.value_hi = key.data.int_val.value_hi; new_key.data.int_val.is_negative = key.data.int_val.is_negative; - uint32_t new_ip_idx = ipIntern(sema->ip, new_key); + uint32_t new_ip_idx = ipForceIntern(sema->ip, new_key); return AIR_REF_FROM_IP(new_ip_idx); } @@ -1695,13 +1701,17 @@ static void zirTypeofLog2IntType(Sema* sema, uint32_t inst) { count++; s >>= 1; } - // Intern the log2 integer type. + // Create the log2 integer type. Use ipForceIntern to match the Zig + // compiler's sharded IP: upstream creates a fresh entry in the + // function-local shard even if the same type exists elsewhere + // (e.g. u5 may already exist from CallingConvention enum resolution + // in a different shard). InternPoolKey key; memset(&key, 0, sizeof(key)); key.tag = IP_KEY_INT_TYPE; key.data.int_type.bits = count; key.data.int_type.signedness = 0; // unsigned - InternPoolIndex ty_idx = ipIntern(sema->ip, key); + InternPoolIndex ty_idx = ipForceIntern(sema->ip, key); instMapPut(&sema->inst_map, inst, AIR_REF_FROM_IP(ty_idx)); } @@ -1752,6 +1762,10 @@ static AirInstRef zirShl( return internComptimeInt(sema, lhs_ty, r_lo, r_hi); } + // The rhs is already coerced to the correct shift type by + // typeof_log2_int_type + as_shift_operand ZIR instructions + // (AstGen forces the coercion before the shl/shr instruction). + AirInstData data; memset(&data, 0, sizeof(data)); data.bin_op.lhs = lhs; @@ -4771,12 +4785,25 @@ static InternPoolIndex ensureNavValUpToDate(uint32_t nav_idx) { FuncZirInfo fi = parseFuncZir(&tmp_sema, inst); semaDeinit(&tmp_sema); - // Parse return type. + // Parse return type. Use resolveZirTypeRef to handle + // compound types (pointers, optionals, etc.) that require + // creating IP entries, matching the Zig compiler which + // creates these entries before the func_type. InternPoolIndex ret_ty = IP_INDEX_VOID_TYPE; if (fi.ret_ty_body_len == 1) { uint32_t ret_ref = zir->extra[fi.ret_ty_ref_pos]; - if (ret_ref < ZIR_REF_START_INDEX) - ret_ty = ret_ref; + InternPoolIndex resolved + = resolveZirTypeRef(zir, ret_ref, ns_idx, file_idx); + if (resolved != IP_INDEX_NONE) + ret_ty = resolved; + } else if (fi.ret_ty_body_len == 2) { + // 2-instruction body: type instruction + break_inline. + // Resolve the first instruction as a type. + uint32_t type_inst = zir->extra[fi.ret_ty_ref_pos]; + InternPoolIndex resolved + = resolveZirTypeInst(zir, type_inst, ns_idx, file_idx); + if (resolved != IP_INDEX_NONE) + ret_ty = resolved; } // Count parameters from param_block.