diff --git a/local_tests/phase161/README.md b/local_tests/phase161/README.md new file mode 100644 index 00000000..2edd1102 --- /dev/null +++ b/local_tests/phase161/README.md @@ -0,0 +1,213 @@ +# Phase 161 Representative Functions Test Suite + +This directory contains 5 representative Nyash functions that exercise all key analyzer patterns for the Phase 161 JoinIR/MIR to .hako migration. + +## Overview + +These functions are used to validate Phase 161-2+ implementation of MirAnalyzerBox and related analyzer infrastructure. Each function is carefully designed to cover a specific control flow pattern. + +## Representative Functions + +### 1. rep1_if_simple.hako +**Pattern**: Simple if/else with PHI merge +**Complexity**: ⭐ Simple +**Tests**: +- Branch detection +- If-merge identification +- Single PHI instruction +- PHI incoming values from both branches + +**Expected MIR Analysis**: +- 1 PHI instruction +- 1 Branch instruction +- 1 If structure detected + +--- + +### 2. rep2_loop_simple.hako +**Pattern**: Simple loop with back edge +**Complexity**: ⭐ Simple +**Tests**: +- Loop detection via backward edge +- Loop-carried PHI at header +- Back edge identification +- Loop body block identification + +**Expected MIR Analysis**: +- 1 Loop detected +- 1 PHI instruction (at loop header) +- Backward edge: Block 2 → Block 1 + +--- + +### 3. rep3_if_loop.hako +**Pattern**: Nested if inside loop +**Complexity**: ⭐⭐ Medium +**Tests**: +- Complex nested control flow +- Multiple PHI instructions (loop PHI + if PHI) +- Interaction between loop and if patterns +- Correct block hierarchy + +**Expected MIR Analysis**: +- 1 Loop detected (loop header) +- 1 If detected (nested within loop body) +- 3 PHI instructions total: + - 2 at loop header (for loop carries) + - 1 at if merge point + +--- + +### 4. rep4_loop_break.hako +**Pattern**: Loop with break statement +**Complexity**: ⭐⭐ Medium +**Tests**: +- Loop with multiple exits +- Break target resolution +- Exit PHI merge with multiple incoming paths +- Complex control flow merging + +**Expected MIR Analysis**: +- 1 Loop detected +- Multiple exit paths from loop +- Break condition identified +- Exit merge block identified + +--- + +### 5. rep5_type_prop.hako +**Pattern**: Type propagation through loop +**Complexity**: ⭐⭐ Medium +**Tests**: +- Type inference through PHI chains +- BinOp type preservation +- Type consistency across loop iterations +- Compare operation type hints + +**Expected MIR Analysis**: +- Type propagation converges +- All ValueIds have consistent types +- PHI merges compatible types +- 4-iteration propagation completes + +--- + +## Generating MIR JSON + +To generate reference MIR JSON for each representative: + +```bash +./target/release/nyash --dump-mir --emit-mir-json rep1_if_simple.mir.json rep1_if_simple.hako +./target/release/nyash --dump-mir --emit-mir-json rep2_loop_simple.mir.json rep2_loop_simple.hako +./target/release/nyash --dump-mir --emit-mir-json rep3_if_loop.mir.json rep3_if_loop.hako +./target/release/nyash --dump-mir --emit-mir-json rep4_loop_break.mir.json rep4_loop_break.hako +./target/release/nyash --dump-mir --emit-mir-json rep5_type_prop.mir.json rep5_type_prop.hako +``` + +This creates the reference `.mir.json` files that MirAnalyzerBox will parse. + +--- + +## Testing MirAnalyzerBox + +Phase 161-2+ will implement analyzer methods that should produce these results: + +### rep1_if_simple + +``` +MirAnalyzerBox analyzer("rep1_if_simple.mir.json", text) +analyzer.list_phis() → [{ block_id: 4, dest: ValueId, incoming: [...] }] +analyzer.list_ifs() → [{ condition_block: 1, merge_block: 4, ... }] +analyzer.summarize_function(0) → { has_phis: true, has_ifs: true, ... } +``` + +### rep2_loop_simple + +``` +analyzer.list_loops() → [{ header_block: 1, contains_blocks: [1,2], ... }] +analyzer.list_phis() → [{ block_id: 1, dest: ValueId, incoming: [...] }] +analyzer.summarize_function(0) → { has_loops: true, has_phis: true, ... } +``` + +### rep3_if_loop + +``` +analyzer.list_loops() → 1 loop +analyzer.list_ifs() → 1 if (nested in loop body) +analyzer.list_phis() → 3 PHI instructions +``` + +### rep4_loop_break + +``` +analyzer.list_loops() → 1 loop with multiple exits +analyzer.list_ifs() → 1 if (for break condition) +``` + +### rep5_type_prop + +``` +analyzer.propagate_types(0) → { ValueId: "i64", ... } +// All types should be consistent, no conflicts +``` + +--- + +## Structure of MIR JSON + +Each `rep_N.mir.json` follows the schema defined in [phase161_joinir_analyzer_design.md](../../docs/development/current/main/phase161_joinir_analyzer_design.md): + +```json +{ + "schema_version": "1", + "functions": [ + { + "name": "main", + "blocks": [ + { + "id": 0, + "instructions": [ + { + "op": "const", + "dest": 1, + "value": 0 + }, + ... + ] + }, + ... + ], + "cfg": { + "entry": 0, + "targets": { "0": [1], "1": [2, 3], ... } + } + } + ] +} +``` + +--- + +## Phase 161 Roadmap Usage + +These representatives are used in: + +1. **Phase 161-2**: Basic MirAnalyzerBox structure + - Implement on rep1 and rep2 (simple patterns) + +2. **Phase 161-3**: PHI/Loop/If detection + - Full testing on all 5 representatives + +3. **Phase 161-4**: Type propagation + - Validate rep5_type_prop + +4. **Phase 161-5**: Full test suite + - All representatives passing all analyzer methods + +--- + +## References + +- [phase161_analyzer_box_design.md](../../docs/development/current/main/phase161_analyzer_box_design.md) - Analyzer Box design +- [phase161_representative_functions.md](../../docs/development/current/main/phase161_representative_functions.md) - Function selection criteria +- [phase161_joinir_analyzer_design.md](../../docs/development/current/main/phase161_joinir_analyzer_design.md) - JSON schema reference diff --git a/local_tests/phase161/rep1_if_simple.hako b/local_tests/phase161/rep1_if_simple.hako new file mode 100644 index 00000000..4476bb74 --- /dev/null +++ b/local_tests/phase161/rep1_if_simple.hako @@ -0,0 +1,18 @@ +// Phase 161 Representative Function 1: Simple If/Else with PHI Merge +// Pattern: Basic if/else statement that generates single PHI merge +// Tests: Branch detection, if-merge identification, single PHI + +box Main { + main() { + local x = 5 + local result + + if x > 3 { + result = 10 + } else { + result = 20 + } + + print(result) + } +} diff --git a/local_tests/phase161/rep1_if_simple.mir.json b/local_tests/phase161/rep1_if_simple.mir.json new file mode 100644 index 00000000..404064dd --- /dev/null +++ b/local_tests/phase161/rep1_if_simple.mir.json @@ -0,0 +1,426 @@ +{ + "capabilities": [ + "unified_call", + "phi", + "effects", + "callee_typing" + ], + "cfg": { + "functions": [ + { + "blocks": [ + { + "id": 0, + "reachable": false, + "successors": [], + "terminator": "Return" + } + ], + "entry_block": 0, + "name": "condition_fn" + }, + { + "blocks": [ + { + "id": 12, + "reachable": false, + "successors": [], + "terminator": "Return" + } + ], + "entry_block": 12, + "name": "Main.equals/1" + }, + { + "blocks": [ + { + "id": 7, + "reachable": false, + "successors": [ + 8, + 9 + ], + "terminator": "Branch" + }, + { + "id": 8, + "reachable": false, + "successors": [ + 10 + ], + "terminator": "Jump" + }, + { + "id": 9, + "reachable": false, + "successors": [ + 10 + ], + "terminator": "Jump" + }, + { + "id": 10, + "reachable": false, + "successors": [], + "terminator": "Return" + } + ], + "entry_block": 7, + "name": "Main.main/0" + }, + { + "blocks": [ + { + "id": 0, + "reachable": false, + "successors": [], + "terminator": "Return" + } + ], + "entry_block": 0, + "name": "main" + }, + { + "blocks": [ + { + "id": 11, + "reachable": false, + "successors": [], + "terminator": "Return" + } + ], + "entry_block": 11, + "name": "Main.toString/0" + } + ] + }, + "functions": [ + { + "blocks": [ + { + "id": 0, + "instructions": [ + { + "dst": 1, + "op": "const", + "value": { + "type": "i64", + "value": 1 + } + }, + { + "op": "ret", + "value": 1 + } + ] + } + ], + "name": "condition_fn", + "params": [ + 0 + ] + }, + { + "blocks": [ + { + "id": 12, + "instructions": [ + { + "dst": 3, + "op": "const", + "value": { + "type": "i64", + "value": 1 + } + }, + { + "op": "ret", + "value": 3 + } + ] + } + ], + "name": "Main.equals/1", + "params": [ + 0, + 1, + 2 + ] + }, + { + "blocks": [ + { + "id": 7, + "instructions": [ + { + "dst": 6, + "op": "copy", + "src": 3 + }, + { + "dst": 9, + "op": "copy", + "src": 6 + }, + { + "dst": 10, + "op": "copy", + "src": 7 + }, + { + "dst": 12, + "op": "copy", + "src": 11 + }, + { + "dst": 2, + "op": "const", + "value": { + "type": "i64", + "value": 5 + } + }, + { + "dst": 5, + "op": "const", + "value": { + "type": "i64", + "value": 3 + } + }, + { + "dst": 6, + "op": "copy", + "src": 3 + }, + { + "dst": 9, + "op": "copy", + "src": 6 + }, + { + "dst": 10, + "op": "copy", + "src": 7 + }, + { + "dst": 8, + "lhs": 9, + "op": "compare", + "operation": ">", + "rhs": 10 + }, + { + "dst": 12, + "op": "copy", + "src": 11 + }, + { + "dst": 3, + "op": "copy", + "src": 2 + }, + { + "dst": 7, + "op": "copy", + "src": 5 + }, + { + "dst": 11, + "op": "copy", + "src": 8 + }, + { + "cond": 12, + "else": 9, + "op": "branch", + "then": 8 + } + ] + }, + { + "id": 8, + "instructions": [ + { + "dst": 18, + "op": "const", + "value": { + "type": "i64", + "value": 10 + } + }, + { + "op": "jump", + "target": 10 + } + ] + }, + { + "id": 9, + "instructions": [ + { + "dst": 24, + "op": "const", + "value": { + "type": "i64", + "value": 20 + } + }, + { + "op": "jump", + "target": 10 + } + ] + }, + { + "id": 10, + "instructions": [ + { + "dst": 30, + "incoming": [ + [ + 18, + 8 + ], + [ + 24, + 9 + ] + ], + "op": "phi" + }, + { + "dst": null, + "mir_call": { + "args": [ + 31 + ], + "callee": { + "name": "print", + "type": "Global" + }, + "effects": [ + "IO" + ], + "flags": {} + }, + "op": "mir_call" + }, + { + "dst": 32, + "op": "const", + "value": { + "type": "void", + "value": 0 + } + }, + { + "dst": 31, + "op": "copy", + "src": 30 + }, + { + "op": "ret", + "value": 32 + } + ] + } + ], + "name": "Main.main/0", + "params": [ + 0, + 1 + ] + }, + { + "blocks": [ + { + "id": 0, + "instructions": [ + { + "dst": 9, + "op": "const", + "value": { + "type": "void", + "value": 0 + } + }, + { + "op": "ret", + "value": 9 + } + ] + } + ], + "name": "main", + "params": [] + }, + { + "blocks": [ + { + "id": 11, + "instructions": [ + { + "dst": 2, + "op": "const", + "value": { + "type": { + "box_type": "StringBox", + "kind": "handle" + }, + "value": "Main(" + } + }, + { + "dst": 3, + "op": "const", + "value": { + "type": { + "box_type": "StringBox", + "kind": "handle" + }, + "value": ")" + } + }, + { + "dst": 6, + "lhs": 4, + "op": "binop", + "operation": "+", + "rhs": 5 + }, + { + "dst": 4, + "op": "copy", + "src": 2 + }, + { + "dst": 5, + "op": "copy", + "src": 3 + }, + { + "op": "ret", + "value": 6 + } + ] + } + ], + "name": "Main.toString/0", + "params": [ + 0, + 1 + ] + } + ], + "metadata": { + "build_time": "Phase 15.5 Development", + "features": [ + "mir_call_unification", + "json_v1_schema" + ], + "generator": "nyash-rust", + "phase": "15.5" + }, + "schema_version": "1.0" +} \ No newline at end of file diff --git a/local_tests/phase161/rep2_loop_simple.hako b/local_tests/phase161/rep2_loop_simple.hako new file mode 100644 index 00000000..5eb04a51 --- /dev/null +++ b/local_tests/phase161/rep2_loop_simple.hako @@ -0,0 +1,14 @@ +// Phase 161 Representative Function 2: Simple Loop with Back Edge +// Pattern: Basic loop statement that generates back edge and loop-carried PHI +// Tests: Loop detection, back edge identification, loop-carried PHI + +box Main { + main() { + local i = 0 + + loop(i < 10) { + print(i) + i = i + 1 + } + } +} diff --git a/local_tests/phase161/rep2_loop_simple.mir.json b/local_tests/phase161/rep2_loop_simple.mir.json new file mode 100644 index 00000000..e7549503 --- /dev/null +++ b/local_tests/phase161/rep2_loop_simple.mir.json @@ -0,0 +1,481 @@ +{ + "capabilities": [ + "unified_call", + "phi", + "effects", + "callee_typing" + ], + "cfg": { + "functions": [ + { + "blocks": [ + { + "id": 10, + "reachable": false, + "successors": [ + 11 + ], + "terminator": "Jump" + }, + { + "id": 11, + "reachable": false, + "successors": [ + 12 + ], + "terminator": "Jump" + }, + { + "id": 12, + "reachable": false, + "successors": [ + 13, + 15 + ], + "terminator": "Branch" + }, + { + "id": 13, + "reachable": false, + "successors": [ + 14 + ], + "terminator": "Jump" + }, + { + "id": 14, + "reachable": false, + "successors": [ + 12 + ], + "terminator": "Jump" + }, + { + "id": 15, + "reachable": false, + "successors": [], + "terminator": "Return" + }, + { + "id": 16, + "reachable": false, + "successors": [ + 12 + ], + "terminator": "Jump" + } + ], + "entry_block": 10, + "name": "Main.main/0" + }, + { + "blocks": [ + { + "id": 17, + "reachable": false, + "successors": [], + "terminator": "Return" + } + ], + "entry_block": 17, + "name": "Main.equals/1" + }, + { + "blocks": [ + { + "id": 18, + "reachable": false, + "successors": [], + "terminator": "Return" + } + ], + "entry_block": 18, + "name": "Main.toString/0" + }, + { + "blocks": [ + { + "id": 0, + "reachable": false, + "successors": [], + "terminator": "Return" + } + ], + "entry_block": 0, + "name": "condition_fn" + }, + { + "blocks": [ + { + "id": 0, + "reachable": false, + "successors": [], + "terminator": "Return" + } + ], + "entry_block": 0, + "name": "main" + } + ] + }, + "functions": [ + { + "blocks": [ + { + "id": 10, + "instructions": [ + { + "dst": 2, + "op": "const", + "value": { + "type": "i64", + "value": 0 + } + }, + { + "dst": 3, + "op": "copy", + "src": 2 + }, + { + "op": "jump", + "target": 11 + } + ] + }, + { + "id": 11, + "instructions": [ + { + "dst": 4, + "op": "copy", + "src": 3 + }, + { + "dst": 4, + "op": "copy", + "src": 3 + }, + { + "op": "jump", + "target": 12 + } + ] + }, + { + "id": 12, + "instructions": [ + { + "dst": 5, + "incoming": [ + [ + 4, + 11 + ], + [ + 18, + 14 + ] + ], + "op": "phi" + }, + { + "dst": 12, + "op": "copy", + "src": 9 + }, + { + "dst": 13, + "op": "copy", + "src": 10 + }, + { + "dst": 8, + "op": "const", + "value": { + "type": "i64", + "value": 10 + } + }, + { + "dst": 12, + "op": "copy", + "src": 9 + }, + { + "dst": 13, + "op": "copy", + "src": 10 + }, + { + "dst": 11, + "lhs": 12, + "op": "compare", + "operation": "<", + "rhs": 13 + }, + { + "dst": 9, + "op": "copy", + "src": 5 + }, + { + "dst": 10, + "op": "copy", + "src": 8 + }, + { + "dst": 14, + "op": "copy", + "src": 11 + }, + { + "cond": 14, + "else": 15, + "op": "branch", + "then": 13 + } + ] + }, + { + "id": 13, + "instructions": [ + { + "dst": 16, + "op": "copy", + "src": 5 + }, + { + "dst": null, + "mir_call": { + "args": [ + 9 + ], + "callee": { + "name": "print", + "type": "Global" + }, + "effects": [ + "IO" + ], + "flags": {} + }, + "op": "mir_call" + }, + { + "dst": 15, + "op": "const", + "value": { + "type": "i64", + "value": 1 + } + }, + { + "dst": 16, + "op": "copy", + "src": 5 + }, + { + "dst": 18, + "lhs": 16, + "op": "binop", + "operation": "+", + "rhs": 17 + }, + { + "dst": 17, + "op": "copy", + "src": 15 + }, + { + "op": "jump", + "target": 14 + } + ] + }, + { + "id": 14, + "instructions": [ + { + "op": "jump", + "target": 12 + } + ] + }, + { + "id": 15, + "instructions": [ + { + "dst": 20, + "op": "const", + "value": { + "type": "void", + "value": 0 + } + }, + { + "op": "ret", + "value": 20 + } + ] + }, + { + "id": 16, + "instructions": [ + { + "op": "jump", + "target": 12 + } + ] + } + ], + "name": "Main.main/0", + "params": [ + 0, + 1 + ] + }, + { + "blocks": [ + { + "id": 17, + "instructions": [ + { + "dst": 3, + "op": "const", + "value": { + "type": "i64", + "value": 1 + } + }, + { + "op": "ret", + "value": 3 + } + ] + } + ], + "name": "Main.equals/1", + "params": [ + 0, + 1, + 2 + ] + }, + { + "blocks": [ + { + "id": 18, + "instructions": [ + { + "dst": 2, + "op": "const", + "value": { + "type": { + "box_type": "StringBox", + "kind": "handle" + }, + "value": "Main(" + } + }, + { + "dst": 3, + "op": "const", + "value": { + "type": { + "box_type": "StringBox", + "kind": "handle" + }, + "value": ")" + } + }, + { + "dst": 6, + "lhs": 4, + "op": "binop", + "operation": "+", + "rhs": 5 + }, + { + "dst": 4, + "op": "copy", + "src": 2 + }, + { + "dst": 5, + "op": "copy", + "src": 3 + }, + { + "op": "ret", + "value": 6 + } + ] + } + ], + "name": "Main.toString/0", + "params": [ + 0, + 1 + ] + }, + { + "blocks": [ + { + "id": 0, + "instructions": [ + { + "dst": 1, + "op": "const", + "value": { + "type": "i64", + "value": 1 + } + }, + { + "op": "ret", + "value": 1 + } + ] + } + ], + "name": "condition_fn", + "params": [ + 0 + ] + }, + { + "blocks": [ + { + "id": 0, + "instructions": [ + { + "dst": 9, + "op": "const", + "value": { + "type": "void", + "value": 0 + } + }, + { + "op": "ret", + "value": 9 + } + ] + } + ], + "name": "main", + "params": [] + } + ], + "metadata": { + "build_time": "Phase 15.5 Development", + "features": [ + "mir_call_unification", + "json_v1_schema" + ], + "generator": "nyash-rust", + "phase": "15.5" + }, + "schema_version": "1.0" +} \ No newline at end of file diff --git a/local_tests/phase161/rep3_if_loop.hako b/local_tests/phase161/rep3_if_loop.hako new file mode 100644 index 00000000..275585c2 --- /dev/null +++ b/local_tests/phase161/rep3_if_loop.hako @@ -0,0 +1,21 @@ +// Phase 161 Representative Function 3: Nested If Inside Loop +// Pattern: If statement nested inside loop generating multiple PHI instructions +// Tests: Complex PHI detection, nested block analysis, mixed if/loop patterns + +box Main { + main() { + local i = 0 + local sum = 0 + + loop(i < 10) { + if i % 2 == 0 { + sum = sum + i + } else { + sum = sum - i + } + i = i + 1 + } + + print(sum) + } +} diff --git a/local_tests/phase161/rep4_loop_break.hako b/local_tests/phase161/rep4_loop_break.hako new file mode 100644 index 00000000..1b06a0ad --- /dev/null +++ b/local_tests/phase161/rep4_loop_break.hako @@ -0,0 +1,17 @@ +// Phase 161 Representative Function 4: Loop with Break Statement +// Pattern: Loop with break that creates multiple exit paths +// Tests: Loop with multiple exits, break target resolution, exit PHI merge + +box Main { + main() { + local i = 0 + + loop(true) { + if i == 5 { + break + } + print(i) + i = i + 1 + } + } +} diff --git a/local_tests/phase161/rep5_type_prop.hako b/local_tests/phase161/rep5_type_prop.hako new file mode 100644 index 00000000..a271ef7b --- /dev/null +++ b/local_tests/phase161/rep5_type_prop.hako @@ -0,0 +1,21 @@ +// Phase 161 Representative Function 5: Type Propagation Through Loop +// Pattern: Loop with type-carrying PHI and arithmetic operations +// Tests: Type propagation, type inference through PHI chain, BinOp type preservation + +box Main { + main() { + local x = 0 + local y = 10 + + loop(x < y) { + local z = x + 1 + if z > 5 { + x = z * 2 + } else { + x = z - 1 + } + } + + print(x) + } +} diff --git a/local_tests/phase161/test_mir_analyzer.hako b/local_tests/phase161/test_mir_analyzer.hako new file mode 100644 index 00000000..1ad5e64e --- /dev/null +++ b/local_tests/phase161/test_mir_analyzer.hako @@ -0,0 +1,170 @@ +// local_tests/phase161/test_mir_analyzer.hako +// Test harness for MirAnalyzerBox (Phase 161-2) + +// Test runner for MIR analysis +box TestRunner { + analyzer: MirAnalyzerBox + test_name: string + + birth(name, mir_json_text) { + me.test_name = name + print("==================================================") + print("Test: " + name) + print("==================================================") + + // Parse MIR JSON + me.analyzer = new MirAnalyzerBox() + local result = me.analyzer.birth(mir_json_text) + + if result == null { + print("FAIL: Failed to parse MIR JSON") + return null + } + + print("PASS: MIR JSON parsed successfully") + return me + } + + method test_validate_schema() { + print("") + print("--- Test: validateSchema() ---") + + local valid = me.analyzer.validateSchema() + + if valid == 1 { + print("PASS: Schema validation successful") + } else { + print("FAIL: Schema validation failed") + } + + return valid + } + + method test_summarize_function(funcIndex, expected_name) { + print("") + print("--- Test: summarize_function(" + funcIndex + ") ---") + + local summary = me.analyzer.summarize_function(funcIndex) + + if summary == null { + print("FAIL: summarize_function returned null") + return 0 + } + + local name = summary.get("name") + local block_count = summary.get("block_count") + local instruction_count = summary.get("instruction_count") + local has_phis = summary.get("has_phis") + local has_loops = summary.get("has_loops") + local has_ifs = summary.get("has_ifs") + + print("Function name: " + name) + print("Blocks: " + block_count) + print("Instructions: " + instruction_count) + print("Has PHI: " + has_phis) + print("Has loops: " + has_loops) + print("Has ifs: " + has_ifs) + + if name == expected_name { + print("PASS: Function name matches expected: " + expected_name) + return 1 + } else { + print("FAIL: Function name mismatch. Expected: " + expected_name + ", Got: " + name) + return 0 + } + } + + method test_count_phis(funcIndex, expected_count) { + print("") + print("--- Test: count_phis(" + funcIndex + ") ---") + + local phi_list = me.analyzer.count_phis(funcIndex) + + if phi_list == null { + print("FAIL: count_phis returned null") + return 0 + } + + local count = phi_list.size() + print("PHI count: " + count) + + if count == expected_count { + print("PASS: PHI count matches expected: " + expected_count) + + // Print details + local i = 0 + loop(i < count) { + local phi = phi_list.at(i) + local block_id = phi.get("block_id") + local dest = phi.get("dest") + local incoming_count = phi.get("incoming_count") + + print(" PHI #" + i + ": block=" + block_id + " dest=r" + dest + " incoming=" + incoming_count) + i = i + 1 + } + + return 1 + } else { + print("FAIL: PHI count mismatch. Expected: " + expected_count + ", Got: " + count) + return 0 + } + } + + method test_count_loops(funcIndex, expected_count) { + print("") + print("--- Test: count_loops(" + funcIndex + ") ---") + + local loop_count = me.analyzer.count_loops(funcIndex) + + print("Loop count: " + loop_count) + + if loop_count == expected_count { + print("PASS: Loop count matches expected: " + expected_count) + return 1 + } else { + print("FAIL: Loop count mismatch. Expected: " + expected_count + ", Got: " + loop_count) + return 0 + } + } + + method run_all_tests(funcIndex, expected_name, expected_phi_count, expected_loop_count) { + me.test_validate_schema() + me.test_summarize_function(funcIndex, expected_name) + me.test_count_phis(funcIndex, expected_phi_count) + me.test_count_loops(funcIndex, expected_loop_count) + + print("") + print("==================================================") + print("Test '" + me.test_name + "' completed") + print("==================================================") + } +} + +// Helper to read file contents (simplified for testing) +box FileHelper { + method read_file(path) { + // Placeholder: In real implementation, use FileBox + // For now, return null to indicate we need to pass content directly + print("[FileHelper] File reading not implemented yet") + return null + } +} + +// Main entry point +static box Main { + main(args) { + print("Phase 161-2: MirAnalyzerBox Test Suite") + print("") + + // Note: In actual testing, we'll load JSON files from disk + // For now, this is a skeleton that shows the test structure + + print("To run tests:") + print("1. Generate MIR JSON: ./target/release/hakorune --dump-mir --emit-mir-json rep1.mir.json rep1_if_simple.hako") + print("2. Pass JSON content to TestRunner") + print("") + print("Test structure defined successfully") + + return 0 + } +} diff --git a/local_tests/phase161/test_rep1_inline.hako b/local_tests/phase161/test_rep1_inline.hako new file mode 100644 index 00000000..aec9eadc --- /dev/null +++ b/local_tests/phase161/test_rep1_inline.hako @@ -0,0 +1,164 @@ +// local_tests/phase161/test_rep1_inline.hako +// Inline test for rep1_if_simple MIR analysis + +static box Main { + main(args) { + print("Phase 161-2: Testing MirAnalyzerBox on rep1_if_simple") + print("=======================================================") + + // Minimal MIR JSON for testing (simplified from actual rep1) + local mir_json = '{"capabilities":["unified_call","phi","effects","callee_typing"],"functions":[{"name":"Main.main/0","params":[],"entry":7,"blocks":[{"id":7,"instructions":[{"op":"const","dst":2,"value":{"type":"i64","value":5}},{"op":"const","dst":5,"value":{"type":"i64","value":3}},{"op":"compare","operation":">","lhs":9,"rhs":10,"dst":8},{"op":"branch","cond":12,"then":8,"else":9}]},{"id":8,"instructions":[{"op":"const","dst":18,"value":{"type":"i64","value":10}},{"op":"jump","target":10}]},{"id":9,"instructions":[{"op":"const","dst":24,"value":{"type":"i64","value":20}},{"op":"jump","target":10}]},{"id":10,"instructions":[{"op":"phi","dst":30,"incoming":[[18,8],[24,9]]},{"op":"ret","value":32}]}]}]}' + + // Parse and analyze + print("") + print("Step 1: Creating MirAnalyzerBox...") + + local analyzer = new MirAnalyzerBox(mir_json) + + if analyzer == null { + print("FAIL: Failed to create analyzer") + return 1 + } + + print("SUCCESS: MirAnalyzerBox created") + + // Test 1: Validate schema + print("") + print("Step 2: Validating schema...") + + local valid = analyzer.validateSchema() + if valid == 1 { + print("SUCCESS: Schema is valid") + } else { + print("FAIL: Schema validation failed") + return 1 + } + + // Test 2: Summarize function + print("") + print("Step 3: Summarizing function 0...") + + local summary = analyzer.summarize_function(0) + if summary == null { + print("FAIL: summarize_function returned null") + return 1 + } + + local name = summary.get("name") + local blocks = summary.get("block_count") + local instructions = summary.get("instruction_count") + local has_phi = summary.get("has_phis") + local has_if = summary.get("has_ifs") + local has_loop = summary.get("has_loops") + + print("Function name: " + name) + print("Blocks: " + blocks) + print("Instructions: " + instructions) + print("Has PHI: " + has_phi) + print("Has If: " + has_if) + print("Has Loop: " + has_loop) + + // Verify expected values + local pass_count = 0 + + if name == "Main.main/0" { + print(" [OK] Function name correct") + pass_count = pass_count + 1 + } else { + print(" [FAIL] Function name mismatch") + } + + if blocks == 4 { + print(" [OK] Block count correct (4)") + pass_count = pass_count + 1 + } else { + print(" [FAIL] Block count mismatch (expected 4, got " + blocks + ")") + } + + if has_phi == 1 { + print(" [OK] PHI detected") + pass_count = pass_count + 1 + } else { + print(" [FAIL] PHI not detected") + } + + if has_if == 1 { + print(" [OK] If detected") + pass_count = pass_count + 1 + } else { + print(" [FAIL] If not detected") + } + + if has_loop == 0 { + print(" [OK] No loop (correct)") + pass_count = pass_count + 1 + } else { + print(" [FAIL] Loop incorrectly detected") + } + + // Test 3: Count PHI instructions + print("") + print("Step 4: Counting PHI instructions...") + + local phi_list = analyzer.count_phis(0) + if phi_list == null { + print("FAIL: count_phis returned null") + return 1 + } + + local phi_count = phi_list.size() + print("PHI count: " + phi_count) + + if phi_count == 1 { + print(" [OK] Correct PHI count (1)") + pass_count = pass_count + 1 + + local phi = phi_list.at(0) + local block_id = phi.get("block_id") + local dest = phi.get("dest") + local incoming_count = phi.get("incoming_count") + + print(" PHI details: block=" + block_id + " dest=r" + dest + " incoming=" + incoming_count) + + if block_id == 10 { + print(" [OK] PHI in correct block (10)") + pass_count = pass_count + 1 + } + + if incoming_count == 2 { + print(" [OK] Correct incoming count (2)") + pass_count = pass_count + 1 + } + } else { + print(" [FAIL] Incorrect PHI count (expected 1, got " + phi_count + ")") + } + + // Test 4: Count loops + print("") + print("Step 5: Counting loops...") + + local loop_count = analyzer.count_loops(0) + print("Loop count: " + loop_count) + + if loop_count == 0 { + print(" [OK] No loops detected (correct)") + pass_count = pass_count + 1 + } else { + print(" [FAIL] Loops incorrectly detected (expected 0, got " + loop_count + ")") + } + + // Final results + print("") + print("=======================================================") + print("Test Results: " + pass_count + " / 9 checks passed") + print("=======================================================") + + if pass_count == 9 { + print("SUCCESS: All tests passed!") + return 0 + } else { + print("PARTIAL: Some tests failed") + return 1 + } + } +} diff --git a/tools/hako_shared/mir_analyzer.hako b/tools/hako_shared/mir_analyzer.hako new file mode 100644 index 00000000..19b11f04 --- /dev/null +++ b/tools/hako_shared/mir_analyzer.hako @@ -0,0 +1,382 @@ +// tools/hako_shared/mir_analyzer.hako - MirAnalyzerBox (Phase 161-2) +// .hako native MIR JSON v1 analyzer for JoinIR/MIR analysis +// Provides PHI/loop/if detection algorithms + +// MirAnalyzerBox: Main analyzer for MIR JSON v1 +box MirAnalyzerBox { + _mir_json: MapBox // Parsed MIR JSON root + _functions: ArrayBox // Cached functions array + _verbose: integer // Verbose mode (0=off, 1=on) + + // Constructor: Parse MIR JSON v1 from text + birth(mir_json_text) { + me._verbose = 0 + + // Parse JSON using JsonParserBox + me._mir_json = JsonParserBox.parse_object(mir_json_text) + + if me._mir_json == null { + print("[MirAnalyzerBox] ERROR: Failed to parse MIR JSON") + return null + } + + // Extract functions array + me._functions = me._mir_json.get("functions") + + if me._functions == null { + print("[MirAnalyzerBox] ERROR: No 'functions' field in MIR JSON") + return null + } + + if me._verbose == 1 { + print("[MirAnalyzerBox] Parsed MIR JSON successfully") + print("[MirAnalyzerBox] Functions count: " + me._functions.size()) + } + + return me + } + + // Validate MIR JSON v1 schema + validateSchema() { + if me._mir_json == null { return 0 } + + // Check required top-level fields + local capabilities = me._mir_json.get("capabilities") + if capabilities == null { + print("[MirAnalyzerBox] ERROR: Missing 'capabilities' field") + return 0 + } + + if me._functions == null { + print("[MirAnalyzerBox] ERROR: Missing 'functions' field") + return 0 + } + + // Check functions array is not empty + if me._functions.size() == 0 { + print("[MirAnalyzerBox] WARNING: Empty functions array") + } + + if me._verbose == 1 { + print("[MirAnalyzerBox] Schema validation: PASS") + } + + return 1 + } + + // Summarize function metadata + // Returns: MapBox with {name, params, blocks, instructions, has_loops, has_ifs, has_phis} + summarize_function(funcIndex) { + local func = me._get_function(funcIndex) + if func == null { + print("[MirAnalyzerBox] ERROR: Function index out of bounds: " + funcIndex) + return null + } + + local summary = new MapBox() + + // Basic metadata + local name = func.get("name") + summary.set("name", name) + + local params = func.get("params") + if params == null { + summary.set("param_count", 0) + } else { + summary.set("param_count", params.size()) + } + + // Block count + local blocks = func.get("blocks") + if blocks == null { + summary.set("block_count", 0) + summary.set("instruction_count", 0) + summary.set("has_phis", 0) + summary.set("has_loops", 0) + summary.set("has_ifs", 0) + return summary + } + + summary.set("block_count", blocks.size()) + + // Count instructions and detect patterns + local total_instructions = 0 + local has_phi = 0 + local has_branch = 0 + + local i = 0 + loop(i < blocks.size()) { + local block = blocks.at(i) + local instructions = block.get("instructions") + + if instructions != null { + total_instructions = total_instructions + instructions.size() + + // Check for PHI and Branch instructions + local j = 0 + loop(j < instructions.size()) { + local inst = instructions.at(j) + local op = inst.get("op") + + if op == "phi" { + has_phi = 1 + } + + if op == "branch" { + has_branch = 1 + } + + j = j + 1 + } + } + + i = i + 1 + } + + summary.set("instruction_count", total_instructions) + summary.set("has_phis", has_phi) + summary.set("has_ifs", has_branch) + + // Loop detection (basic backward edge check) + local has_loop = me._has_backward_edge(blocks) + summary.set("has_loops", has_loop) + + if me._verbose == 1 { + print("[MirAnalyzerBox] Summary for " + name + ":") + print(" Blocks: " + blocks.size()) + print(" Instructions: " + total_instructions) + print(" Has PHI: " + has_phi) + print(" Has Branch: " + has_branch) + print(" Has Loop: " + has_loop) + } + + return summary + } + + // Count PHI instructions in function + // Returns: ArrayBox of {block_id, dest, incoming_count} + count_phis(funcIndex) { + local func = me._get_function(funcIndex) + if func == null { + print("[MirAnalyzerBox] ERROR: Function index out of bounds: " + funcIndex) + return null + } + + local blocks = func.get("blocks") + if blocks == null { + return new ArrayBox() + } + + local phi_list = new ArrayBox() + + local i = 0 + loop(i < blocks.size()) { + local block = blocks.at(i) + local block_id = block.get("id") + local instructions = block.get("instructions") + + if instructions != null { + local j = 0 + loop(j < instructions.size()) { + local inst = instructions.at(j) + local op = inst.get("op") + + if op == "phi" { + local phi_info = new MapBox() + phi_info.set("block_id", block_id) + + local dest = inst.get("dst") + phi_info.set("dest", dest) + + local incoming = inst.get("incoming") + if incoming == null { + phi_info.set("incoming_count", 0) + } else { + phi_info.set("incoming_count", incoming.size()) + } + + phi_list.push(phi_info) + + if me._verbose == 1 { + print("[MirAnalyzerBox] PHI found: block=" + block_id + " dest=" + dest) + } + } + + j = j + 1 + } + } + + i = i + 1 + } + + return phi_list + } + + // Count loops in function (via CFG backward edge detection) + // Returns: integer count of loops detected + count_loops(funcIndex) { + local func = me._get_function(funcIndex) + if func == null { + print("[MirAnalyzerBox] ERROR: Function index out of bounds: " + funcIndex) + return 0 + } + + local blocks = func.get("blocks") + if blocks == null { + return 0 + } + + // Build block_id -> block_index map for quick lookup + local block_map = new MapBox() + local i = 0 + loop(i < blocks.size()) { + local block = blocks.at(i) + local block_id = block.get("id") + block_map.set("" + block_id, i) + i = i + 1 + } + + // Count backward edges (target_id < source_id) + local loop_count = 0 + + i = 0 + loop(i < blocks.size()) { + local block = blocks.at(i) + local block_id = block.get("id") + local instructions = block.get("instructions") + + if instructions != null { + // Check last instruction for control flow + local last_idx = instructions.size() - 1 + if last_idx >= 0 { + local last_inst = instructions.at(last_idx) + local op = last_inst.get("op") + + // Check jump instruction + if op == "jump" { + local target = last_inst.get("target") + if target != null { + if target < block_id { + loop_count = loop_count + 1 + + if me._verbose == 1 { + print("[MirAnalyzerBox] Backward edge: block " + block_id + " -> " + target) + } + } + } + } + + // Check branch instruction (both targets) + if op == "branch" { + local then_target = last_inst.get("then") + local else_target = last_inst.get("else") + + if then_target != null { + if then_target < block_id { + loop_count = loop_count + 1 + + if me._verbose == 1 { + print("[MirAnalyzerBox] Backward edge (then): block " + block_id + " -> " + then_target) + } + } + } + + if else_target != null { + if else_target < block_id { + loop_count = loop_count + 1 + + if me._verbose == 1 { + print("[MirAnalyzerBox] Backward edge (else): block " + block_id + " -> " + else_target) + } + } + } + } + } + } + + i = i + 1 + } + + return loop_count + } + + // Enable/disable verbose mode + set_verbose(enabled) { + if enabled == 1 { + me._verbose = 1 + print("[MirAnalyzerBox] Verbose mode: ON") + } else { + me._verbose = 0 + } + } + + // Internal: Get function by index + _get_function(funcIndex) { + if me._functions == null { return null } + if funcIndex < 0 { return null } + if funcIndex >= me._functions.size() { return null } + + return me._functions.at(funcIndex) + } + + // Internal: Check if function has backward edges (loop indicator) + _has_backward_edge(blocks) { + if blocks == null { return 0 } + + local i = 0 + loop(i < blocks.size()) { + local block = blocks.at(i) + local block_id = block.get("id") + local instructions = block.get("instructions") + + if instructions != null { + local last_idx = instructions.size() - 1 + if last_idx >= 0 { + local last_inst = instructions.at(last_idx) + local op = last_inst.get("op") + + if op == "jump" { + local target = last_inst.get("target") + if target != null { + if target < block_id { + return 1 + } + } + } + + if op == "branch" { + local then_target = last_inst.get("then") + local else_target = last_inst.get("else") + + if then_target != null { + if then_target < block_id { + return 1 + } + } + + if else_target != null { + if else_target < block_id { + return 1 + } + } + } + } + } + + i = i + 1 + } + + return 0 + } +} + +// Main entry point for standalone testing +static box MirAnalyzerMain { + main(args) { + print("[MirAnalyzerMain] Phase 161-2 Basic MirAnalyzerBox Implementation") + print("[MirAnalyzerMain] Usage: provide MIR JSON file path as argument") + + // TODO: Add file reading and analysis test + return 0 + } +}