This commit investigates ValueId(21) undefined error in Stage-B compilation.
Changes:
- src/mir/builder/builder_calls.rs:
- Add NYASH_DEBUG_PARAM_RECEIVER=1 trace for method call receivers
- Track variable_map lookups and ValueId mismatches
- Log receiver origin and current_block context
- src/mir/builder/utils.rs:
- Fix start_new_block() to avoid overwriting existing blocks
- Check if block exists before calling add_block()
- Prevents Copy instructions from being lost
- src/mir/function.rs:
- Add warning log when replacing existing block
- Helps detect block overwrite issues
- lang/src/mir/builder/ (Hako files):
- Add debug prints for method lowering paths
- These were not used (Stage-B uses Rust MIR Builder)
- Kept for future Hako MIR Builder debugging
Key Discovery:
- Stage-B compilation uses Rust MIR Builder, not Hako MIR Builder
- ValueId(21) is undefined receiver in StageBArgsBox.resolve_src/1
- MIR shows: call_method ParserBox.length() [recv: %21] but %21 has no definition
- Likely caused by LocalSSA Copy emission failure or block overwrite
Next: Fix LocalSSA to ensure receiver Copy is properly emitted and preserved
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Problem**: is_parameter() was too simple, checking only ValueId which changes
through copies/PHIs. This caused parameters like 'data' to be misclassified as
carriers, leading to incorrect PHI construction.
**Solution**: Track original parameter names at function entry.
**Changes**:
1. **Added function_param_names field** (builder.rs):
- HashSet<String> to track original parameter names
- Populated in lower_static_method_as_function()
- Cleared and repopulated for each new function
2. **Improved is_parameter()** (loop_builder.rs):
- Check name against function_param_names instead of ValueId
- More reliable than checking func.params (ValueIds change)
- __pin$*$@* variables correctly classified as carriers
- Added debug logging with NYASH_LOOPFORM_DEBUG
3. **Enhanced debug output** (loopform_builder.rs):
- Show carrier/pinned classification during prepare_structure()
- Show variable_map state after emit_header_phis()
**Test Results**:
- ✅ 'args' correctly identified as parameter (was working)
- ✅ 'data' now correctly identified as parameter (was broken)
- ✅ __pin variables correctly classified as carriers
- ✅ PHI values allocated and variable_map updated correctly
- ⚠️ ValueId undefined errors persist (separate issue)
**Remaining Issue**:
ValueId(10) undefined error suggests PHI visibility problem or VM verification
issue. Needs further investigation of emit_phi_at_block_start() or VM executor.
**Backward Compatibility**:
- Flag OFF: 100% existing behavior preserved (legacy path unchanged)
- Feature-flagged with NYASH_LOOPFORM_PHI_V2=1
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Problem**: Loop body PHIs reference ValueIds that don't exist at header exit.
Example: bb6 uses %14 from bb3, but %14 is only defined in bb6.
**Partial Fix**:
1. Create header_exit_snapshot from PHI values + new pinned variables
2. Use snapshot for loop body PHI creation instead of current variable_map
3. Use snapshot for exit PHI generation
**Progress**: Error moved from BasicBlockId(4) to BasicBlockId(3)
**Remaining**: Circular dependency - PHIs reference other PHIs in same block.
Need to ensure snapshot contains only values defined before header branch.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Goal**: Create 100-line minimal test case to reproduce SSA/ValueId
bugs in Stage-B compilation without the complexity of full compiler_stageb.hako.
**Files added**:
1. **lang/src/compiler/tests/stageb_min_sample.hako** (65 lines)
- Pattern 1: Method call in if-block before loop (TestArgs.process)
- Pattern 2: Simple method calls without loops (TestSimple.run)
- Pattern 3: Nested if/loop with method calls (TestNested.complex)
- All patterns reproduce ValueId SSA bugs
2. **tools/test_stageb_min.sh** (executable test script)
- Test 1: Direct VM execution
- Test 2: Stage-B compilation pipeline
- Test 3: MIR verification
**Test results** (as of commit):
Test 1 (Direct VM):
```
❌ ValueId(14) error in TestArgs.process/1
(different from ValueId(17) in Stage-B!)
```
Test 2 (Stage-B):
```
❌ ValueId(17) error in StageBArgsBox.resolve_src/1
(expected, same as full compiler_stageb.hako)
```
Test 3 (MIR verification):
```
✅ No verification errors
(verifier doesn't catch these specific SSA bugs)
```
**Findings**:
- Multiple ValueId SSA bugs exist (14, 17, etc.)
- MIR verifier needs enhancement to catch receiver use-before-def
- Minimal harness successfully reproduces issues for easier debugging
**Next steps** (not in this commit):
- Fix ValueId(14) in TestArgs.process
- Fix ValueId(17) in StageBArgsBox.resolve_src
- Enhance MIR verifier to catch Method receiver SSA bugs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Problem**: NewBox for static boxes (e.g., HakoCli) failed with
"Plugins disabled, cannot create HakoCli" error when NYASH_DISABLE_PLUGINS=1.
**Root cause**: static box declarations were only registered with
VM's register_static_box_decl for singleton persistence, but NOT
included in InlineUserBoxFactory's decls. This caused UnifiedBoxRegistry
to skip User factory and fall through to Plugin factory, which failed
when plugins were disabled.
**Fix**: Include static_box_decls in InlineUserBoxFactory's decls map.
Static boxes remain singletons (VM ensures single instance via
register_static_box_decl), but now they're also advertised by User factory
so NewBox doesn't incorrectly route to Plugin factory.
**Implementation** (src/runner/modes/vm.rs):
1. Add static_box_decls to InlineUserBoxFactory decls after nonstatic_decls
2. nonstatic takes precedence if name conflicts (rare but possible)
3. StaticName -> StaticNameInstance aliases still work as before
**Behavior** (UnifiedBoxRegistry with StrictPluginFirst policy):
- try factory#1 Plugin → fails (NYASH_DISABLE_PLUGINS=1)
- try factory#2 User → succeeds (static box found in decls)
- NewBox creates InstanceBox, VM ensures singleton semantics
**Verification**:
- Test case: /tmp/test_static_newbox.hako
- NewBox TestStatic successfully creates instance via User factory
- Methods callable, return values correct
- No plugin errors with NYASH_DISABLE_PLUGINS=1
**Impact**: static box new instances now work correctly without plugins,
matching Rust layer's design where static boxes are user-defined types
(not plugin types) with singleton semantics.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Problem**: "use of undefined value ValueId(22)" errors in Stage-B
when method receivers were used after loops.
**Root cause**: prepare_loop_variables_with() explicitly skipped
pinned variables (like __pin$6438$@recv) when creating loop header
PHIs. Comment claimed they were "materialized via entry-phi in loop
body" but this only happened for the body block, not for loop exit
merge points. When receivers were used after loops, there was no PHI
to merge values from different control flow paths.
**Fix**: Remove the skip logic (lines 174-177) so pinned variables
get PHIs at loop headers and exits like any other variable.
**Impact**:
- ValueId(22) error eliminated in Stage-B selfhost tests
- MIR verification passes with NYASH_VM_VERIFY_MIR=1
- Pinned slots now correctly merge across loop boundaries
- No more "use of undefined value" for cross-loop receivers
**Test case**: /tmp/test_loop_recv.hako demonstrates receiver usage
in loop and after loop exit, now works correctly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Phase 25.1b: Complete SSA fix - eliminate all global ValueId usage in function contexts.
Root cause: ~75 locations throughout MIR builder were using global value
generator (self.value_gen.next()) instead of function-local allocator
(f.next_value_id()), causing SSA verification failures and runtime
"use of undefined value" errors.
Solution:
- Added next_value_id() helper that automatically chooses correct allocator
- Fixed 19 files with ~75 occurrences of ValueId allocation
- All function-context allocations now use function-local IDs
Files modified:
- src/mir/builder/utils.rs: Added next_value_id() helper, fixed 8 locations
- src/mir/builder/builder_calls.rs: 17 fixes
- src/mir/builder/ops.rs: 8 fixes
- src/mir/builder/stmts.rs: 7 fixes
- src/mir/builder/emission/constant.rs: 6 fixes
- src/mir/builder/rewrite/*.rs: 10 fixes
- + 13 other files
Verification:
- cargo build --release: SUCCESS
- Simple tests with NYASH_VM_VERIFY_MIR=1: Zero undefined errors
- Multi-parameter static methods: All working
Known remaining: ValueId(22) in Stage-B (separate issue to investigate)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Phase 25.1b: Resolve 'Undefined value %0' errors in static methods.
Root cause: Parameters and constants were allocated using global value
generator (self.value_gen.next()) instead of function-local allocator
(f.next_value_id()), causing SSA verification failures.
Fixes:
- src/mir/builder/decls.rs: Use function-local ID for parameter binding
- src/mir/builder/emission/constant.rs: Context-aware constant emission
- src/mir/builder/builder_calls.rs: Function-local param allocation
Verification:
- NYASH_VM_VERIFY_MIR=1: Zero 'Undefined value %0' errors
- Simple test cases: All pass
- Stage-B compiler: %0 errors completely resolved
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add propagate_copy_types() to track MatI64 through copy instructions
- Fix PHI detection bug: indexOf("{") → indexOf("\"op\":\"")
- Add 4-iteration loop for multi-step propagation chains
- Enhance diagnostics with MatI64 vids list and skip reasons
This fixes type propagation for complex SSA patterns where MatI64 types
flow through multiple copy and phi instructions. Small test cases now
pass successfully.
Note: microbench matmul_core still has issues - investigating separately.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## Problem
Phase 25 numeric_core transformation wasn't working in microbench.sh:
- NYASH_AOT_NUMERIC_CORE=1 was set by user externally
- But wasn't propagated to hakorune_emit_mir.sh
- Result: BoxCall(mul_naive) remained instead of Call("NyNumericMatI64.mul_naive")
## Solution
Add explicit env var propagation in microbench.sh (line 933-934):
```bash
NYASH_AOT_NUMERIC_CORE="${NYASH_AOT_NUMERIC_CORE:-0}" \
NYASH_AOT_NUMERIC_CORE_TRACE="${NYASH_AOT_NUMERIC_CORE_TRACE:-0}" \
```
This ensures user-set NYASH_AOT_NUMERIC_CORE is passed through to:
hakorune_emit_mir.sh → Provider → AotPrep → numeric_core.hako
## Verification
Tested with:
```bash
NYASH_AOT_NUMERIC_CORE=1 tools/perf/microbench.sh --case matmul_core --backend llvm --exe --runs 1 --n 4
```
Now transformation works correctly (pending numeric_core phi propagation fix).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Complete imports pipeline by extracting using statements and passing to MirBuilder:
- Extract "using X as Y" from source file using grep/sed
- Build JSON map {"Y":"Y"} for all imports
- Set HAKO_MIRBUILDER_IMPORTS environment variable
- extern_provider reads this and passes to program_json_to_mir_json_with_imports()
- MapVars::resolve() recognizes MatI64 as valid static box reference
Test result:
✅ /tmp/test_imports.hako (using MatI64) → MIR JSON generated without errors
✅ No "undefined variable: MatI64" error
✅ boxcall with MatI64.new() properly resolved
Phase 21.8 Step 7 complete - imports pipeline fully functional!
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Step 6 Complete**: Extract using imports from .hako source in pipeline
Changes:
- Modified collect_using_and_strip() to return (cleaned, prelude_paths, imports)
- Build imports HashMap from seen_aliases (alias -> alias mapping)
- Updated all 6 call sites to handle new 3-tuple return type
- This provides MirBuilder with info about which names are valid static box references
Next: Wire imports through extern_provider to MirBuilder (Step 7)
Related: #phase-21.8 MatI64/IntArrayCore integration
Phase 21.8 foundation for MatI64/IntArrayCore integration
Changes:
- Add `imports: HashMap<String, String>` to BridgeEnv
- Extend MapVars::resolve() to check imports and create static box references
- Add BridgeEnv::with_imports() to initialize with using imports map
- Add parse_json_v0_to_module_with_imports() to json_v0_bridge
- Add program_json_to_mir_json_with_imports() to mir_builder.rs
- Maintain backward compatibility via empty HashMap defaults
Next: Wire using extraction from pipeline and test with MatI64
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fix CollectionsHot type_table to resolve copy chains when propagating types
through PHI nodes, enabling ArrayBox optimization in deeply nested loops (matmul).
**Root Cause:**
- PHI incoming values were checked directly in type_table
- Copy chains (e.g., SSA 56→11) were not resolved
- Types failed to propagate through deep loop nesting
**Solution:**
- Add copy_src_map parameter to build_type_table()
- Resolve copy chains before checking if PHI incoming values are typed
- Multi-pass fixpoint algorithm (up to 12 passes) ensures convergence
**Impact:**
- matmul: ArrayBox A/B/C now propagate through nested loops
- Expected: boxcall→externcall conversion for get/set/push
- Backwards compatible: opt-in (NYASH_AOT_COLLECTIONS_HOT=1), no behavior change when disabled
**Analysis:**
- Documented in docs/development/analysis/matmul_collections_hot_fix.md
- Box→SSA flow traced: A(dst=2)→...→71(get), B(dst=3)→...→73(get), C(dst=4)→...→105(set)
**Testing:** Pending verification due to VM step budget constraints
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Root Cause:
- Else blocks were not propagating variable assignments to outer scope
- Bug 1 (if_form.rs): PHI materialization happened before variable_map reset,
causing PHI nodes to be lost
- Bug 2 (phi.rs): Variable merge didn't check if else branch modified variables
Changes:
- src/mir/builder/if_form.rs:93-127
- Reordered: reset variable_map BEFORE materializing PHI nodes
- Now matches then-branch pattern (reset → materialize → execute)
- Applied to both "else" and "no else" branches for consistency
- src/mir/builder/phi.rs:137-154
- Added else_modified_var check to detect variable modifications
- Use modified value from else_var_map_end_opt when available
- Fall back to pre-if value only when truly not modified
Test Results:
✅ Simple block: { x=42 } → 42
✅ If block: if 1 { x=42 } → 42
✅ Else block: if 0 { x=99 } else { x=42 } → 42 (FIXED!)
✅ Stage-B body extraction: "return 42" correctly extracted (was null)
Impact:
- Else block variable assignments now work correctly
- Stage-B compiler body extraction restored
- Selfhost builder path can now function
- Foundation for Phase 21.x progress
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed:
- Environment::set now properly searches ancestor chain before creating new binding
- Added exists_in_chain_locked() helper for explicit existence checking
- Simple {} blocks now correctly update outer scope variables
Verified Working:
- local x = 10; { x = 42 }; print(x) → prints 42 ✅
Still Broken:
- else blocks don't update outer scope variables
- local x = 10; if flag { x = 99 } else { x = 42 }; print(x) → prints 10 ❌
Root Cause Identified:
- Issue is in MIR Builder (compile-time), not Environment (runtime)
- src/mir/builder/if_form.rs:108 resets variable_map before else block
- PHI generation at merge doesn't use else_var_map_end correctly
- MIR shows: phi [%32, bb1], [%1, bb2] where %1 is original value, not else value
Next: Fix else block variable merging in if_form.rs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented:
- UsingResolverBox full implementation in using_resolver_box.hako
- state_new(): Empty state creation
- load_modules_json(): Load modules JSON from nyash.toml
- resolve_path_alias(): Resolve paths from aliases
- resolve_namespace_alias(): Tail segment matching with case-insensitive support
- to_context_json(): Generate context JSON for ParserBox
- Added sh_core entry to nyash.toml modules section
- Maps to lang/src/shared/common/string_helpers.hako
- Fixes "using not found: 'sh_core'" errors
- Cleaned up compiler_stageb.hako
- Removed problematic using statements
- Added documentation
Known Issue (to be fixed next):
- Body extraction bug in compiler_stageb.hako:51-197
- Multiline source extraction fails for "static box Main { main() {...} }"
- Results in empty Program JSON body
- Causes Stage-B emit pipeline to fall back to jsonfrag (ratio=207900%)
- This is the root cause blocking selfhost builder path
Impact:
- ✅ sh_core resolution errors fixed
- ✅ UsingResolverBox infrastructure complete
- ❌ Stage-B emit pipeline not restored (body extraction bug)
- ❌ Selfhost builder path still blocked
Next Priority: Fix body extraction bug to restore Stage-B pipeline
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>