fix(mir-builder): static method arity mismatch根治 - Phase 25.x
**問題**: - ParserStmtBox.parse_using/4 に5引数が渡される - me.method呼び出しで instance/static 判別なし - static method に誤って receiver 追加 **修正**: - MeCallPolicyBox: params[0]の型で instance/static 判別 - Instance method: receiver 追加 - Static method: receiver なし - Arity検証(NYASH_ME_CALL_ARITY_STRICT=1) **ドキュメント**: - docs/reference/environment-variables.md 新規作成 - docs/development/architecture/mir-logs-observability.md 更新 **テスト**: - src/tests/mir_stage1_cli_emit_program_min.rs 追加 - 既存 stage1 テスト全てパス Phase: 25.x
This commit is contained in:
19
apps/tests/minimal_usingcollector_collect.hako
Normal file
19
apps/tests/minimal_usingcollector_collect.hako
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// minimal_usingcollector_collect.hako
|
||||||
|
// 目的: UsingCollectorBox.collect の SSA/PHI バグを最小構成で再現するテスト入力。
|
||||||
|
// - Stage‑1 CLI が読む `apps/tests/stage1_using_minimal.hako` と同形の 1 行 using を含む。
|
||||||
|
// - Rust 側では NyashParser → MirCompiler → VM 実行時に
|
||||||
|
// UsingCollectorBox.collect/1 の substring まわりで Undefined Value が発生していないかを観測する。
|
||||||
|
|
||||||
|
using lang.compiler.parser.using.using_collector_box as UsingCollectorBox
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
// Stage‑1 using minimal と同じ形の 1 行 using。
|
||||||
|
// UsingCollectorBox.collect はこの文字列を line-scan して JSON を返す。
|
||||||
|
local src = "using \"foo/bar.hako\" as Foo\n"
|
||||||
|
local json = UsingCollectorBox.collect(src)
|
||||||
|
print(json)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
24
apps/tests/stage1_cli_emit_program_min.hako
Normal file
24
apps/tests/stage1_cli_emit_program_min.hako
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// stage1_cli_emit_program_min.hako
|
||||||
|
// 目的: Stage1Cli + Stage1UsingResolverBox + BuildBox を直接呼び出す中間テスト。
|
||||||
|
// - Rust ブリッジや Stage0 環境変数の組み立てを介さず、
|
||||||
|
// Stage1Cli.emit_program_json(src) をそのまま VM 上で実行する。
|
||||||
|
// - src には Stage‑1 minimal と同じ using 1 行を与える。
|
||||||
|
|
||||||
|
using lang.src.runner.stage1_cli as Stage1Cli
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main(args) {
|
||||||
|
// UsingResolver 側の apply フラグは明示的に 1 にしておく。
|
||||||
|
env.set("HAKO_STAGEB_APPLY_USINGS", "1")
|
||||||
|
env.set("HAKO_STAGEB_MODULES_LIST", "Foo=foo/bar.hako")
|
||||||
|
|
||||||
|
// 本線と同様に STAGE1_SOURCE_TEXT 経由でソースを渡し、FileBox 経路は通さない。
|
||||||
|
local src = "using \"foo/bar.hako\" as Foo\n"
|
||||||
|
env.set("STAGE1_SOURCE_TEXT", src)
|
||||||
|
|
||||||
|
// 第1引数は path だが、STAGE1_SOURCE_TEXT があるため _read_file は呼ばれない。
|
||||||
|
local prog = Stage1Cli.emit_program_json("apps/tests/stage1_using_minimal.hako")
|
||||||
|
print("[stage1_cli_emit_program_min] prog=" + ("" + prog))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
27
apps/tests/stage1_resolver_usingcollector_min.hako
Normal file
27
apps/tests/stage1_resolver_usingcollector_min.hako
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// stage1_resolver_usingcollector_min.hako
|
||||||
|
// 目的: Stage1UsingResolverBox + UsingCollectorBox のペアだけを合体させたときに
|
||||||
|
// SSA/PHI が崩れずに動くか確認する中間レベルのテスト。
|
||||||
|
//
|
||||||
|
// - Stage‑1 CLI 本線とは独立に、resolve_for_source(src) → UsingCollectorBox.collect(src)
|
||||||
|
// の経路だけを小さな箱で再現する。
|
||||||
|
// - src には Stage‑1 最小ケースと同じ using 1 行を与える。
|
||||||
|
|
||||||
|
using lang.compiler.entry.using_resolver_box as Stage1UsingResolverBox
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main(args) {
|
||||||
|
// UsingResolver 側の apply フラグを明示的に有効化(prefix 経路を通す)
|
||||||
|
env.set("HAKO_STAGEB_APPLY_USINGS", "1")
|
||||||
|
|
||||||
|
// modules_list は最低限の 1 エントリだけを与える(実際にファイルが存在しなくても良い)。
|
||||||
|
// _read_file が失敗しても prefix 連結をスキップするだけなので、UsingCollector の JSON 生成までは到達する。
|
||||||
|
env.set("HAKO_STAGEB_MODULES_LIST", "Foo=foo/bar.hako")
|
||||||
|
|
||||||
|
// Stage‑1 minimal と同形の 1 行 using
|
||||||
|
local src = "using \"foo/bar.hako\" as Foo\n"
|
||||||
|
local prefix = Stage1UsingResolverBox.resolve_for_source(src)
|
||||||
|
print("[stage1_resolver_usingcollector_min] prefix=" + ("" + prefix))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
18
apps/tests/stageb_program_min_using_only.hako
Normal file
18
apps/tests/stageb_program_min_using_only.hako
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// stageb_program_min_using_only.hako
|
||||||
|
// 目的: Stage‑B BuildBox + ParserBox だけで
|
||||||
|
// 「using 1 行」の Program(JSON v0) emit 中に型崩れや Compare エラーが出ないか確認する最小ケース。
|
||||||
|
//
|
||||||
|
// - Stage1Cli や UsingResolver は通さず、BuildBox.emit_program_json_v0(src, null) を直叩きする。
|
||||||
|
// - src は Stage‑1 minimal と同じ `using "foo/bar.hako" as Foo\n` 一行。
|
||||||
|
|
||||||
|
using lang.compiler.build.build_box as BuildBox
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main(args) {
|
||||||
|
local src = "using \"foo/bar.hako\" as Foo\n"
|
||||||
|
local prog = BuildBox.emit_program_json_v0(src, null)
|
||||||
|
print("[stageb_program_min_using_only] prog=" + ("" + prog))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -97,12 +97,37 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### 5. MeCall Arity Debug(Phase 25.x追加)
|
||||||
|
|
||||||
|
**場所**: `src/mir/builder/method_call_handlers.rs`
|
||||||
|
|
||||||
|
| 行番号 | タグ | 説明 | 用途 |
|
||||||
|
|-------|------|-----|------|
|
||||||
|
| 70-73 | `[me-call] arity mismatch (instance)` | Instance method arity不一致警告 | Dev専用 |
|
||||||
|
| 98-101 | `[me-call] arity mismatch (static)` | Static method arity不一致警告 | Dev専用 |
|
||||||
|
| 150-154 | `[static-call] emit` | Static method呼び出しトレース | 観測用 |
|
||||||
|
|
||||||
|
**有効化環境変数**:
|
||||||
|
- `NYASH_ME_CALL_ARITY_STRICT=1` - 厳密モード(不一致でエラー返却)
|
||||||
|
- `NYASH_STATIC_CALL_TRACE=1` - トレースモード(warning出力)
|
||||||
|
|
||||||
|
**説明**:
|
||||||
|
- `me.method(...)` 呼び出しのarity検証
|
||||||
|
- Instance method(receiver追加)vs Static method(receiver なし)の判別
|
||||||
|
- ParserStmtBox.parse_using/4 の5引数バグ等の検出
|
||||||
|
|
||||||
|
**用途と削除時期**:
|
||||||
|
- Phase XX(static box instance化)完了後に削除予定
|
||||||
|
- 過渡期のデバッグ支援ログ(static boxのsingleton化後は不要)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 用途別集計表
|
## 用途別集計表
|
||||||
|
|
||||||
| 用途 | 件数 | 内容 | 用途と削除時期 |
|
| 用途 | 件数 | 内容 | 用途と削除時期 |
|
||||||
|-----|-------|-----|----------|
|
|-----|-------|-----|----------|
|
||||||
| **Dev専用** | 11 | Stage-1 CLI / StringHelpers デバッグ用 | 削除予定(Phase 25.x 完了) |
|
| **Dev専用** | 13 | Stage-1 CLI / StringHelpers / MeCall Arity デバッグ用 | 削除予定(Phase 25.x 完了) |
|
||||||
| **観測用** | 3 | FuncScanner ループ反復観測 | 保持推奨(MIR観測 API 昇格) |
|
| **観測用** | 4 | FuncScanner ループ反復 / Static call トレース | 保持推奨(MIR観測 API 昇格) |
|
||||||
| **コメント** | 1 | テスト場所の注釈 | - |
|
| **コメント** | 1 | テスト場所の注釈 | - |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@ -9,8 +9,12 @@
|
|||||||
- `src/mir/loop_builder.rs`
|
- `src/mir/loop_builder.rs`
|
||||||
- `src/mir/phi_core/loopform_builder.rs`
|
- `src/mir/phi_core/loopform_builder.rs`
|
||||||
- `src/mir/phi_core/loop_snapshot_merge.rs`
|
- `src/mir/phi_core/loop_snapshot_merge.rs`
|
||||||
- Stage‑1 UsingResolver テストの観察点:
|
- Stage‑1 UsingResolver テストの観察点:
|
||||||
- `src/tests/mir_stage1_using_resolver_verify.rs`(collect_entries の SSA/PHI 期待を確認)
|
- `src/tests/mir_stage1_using_resolver_verify.rs`
|
||||||
|
- collect_entries 系ループ(JSON スキャン)
|
||||||
|
- Region+next_i 形の entries ループ
|
||||||
|
- modules_list 分割ループ(Region+next_start)
|
||||||
|
- resolve_for_source 相当で entries ループと modules_map 参照を同時に行うケース
|
||||||
- 既存の JSON フロント経路でどのブロック/値が PHI 化されているかを dump しておくと導線が追いやすい。
|
- 既存の JSON フロント経路でどのブロック/値が PHI 化されているかを dump しておくと導線が追いやすい。
|
||||||
|
|
||||||
## JSON v0 フロント側の契約(Stage‑B → Stage‑1)
|
## JSON v0 フロント側の契約(Stage‑B → Stage‑1)
|
||||||
@ -113,7 +117,9 @@ source -> | Stage-B (block) | --> | Stage-1 UsingRes | --> | MirBuilder (.ha
|
|||||||
### ループ洗い出しメモ(entry / pipeline_v2)
|
### ループ洗い出しメモ(entry / pipeline_v2)
|
||||||
- entry UsingResolver(lang/src/compiler/entry/using_resolver_box.hako)
|
- entry UsingResolver(lang/src/compiler/entry/using_resolver_box.hako)
|
||||||
- Region+next_i 化済み: entries イテレーション / JSON スキャン / modules_list 分割
|
- Region+next_i 化済み: entries イテレーション / JSON スキャン / modules_list 分割
|
||||||
- 追加テスト: modules_list 分割ループ(start/next_start)をそのまま MIR 化できることを `mir_stage1_using_resolver_module_map_regionized_verifies` で固定。
|
- 追加テスト:
|
||||||
|
- modules_list 分割ループ(start/next_start)をそのまま MIR 化できることを `mir_stage1_using_resolver_module_map_regionized_verifies` で固定。
|
||||||
|
- resolve_for_source 相当で entries ループと modules_map 参照(MapBox.has/get)を同時に行うケースを `mir_stage1_using_resolver_resolve_with_modules_map_verifies` で固定。
|
||||||
- 残り: なし(現状の 3 ループは all Region 形)
|
- 残り: なし(現状の 3 ループは all Region 形)
|
||||||
- pipeline_v2 UsingResolver(lang/src/compiler/pipeline_v2/using_resolver_box.hako)
|
- pipeline_v2 UsingResolver(lang/src/compiler/pipeline_v2/using_resolver_box.hako)
|
||||||
- 役割: modules_json 上で alias を解決する stateful helper(テキスト収集は entry 側)。
|
- 役割: modules_json 上で alias を解決する stateful helper(テキスト収集は entry 側)。
|
||||||
|
|||||||
@ -17,6 +17,10 @@ Status: design-only + Stage0 stub 実装済み(Phase 25.1 時点では仕様
|
|||||||
- Stage1 実行時は「CLI として」ではなく、これらのランタイムサービス層(C-ABI/extern)として利用することを前提とする。
|
- Stage1 実行時は「CLI として」ではなく、これらのランタイムサービス層(C-ABI/extern)として利用することを前提とする。
|
||||||
- Phase 25.1 現在は、Rust Stage0 から `.hako` 側 stub CLI(`lang/src/runner/stage1_cli.hako`)を子プロセスとして起動する
|
- Phase 25.1 現在は、Rust Stage0 から `.hako` 側 stub CLI(`lang/src/runner/stage1_cli.hako`)を子プロセスとして起動する
|
||||||
ブリッジ(`src/runner/stage1_bridge.rs`)のみ実装済みで、自己ホスト EXE(`target/selfhost/hakorune`)はまだ設計段階。
|
ブリッジ(`src/runner/stage1_bridge.rs`)のみ実装済みで、自己ホスト EXE(`target/selfhost/hakorune`)はまだ設計段階。
|
||||||
|
- ブリッジは env-only 仕様で Stage1 stub を呼び出し、`STAGE1_EMIT_PROGRAM_JSON` / `STAGE1_EMIT_MIR_JSON` / `STAGE1_BACKEND`
|
||||||
|
/ `STAGE1_SOURCE` などの環境変数をセットする。
|
||||||
|
- Stage‑1 UsingResolver は `HAKO_STAGEB_APPLY_USINGS=0` を既定とし、CLI 経路では prefix 連結を行わない(using 解決の検証は
|
||||||
|
専用テストで行い、CLI 本線は Program(JSON) 生成に集中させる)。
|
||||||
- Stage1(Hakorune selfhost CLI)
|
- Stage1(Hakorune selfhost CLI)
|
||||||
- 実体: `target/selfhost/hakorune`(Phase 25.1 では Ny Executor プロトタイプ)。
|
- 実体: `target/selfhost/hakorune`(Phase 25.1 では Ny Executor プロトタイプ)。
|
||||||
- 役割: `.hako → Program(JSON) → MIR(JSON) → 実行/EXE` というパイプラインの制御。
|
- 役割: `.hako → Program(JSON) → MIR(JSON) → 実行/EXE` というパイプラインの制御。
|
||||||
@ -229,6 +233,35 @@ hakorune emit mir-json [-o <out>] [--quiet] <source.hako>
|
|||||||
- `tools/selfhost/run_stage1_cli.sh --bin /tmp/hakorune-dev emit program-json apps/tests/minimal.hako` は exit code 0 だが stdout が空のまま。Stage‑B 側で box 定義を Program(JSON) に含められるようになるまで、Rust CLI / BuildBox (`tools/hakorune_emit_mir.sh`) 経由での JSON 取得を継続する。
|
- `tools/selfhost/run_stage1_cli.sh --bin /tmp/hakorune-dev emit program-json apps/tests/minimal.hako` は exit code 0 だが stdout が空のまま。Stage‑B 側で box 定義を Program(JSON) に含められるようになるまで、Rust CLI / BuildBox (`tools/hakorune_emit_mir.sh`) 経由での JSON 取得を継続する。
|
||||||
- using 解決は Stage0(Rust Runner)と Stage1(Hakorune)の二系統に分離する方針。Stage1 側は `lang.compiler.entry.using_resolver_box` で `nyash.toml` の `[modules]` を参照し、`HAKO_STAGEB_MODULES_LIST`(shell 側で生成した `name=path` リスト)をキーに依存 Box を text merge する。Rust 側は既存の Runner using 実装を維持し、Stage1 経路はこの Box で独立した自己ホスト導線を持つ。
|
- using 解決は Stage0(Rust Runner)と Stage1(Hakorune)の二系統に分離する方針。Stage1 側は `lang.compiler.entry.using_resolver_box` で `nyash.toml` の `[modules]` を参照し、`HAKO_STAGEB_MODULES_LIST`(shell 側で生成した `name=path` リスト)をキーに依存 Box を text merge する。Rust 側は既存の Runner using 実装を維持し、Stage1 経路はこの Box で独立した自己ホスト導線を持つ。
|
||||||
|
|
||||||
|
### Stage‑1 CLI デバッグメモ(Stage1Cli + BuildBox + ParserBox)
|
||||||
|
|
||||||
|
- 中間スモーク: `apps/tests/stage1_cli_emit_program_min.hako`
|
||||||
|
- 役割: `Stage1Cli.emit_program_json` → `BuildBox.emit_program_json_v0` → `ParserBox.parse_program2` までを、Rust ブリッジや Stage0 runner を経由せずに直接 VM 上で実行する最小ケース。
|
||||||
|
- 実行例:
|
||||||
|
```bash
|
||||||
|
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 \
|
||||||
|
NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 \
|
||||||
|
./target/release/hakorune apps/tests/stage1_cli_emit_program_min.hako
|
||||||
|
```
|
||||||
|
- Rust テスト側のハーネス: `src/tests/mir_stage1_cli_emit_program_min.rs`
|
||||||
|
- `include_str!("../../lang/src/runner/stage1_cli.hako")` で Stage1Cli 本体をバンドルし、`static box Main` を末尾に付けて 1 ソースとしてパースする形に統一。
|
||||||
|
- `mir_stage1_cli_emit_program_min_compiles_and_verifies`:
|
||||||
|
- Stage1Cli + UsingResolver + BuildBox を含んだモジュールが MIR 生成・verify まで通ることを確認(SSA/PHI 崩壊なし)。
|
||||||
|
- `mir_stage1_cli_emit_program_min_exec_hits_type_error`:
|
||||||
|
- VM 実行まで進め、Stage‑1 CLI 経路がまだ決定的に失敗すること(型エラー or 未解決呼び出し)が Rust テスト内で再現できることを確認するための箱。
|
||||||
|
- 現時点で観測されている実行時エラー(2025‑11‑21 時点):
|
||||||
|
- `apps/tests/stage1_cli_emit_program_min.hako` を直接 `hakorune` で実行すると、
|
||||||
|
- 以前の「String + Void」「String + MapBox」ではなく、
|
||||||
|
- `Type error: unsupported compare Ge on String("using \"foo/bar.hako\" as Foo\n") and Integer(19)`
|
||||||
|
- というエラーが Stage‑1 経路で発生している。
|
||||||
|
- 経路としては `Stage1Cli.emit_program_json` → `BuildBox.emit_program_json_v0` → `ParserBox.parse_program2` まで進んだうえで、
|
||||||
|
どこかで Compare(Ge) の両辺が String vs Int に崩れていることが確認できている。
|
||||||
|
- デバッグ方針:
|
||||||
|
- Stage‑1 CLI 本線のバグ追跡は、必ず「小さな箱」から行う:
|
||||||
|
- `.hako` 側: `apps/tests/stage1_cli_emit_program_min.hako` で Stage1Cli+UsingResolver+BuildBox をまとめて叩く中間スモーク。
|
||||||
|
- Rust 側: `src/tests/mir_stage1_cli_emit_program_min.rs` で同じソースを `NyashParser → MirCompiler → VM` まで持ち込むテスト。
|
||||||
|
- 次フェーズでは、このハーネス上で Compare(Ge) 命令をスキャンし、「左オペランドがソース行 String、右が Int になっている Compare」を 1 関数単位まで特定し、その関数を別の minimal .hako + Rust テストに切り出して修正する。
|
||||||
|
|
||||||
## `check` コマンド(予約)
|
## `check` コマンド(予約)
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|||||||
153
docs/reference/environment-variables.md
Normal file
153
docs/reference/environment-variables.md
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
# 環境変数リファレンス
|
||||||
|
|
||||||
|
Nyashの動作を制御する環境変数の一覧。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MIR Builder検証系
|
||||||
|
|
||||||
|
MIRビルダーの動作検証・デバッグ用環境変数。
|
||||||
|
|
||||||
|
| 環境変数 | デフォルト | 用途 | 追加Phase |
|
||||||
|
|---------|----------|-----|-----------|
|
||||||
|
| `NYASH_ME_CALL_ARITY_STRICT=1` | OFF | me.method呼び出しのarity不一致でエラー返却 | Phase 25.x |
|
||||||
|
| `NYASH_STATIC_CALL_TRACE=1` | OFF | Static method呼び出しのトレース出力 | Phase 25.x |
|
||||||
|
| `NYASH_VERIFY_ALLOW_NO_PHI=1` | OFF | PHI検証スキップ(支配・マージ) | - |
|
||||||
|
| `NYASH_VERIFY_ALLOW_LEGACY=1` | OFF | レガシー命令許可 | - |
|
||||||
|
| `NYASH_VERIFY_BARRIER_STRICT=1` | OFF | Barrier配置厳密チェック | - |
|
||||||
|
| `NYASH_VERIFY_EDGE_COPY_STRICT=1` | OFF | Edge copy検証 | - |
|
||||||
|
| `NYASH_VERIFY_RET_PURITY=1` | OFF | Return block純粋性検証 | - |
|
||||||
|
| `NYASH_BREAKFINDER_SSA_TRACE=1` | OFF | SSA詳細トレース(BreakFinderBox等) | - |
|
||||||
|
|
||||||
|
### 使用例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Dev環境で厳密検証(arity不一致でビルドエラー)
|
||||||
|
NYASH_ME_CALL_ARITY_STRICT=1 cargo test
|
||||||
|
|
||||||
|
# トレースのみ(warning表示、エラーにはしない)
|
||||||
|
NYASH_STATIC_CALL_TRACE=1 ./target/release/hakorune program.hako
|
||||||
|
|
||||||
|
# PHI検証を一時的にスキップ
|
||||||
|
NYASH_VERIFY_ALLOW_NO_PHI=1 ./target/release/hakorune program.hako
|
||||||
|
|
||||||
|
# SSA詳細トレース(BreakFinderBox等のデバッグ)
|
||||||
|
NYASH_BREAKFINDER_SSA_TRACE=1 cargo test mir_stage1_using_resolver_verify 2>&1 | grep "breakfinder/ssa"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stage-1 CLI制御系
|
||||||
|
|
||||||
|
Stage-1 CLI(Nyash selfhosting compiler)の制御用環境変数。
|
||||||
|
|
||||||
|
| 環境変数 | デフォルト | 用途 | 追加Phase |
|
||||||
|
|---------|----------|-----|-----------|
|
||||||
|
| `NYASH_USE_STAGE1_CLI=1` | OFF | Stage-1 CLI有効化 | Phase 25.1 |
|
||||||
|
| `STAGE1_CLI_DEBUG=1` | OFF | Stage-1 CLI詳細ログ | Phase 25.1 |
|
||||||
|
| `STAGE1_EMIT_PROGRAM_JSON=1` | OFF | Program JSON出力モード | Phase 25.1 |
|
||||||
|
| `STAGE1_EMIT_MIR_JSON=1` | OFF | MIR JSON出力モード | Phase 25.1 |
|
||||||
|
| `STAGE1_BACKEND={vm\|llvm\|pyvm}` | `vm` | 実行バックエンド指定 | Phase 25.1 |
|
||||||
|
| `STAGE1_SOURCE=<path>` | - | ソースファイルパス | Phase 25.1 |
|
||||||
|
| `STAGE1_SOURCE_TEXT=<text>` | - | ソーステキスト(inline指定) | Phase 25.1 |
|
||||||
|
| `STAGE1_PROGRAM_JSON=<path>` | - | Program JSONパス | Phase 25.1 |
|
||||||
|
|
||||||
|
### 使用例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stage-1 CLI経由で.hakoファイルをコンパイル
|
||||||
|
NYASH_USE_STAGE1_CLI=1 STAGE1_SOURCE=program.hako \
|
||||||
|
./target/release/hakorune lang/src/runner/stage1_cli.hako
|
||||||
|
|
||||||
|
# Program JSON生成
|
||||||
|
STAGE1_EMIT_PROGRAM_JSON=1 STAGE1_SOURCE=program.hako \
|
||||||
|
./target/release/hakorune lang/src/runner/stage1_cli.hako
|
||||||
|
|
||||||
|
# デバッグログ付き
|
||||||
|
STAGE1_CLI_DEBUG=1 NYASH_USE_STAGE1_CLI=1 \
|
||||||
|
./target/release/hakorune program.hako
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parser制御系
|
||||||
|
|
||||||
|
パーサーの動作制御用環境変数。
|
||||||
|
|
||||||
|
| 環境変数 | デフォルト | 用途 | 追加Phase |
|
||||||
|
|---------|----------|-----|-----------|
|
||||||
|
| `NYASH_PARSER_STAGE3=1` | OFF | Stage-3 parser有効化 | Phase 25.3 |
|
||||||
|
| `HAKO_PARSER_STAGE3=1` | OFF | Stage-3 parser有効化(.hako側) | Phase 25.3 |
|
||||||
|
| `NYASH_ENABLE_USING=1` | OFF | using文サポート有効化 | Phase 25.1 |
|
||||||
|
| `HAKO_ENABLE_USING=1` | OFF | using文サポート有効化(.hako側) | Phase 25.1 |
|
||||||
|
| `HAKO_STAGEB_APPLY_USINGS=1` | OFF | Stage-B using適用 | Phase 25.1 |
|
||||||
|
|
||||||
|
### 使用例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# using文を使ったプログラムをパース
|
||||||
|
NYASH_PARSER_STAGE3=1 NYASH_ENABLE_USING=1 \
|
||||||
|
./target/release/hakorune program_with_using.hako
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## デバッグ・観測系
|
||||||
|
|
||||||
|
デバッグ・トレース用環境変数。
|
||||||
|
|
||||||
|
| 環境変数 | デフォルト | 用途 | 追加Phase |
|
||||||
|
|---------|----------|-----|-----------|
|
||||||
|
| `NYASH_CLI_VERBOSE=1` | OFF | 詳細診断情報出力 | - |
|
||||||
|
| `NYASH_VM_TRACE=1` | OFF | VM実行トレース出力 | - |
|
||||||
|
| `NYASH_VM_DUMP_MIR=1` | OFF | VM実行前MIR出力 | - |
|
||||||
|
| `NYASH_DUMP_JSON_IR=1` | OFF | JSON IR出力 | - |
|
||||||
|
| `NYASH_STAGE1_MIR_DUMP=1` | OFF | Stage-1 MIR出力 | Phase 25.x |
|
||||||
|
| `NYASH_STAGE1_SCAN_GE=1` | OFF | Compare Ge命令スキャン | Phase 25.x |
|
||||||
|
| `NYASH_TO_I64_DEBUG=1` | OFF | to_i64変換デバッグ | Phase 25.x |
|
||||||
|
| `NYASH_FUNCSCANNER_DEBUG=1` | OFF | FuncScanner詳細ログ | Phase 25.3 |
|
||||||
|
|
||||||
|
### 使用例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# VM実行トレース
|
||||||
|
NYASH_VM_TRACE=1 ./target/release/hakorune program.hako 2>&1 | grep "vm-trace"
|
||||||
|
|
||||||
|
# MIR確認(複数手法)
|
||||||
|
NYASH_VM_DUMP_MIR=1 ./target/release/hakorune program.hako
|
||||||
|
./target/release/hakorune --dump-mir program.hako
|
||||||
|
./target/release/hakorune --emit-mir-json debug.json program.hako
|
||||||
|
|
||||||
|
# Stage-1 CLI + MIRダンプ
|
||||||
|
NYASH_STAGE1_MIR_DUMP=1 cargo test mir_stage1_cli_emit_program_min
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## プラグイン・Box制御系
|
||||||
|
|
||||||
|
プラグインとBox factoryの制御用環境変数。
|
||||||
|
|
||||||
|
| 環境変数 | デフォルト | 用途 | 追加Phase |
|
||||||
|
|---------|----------|-----|-----------|
|
||||||
|
| `NYASH_DISABLE_PLUGINS=1` | OFF | プラグイン無効化 | - |
|
||||||
|
| `NYASH_BOX_FACTORY_POLICY={builtin_first\|plugin_first}` | `builtin_first` | Box factory優先順位 | Phase 15.5 |
|
||||||
|
| `NYASH_FILEBOX_MODE={auto\|plugin\|builtin}` | `auto` | FileBox実装選択 | Phase 15.5 |
|
||||||
|
|
||||||
|
### 使用例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# プラグイン無効で実行(コアBox経路のみ)
|
||||||
|
NYASH_DISABLE_PLUGINS=1 ./target/release/hakorune program.hako
|
||||||
|
|
||||||
|
# Plugin優先でBox生成
|
||||||
|
NYASH_BOX_FACTORY_POLICY=plugin_first ./target/release/hakorune program.hako
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 参考
|
||||||
|
|
||||||
|
- **MIRログ観測**: [docs/development/architecture/mir-logs-observability.md](../development/architecture/mir-logs-observability.md)
|
||||||
|
- **Phase 25.4計画**: [docs/development/roadmap/phases/phase-25.4-naming-cli-cleanup/README.md](../development/roadmap/phases/phase-25.4-naming-cli-cleanup/README.md)
|
||||||
|
- **MIR検証システム**: [src/mir/verification/](../../src/mir/verification/)
|
||||||
@ -147,15 +147,38 @@ static box Stage1Cli {
|
|||||||
// and create a fresh ArrayBox from environment variables instead
|
// and create a fresh ArrayBox from environment variables instead
|
||||||
local args_safe = new ArrayBox()
|
local args_safe = new ArrayBox()
|
||||||
|
|
||||||
// Config box: Parse all env vars into structured config
|
// 現状: MIR 型レジストリの制約により MapBox ベースの ConfigBox は本線では使用しない。
|
||||||
local cfg = Stage1CliConfigBox.from_env()
|
// env から直接読み取り、Stage1CliConfigBox は将来の構造化用として温存する。
|
||||||
local mode = cfg.get("mode")
|
|
||||||
local debug = cfg.get("debug")
|
// mode 判定(env-only)
|
||||||
|
local mode = "disabled"
|
||||||
|
if env.get("STAGE1_EMIT_PROGRAM_JSON") == "1" {
|
||||||
|
mode = "emit_program_json"
|
||||||
|
} else if env.get("STAGE1_EMIT_MIR_JSON") == "1" {
|
||||||
|
mode = "emit_mir_json"
|
||||||
|
} else if env.get("NYASH_USE_STAGE1_CLI") == "1" {
|
||||||
|
mode = "run"
|
||||||
|
}
|
||||||
|
|
||||||
|
// backend と入力まわり
|
||||||
|
local backend = env.get("STAGE1_BACKEND")
|
||||||
|
if backend == null { backend = "vm" }
|
||||||
|
backend = "" + backend
|
||||||
|
|
||||||
|
local source = env.get("STAGE1_SOURCE")
|
||||||
|
local source_text = env.get("STAGE1_SOURCE_TEXT")
|
||||||
|
local prog_path = env.get("STAGE1_PROGRAM_JSON")
|
||||||
|
local debug = env.get("STAGE1_CLI_DEBUG")
|
||||||
|
|
||||||
// Log entry point for debugging
|
// Log entry point for debugging
|
||||||
if debug == "1" {
|
if debug == "1" {
|
||||||
__mir__.log("[stage1_main] args_safe at entry", args_safe)
|
__mir__.log("[stage1_main] args_safe at entry", args_safe)
|
||||||
print("[stage1-cli/debug] stage1_main ENTRY: mode=" + ("" + mode) + " backend=" + ("" + cfg.get("backend")))
|
print("[stage1-cli/debug] stage1_main ENTRY: mode=" + ("" + mode) + " backend=" + backend)
|
||||||
|
__mir__.log("[stage1_main] env-config",
|
||||||
|
mode,
|
||||||
|
backend,
|
||||||
|
source,
|
||||||
|
prog_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Early exit if CLI is disabled
|
// Early exit if CLI is disabled
|
||||||
@ -166,21 +189,6 @@ static box Stage1Cli {
|
|||||||
return 97
|
return 97
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log config toggles before dispatch
|
|
||||||
if debug == "1" {
|
|
||||||
__mir__.log("[stage1_main] config",
|
|
||||||
cfg.get("mode"),
|
|
||||||
cfg.get("backend"),
|
|
||||||
cfg.get("source_path"),
|
|
||||||
cfg.get("program_json_path"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract config fields
|
|
||||||
local source = cfg.get("source_path")
|
|
||||||
local source_text = cfg.get("source_text")
|
|
||||||
local prog_path = cfg.get("program_json_path")
|
|
||||||
local backend = cfg.get("backend")
|
|
||||||
|
|
||||||
// Dispatch by mode
|
// Dispatch by mode
|
||||||
if mode == "emit_program_json" {
|
if mode == "emit_program_json" {
|
||||||
if source == null || source == "" {
|
if source == null || source == "" {
|
||||||
|
|||||||
@ -36,15 +36,37 @@ pub static OPERATORS_DIV_RULES: &[(&str, &str, &str, &str)] = &[
|
|||||||
];
|
];
|
||||||
pub fn lookup_keyword(word: &str) -> Option<&'static str> {
|
pub fn lookup_keyword(word: &str) -> Option<&'static str> {
|
||||||
for (k, t) in KEYWORDS {
|
for (k, t) in KEYWORDS {
|
||||||
if *k == word {
|
if *k == word { return Some(*t); }
|
||||||
return Some(*t);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[
|
pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[
|
||||||
"box", "global", "function", "static", "if", "loop", "break", "return", "print", "nowait",
|
"box",
|
||||||
"include", "local", "outbox", "try", "throw", "using", "from",
|
"global",
|
||||||
|
"function",
|
||||||
|
"static",
|
||||||
|
"if",
|
||||||
|
"loop",
|
||||||
|
"break",
|
||||||
|
"return",
|
||||||
|
"print",
|
||||||
|
"nowait",
|
||||||
|
"include",
|
||||||
|
"local",
|
||||||
|
"outbox",
|
||||||
|
"try",
|
||||||
|
"throw",
|
||||||
|
"using",
|
||||||
|
"from",
|
||||||
];
|
];
|
||||||
pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &["add", "sub", "mul", "div", "and", "or", "eq", "ne"];
|
pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &[
|
||||||
|
"add",
|
||||||
|
"sub",
|
||||||
|
"mul",
|
||||||
|
"div",
|
||||||
|
"and",
|
||||||
|
"or",
|
||||||
|
"eq",
|
||||||
|
"ne",
|
||||||
|
];
|
||||||
@ -7,7 +7,7 @@ use crate::ast::ASTNode;
|
|||||||
use crate::mir::builder::builder_calls::CallTarget;
|
use crate::mir::builder::builder_calls::CallTarget;
|
||||||
use crate::mir::builder::calls::function_lowering;
|
use crate::mir::builder::calls::function_lowering;
|
||||||
use crate::mir::builder::{MirBuilder, ValueId};
|
use crate::mir::builder::{MirBuilder, ValueId};
|
||||||
use crate::mir::{MirInstruction, TypeOpKind};
|
use crate::mir::{MirInstruction, MirType, TypeOpKind};
|
||||||
|
|
||||||
/// Me-call 専用のポリシー箱。
|
/// Me-call 専用のポリシー箱。
|
||||||
///
|
///
|
||||||
@ -35,26 +35,85 @@ impl MeCallPolicyBox {
|
|||||||
}
|
}
|
||||||
let arity = arg_values.len();
|
let arity = arg_values.len();
|
||||||
let fname = function_lowering::generate_method_function_name(cls, method, arity);
|
let fname = function_lowering::generate_method_function_name(cls, method, arity);
|
||||||
let exists = if let Some(ref module) = builder.current_module {
|
if let Some(ref module) = builder.current_module {
|
||||||
module.functions.contains_key(&fname)
|
if let Some(func) = module.functions.get(&fname) {
|
||||||
} else {
|
// Decide whether this lowered function expects an implicit receiver.
|
||||||
false
|
// Instance methods: params[0] is Box(box_name)
|
||||||
};
|
// Static methods: params[0] is non-Box or params.is_empty()
|
||||||
if exists {
|
let params = &func.signature.params;
|
||||||
// Pass 'me' as first arg
|
let is_instance_method = !params.is_empty()
|
||||||
let me_id = builder.build_me_expression()?;
|
&& matches!(params[0], MirType::Box(_));
|
||||||
let mut call_args = Vec::with_capacity(arity + 1);
|
|
||||||
call_args.push(me_id);
|
// Expected argument count from signature (including receiver for instance)
|
||||||
call_args.extend(arg_values.into_iter());
|
let expected_params = params.len();
|
||||||
let dst = builder.next_value_id();
|
let provided_static = arg_values.len();
|
||||||
// Emit as unified global call to lowered function
|
let provided_instance = arg_values.len() + 1;
|
||||||
builder.emit_unified_call(
|
|
||||||
Some(dst),
|
// Build call_args based on method kind
|
||||||
CallTarget::Global(fname.clone()),
|
let call_args: Vec<ValueId> = if is_instance_method {
|
||||||
call_args,
|
// Instance method: prepend 'me' receiver
|
||||||
)?;
|
if expected_params != provided_instance {
|
||||||
builder.annotate_call_result_from_func_name(dst, &fname);
|
if std::env::var("NYASH_ME_CALL_ARITY_STRICT")
|
||||||
return Ok(Some(dst));
|
.ok()
|
||||||
|
.as_deref()
|
||||||
|
== Some("1")
|
||||||
|
{
|
||||||
|
return Err(format!(
|
||||||
|
"[me-call] arity mismatch (instance): {}: declared {} params, got {} args(+me)",
|
||||||
|
fname, expected_params, provided_instance
|
||||||
|
));
|
||||||
|
} else if std::env::var("NYASH_STATIC_CALL_TRACE")
|
||||||
|
.ok()
|
||||||
|
.as_deref()
|
||||||
|
== Some("1")
|
||||||
|
{
|
||||||
|
eprintln!(
|
||||||
|
"[me-call] arity mismatch (instance): {}: declared {} params, got {} args(+me)",
|
||||||
|
fname, expected_params, provided_instance
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let me_id = builder.build_me_expression()?;
|
||||||
|
let mut v = Vec::with_capacity(provided_instance);
|
||||||
|
v.push(me_id);
|
||||||
|
v.extend(arg_values.into_iter());
|
||||||
|
v
|
||||||
|
} else {
|
||||||
|
// Static method: no receiver
|
||||||
|
if expected_params != provided_static {
|
||||||
|
if std::env::var("NYASH_ME_CALL_ARITY_STRICT")
|
||||||
|
.ok()
|
||||||
|
.as_deref()
|
||||||
|
== Some("1")
|
||||||
|
{
|
||||||
|
return Err(format!(
|
||||||
|
"[me-call] arity mismatch (static): {}: declared {} params, got {} args",
|
||||||
|
fname, expected_params, provided_static
|
||||||
|
));
|
||||||
|
} else if std::env::var("NYASH_STATIC_CALL_TRACE")
|
||||||
|
.ok()
|
||||||
|
.as_deref()
|
||||||
|
== Some("1")
|
||||||
|
{
|
||||||
|
eprintln!(
|
||||||
|
"[me-call] arity mismatch (static): {}: declared {} params, got {} args",
|
||||||
|
fname, expected_params, provided_static
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arg_values
|
||||||
|
};
|
||||||
|
|
||||||
|
let dst = builder.next_value_id();
|
||||||
|
// Emit as unified global call to lowered function
|
||||||
|
builder.emit_unified_call(
|
||||||
|
Some(dst),
|
||||||
|
CallTarget::Global(fname.clone()),
|
||||||
|
call_args,
|
||||||
|
)?;
|
||||||
|
builder.annotate_call_result_from_func_name(dst, &fname);
|
||||||
|
return Ok(Some(dst));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: treat me.method(...) as a static method on the enclosing box.
|
// Fallback: treat me.method(...) as a static method on the enclosing box.
|
||||||
|
|||||||
@ -221,8 +221,12 @@ impl NyashRunner {
|
|||||||
if std::env::var("NYASH_BOX_FACTORY_POLICY").is_err() {
|
if std::env::var("NYASH_BOX_FACTORY_POLICY").is_err() {
|
||||||
cmd.env("NYASH_BOX_FACTORY_POLICY", "builtin_first");
|
cmd.env("NYASH_BOX_FACTORY_POLICY", "builtin_first");
|
||||||
}
|
}
|
||||||
|
// Stage‑1 CLI 経路では既定で using 適用を無効化し、
|
||||||
|
// prefix は空(HAKO_STAGEB_APPLY_USINGS=0)とする。
|
||||||
|
// UsingResolver/UsingCollector の検証は専用テストで行い、
|
||||||
|
// CLI 本線はシンプルな Program(JSON) 生成に集中させる。
|
||||||
if std::env::var("HAKO_STAGEB_APPLY_USINGS").is_err() {
|
if std::env::var("HAKO_STAGEB_APPLY_USINGS").is_err() {
|
||||||
cmd.env("HAKO_STAGEB_APPLY_USINGS", "1");
|
cmd.env("HAKO_STAGEB_APPLY_USINGS", "0");
|
||||||
}
|
}
|
||||||
if std::env::var("NYASH_ENABLE_USING").is_err() {
|
if std::env::var("NYASH_ENABLE_USING").is_err() {
|
||||||
cmd.env("NYASH_ENABLE_USING", "1");
|
cmd.env("NYASH_ENABLE_USING", "1");
|
||||||
|
|||||||
142
src/tests/mir_stage1_cli_emit_program_min.rs
Normal file
142
src/tests/mir_stage1_cli_emit_program_min.rs
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
use crate::ast::ASTNode;
|
||||||
|
use crate::backend::VM;
|
||||||
|
use crate::mir::printer::MirPrinter;
|
||||||
|
use crate::mir::{instruction::MirInstruction, types::CompareOp, MirCompiler, MirVerifier};
|
||||||
|
use crate::parser::NyashParser;
|
||||||
|
|
||||||
|
fn ensure_stage3_env() {
|
||||||
|
std::env::set_var("NYASH_PARSER_STAGE3", "1");
|
||||||
|
std::env::set_var("HAKO_PARSER_STAGE3", "1");
|
||||||
|
std::env::set_var("NYASH_ENABLE_USING", "1");
|
||||||
|
std::env::set_var("HAKO_ENABLE_USING", "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bundle Stage1Cli 本体 + 最小 Main を 1 ソースにまとめたフィクスチャ。
|
||||||
|
/// - using 解決を Stage‑0 ランナーや hako.toml に頼らず、このテスト内だけで完結させる。
|
||||||
|
fn stage1_cli_fixture_src() -> String {
|
||||||
|
let stage1_cli_src = include_str!("../../lang/src/runner/stage1_cli.hako");
|
||||||
|
let test_main_src = r#"
|
||||||
|
using lang.src.runner.stage1_cli as Stage1Cli
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main(args) {
|
||||||
|
env.set("HAKO_STAGEB_APPLY_USINGS", "1")
|
||||||
|
env.set("HAKO_STAGEB_MODULES_LIST", "Foo=foo/bar.hako")
|
||||||
|
env.set("STAGE1_SOURCE_TEXT", "using \"foo/bar.hako\" as Foo\n")
|
||||||
|
local prog = Stage1Cli.emit_program_json("apps/tests/stage1_using_minimal.hako")
|
||||||
|
print("[stage1_cli_emit_program_min] prog=" + ("" + prog))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
format!("{stage1_cli_src}\n\n{test_main_src}")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stage1Cli.emit_program_json 経路の最小再現を Rust テスト側に持ち込むハーネス。
|
||||||
|
/// - apps/tests/stage1_cli_emit_program_min.hako と同じ形で Stage1Cli を呼び出す。
|
||||||
|
/// - ここでは「MIR/SSA が壊れずモジュールが verify できるか」までを確認し、
|
||||||
|
/// 実際の VM 実行時の型崩れは別フェーズで VM テストとして扱う前提。
|
||||||
|
#[test]
|
||||||
|
fn mir_stage1_cli_emit_program_min_compiles_and_verifies() {
|
||||||
|
ensure_stage3_env();
|
||||||
|
let src = stage1_cli_fixture_src();
|
||||||
|
|
||||||
|
let ast: ASTNode = NyashParser::parse_from_string(&src).expect("parse ok");
|
||||||
|
let mut mc = MirCompiler::with_options(false);
|
||||||
|
let cr = mc.compile(ast).expect("compile");
|
||||||
|
|
||||||
|
// Optional: dump MIR when debugging this path
|
||||||
|
if std::env::var("NYASH_STAGE1_MIR_DUMP").ok().as_deref() == Some("1") {
|
||||||
|
let printer = MirPrinter::verbose();
|
||||||
|
let txt = printer.print_module(&cr.module);
|
||||||
|
eprintln!("=== MIR stage1_cli_emit_program_min ===\n{}", txt);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut verifier = MirVerifier::new();
|
||||||
|
if let Err(errors) = verifier.verify_module(&cr.module) {
|
||||||
|
for e in &errors {
|
||||||
|
eprintln!("[rust-mir-verify] {}", e);
|
||||||
|
}
|
||||||
|
panic!("MIR verification failed for stage1_cli_emit_program_min");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// VM 実行まで進めて、現在発生している String > Integer の型エラーを Rust テスト内で再現する。
|
||||||
|
#[test]
|
||||||
|
fn mir_stage1_cli_emit_program_min_exec_hits_type_error() {
|
||||||
|
ensure_stage3_env();
|
||||||
|
let src = stage1_cli_fixture_src();
|
||||||
|
|
||||||
|
let ast: ASTNode = NyashParser::parse_from_string(&src).expect("parse ok");
|
||||||
|
let mut mc = MirCompiler::with_options(false);
|
||||||
|
let cr = mc.compile(ast).expect("compile");
|
||||||
|
|
||||||
|
// Optional: scan for Compare::Ge instructions to locate suspicious comparisons
|
||||||
|
if std::env::var("NYASH_STAGE1_SCAN_GE")
|
||||||
|
.ok()
|
||||||
|
.as_deref()
|
||||||
|
== Some("1")
|
||||||
|
{
|
||||||
|
for (fname, func) in cr.module.functions.iter() {
|
||||||
|
for (bb_id, bb) in func.blocks.iter() {
|
||||||
|
for inst in bb.instructions.iter() {
|
||||||
|
if let MirInstruction::Compare { op, lhs, rhs, .. } = inst {
|
||||||
|
if *op == CompareOp::Ge {
|
||||||
|
eprintln!(
|
||||||
|
"[stage1-cli/scan] Compare Ge in {} @bb{:?} lhs=%{:?} rhs=%{:?}",
|
||||||
|
fname, bb_id, lhs, rhs
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(term) = &bb.terminator {
|
||||||
|
if let MirInstruction::Compare { op, lhs, rhs, .. } = term {
|
||||||
|
if *op == CompareOp::Ge {
|
||||||
|
eprintln!(
|
||||||
|
"[stage1-cli/scan] Compare Ge(term) in {} @bb{:?} lhs=%{:?} rhs=%{:?}",
|
||||||
|
fname, bb_id, lhs, rhs
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut verifier = MirVerifier::new();
|
||||||
|
if let Err(errors) = verifier.verify_module(&cr.module) {
|
||||||
|
for e in &errors {
|
||||||
|
eprintln!("[rust-mir-verify] {}", e);
|
||||||
|
}
|
||||||
|
panic!("MIR verification failed for stage1_cli_emit_program_min exec path");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut vm = VM::new();
|
||||||
|
let exec = vm.execute_module(&cr.module);
|
||||||
|
match exec {
|
||||||
|
Ok(v) => {
|
||||||
|
panic!(
|
||||||
|
"expected VM exec to hit Stage‑1 CLI wiring error, but it succeeded with value: {}",
|
||||||
|
v.to_string_box().value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let msg = format!("{}", e);
|
||||||
|
if std::env::var("NYASH_STAGE1_MIR_DUMP")
|
||||||
|
.ok()
|
||||||
|
.as_deref()
|
||||||
|
== Some("1")
|
||||||
|
{
|
||||||
|
eprintln!("[stage1-cli/debug] VM exec error: {}", msg);
|
||||||
|
}
|
||||||
|
let is_type_error =
|
||||||
|
msg.contains("unsupported") && (msg.contains("Gt") || msg.contains("compare"));
|
||||||
|
let is_missing_stage1 = msg.contains("Stage1Cli.emit_program_json/1");
|
||||||
|
assert!(
|
||||||
|
is_type_error || is_missing_stage1,
|
||||||
|
"expected Stage‑1 CLI path to fail deterministically (type mismatch or missing Stage1Cli); got: {}",
|
||||||
|
msg
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -431,3 +431,111 @@ static box ParserBoxHarness {
|
|||||||
panic!("MIR verification failed for ParserBoxHarness");
|
panic!("MIR verification failed for ParserBoxHarness");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// resolve_for_source 相当の処理で entries ループと modules_map 参照を同時に行うケースを固定する。
|
||||||
|
/// Region+next_i 形のループと MapBox get/has が組み合わさっても PHI/SSA が崩れないことを確認する。
|
||||||
|
#[test]
|
||||||
|
fn mir_stage1_using_resolver_resolve_with_modules_map_verifies() {
|
||||||
|
ensure_stage3_env();
|
||||||
|
let src = r#"
|
||||||
|
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
||||||
|
|
||||||
|
static box Stage1UsingResolverResolveWithMap {
|
||||||
|
_collect_using_entries(src_unused) {
|
||||||
|
// Minimal JSON-like entries: two usable, one empty name for skip path
|
||||||
|
local json = "[{\"name\":\"A\"},{\"name\":\"B\"},{\"name\":\"\"}]"
|
||||||
|
local out = new ArrayBox()
|
||||||
|
local pos = 0
|
||||||
|
local n = json.length()
|
||||||
|
loop(pos < n) {
|
||||||
|
local next_pos = n
|
||||||
|
local name_idx = JsonFragBox.index_of_from(json, "\"name\":\"", pos)
|
||||||
|
if name_idx < 0 { break }
|
||||||
|
local name = JsonFragBox.read_string_after(json, name_idx + 7)
|
||||||
|
local obj_end = JsonFragBox.index_of_from(json, "}", name_idx)
|
||||||
|
if obj_end < 0 { obj_end = n }
|
||||||
|
if name != "" {
|
||||||
|
local entry = new MapBox()
|
||||||
|
entry.set("name", name)
|
||||||
|
out.push(entry)
|
||||||
|
}
|
||||||
|
next_pos = obj_end + 1
|
||||||
|
pos = next_pos
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
_build_module_map(raw) {
|
||||||
|
// Delimiter分割で key=val を MapBox に積む Region+next_start 形。
|
||||||
|
local map = new MapBox()
|
||||||
|
if raw == null { return map }
|
||||||
|
local delim = "|||"
|
||||||
|
local start = 0
|
||||||
|
local cont = 1
|
||||||
|
loop(cont == 1) {
|
||||||
|
local next_start = raw.length()
|
||||||
|
local next = JsonFragBox.index_of_from(raw, delim, start)
|
||||||
|
local seg = ""
|
||||||
|
if next >= 0 {
|
||||||
|
seg = raw.substring(start, next)
|
||||||
|
next_start = next + delim.length()
|
||||||
|
} else {
|
||||||
|
seg = raw.substring(start, raw.length())
|
||||||
|
cont = 0
|
||||||
|
}
|
||||||
|
if seg.length() > 0 {
|
||||||
|
local eq_idx = JsonFragBox.index_of_from(seg, "=", 0)
|
||||||
|
if eq_idx >= 0 {
|
||||||
|
local key = seg.substring(0, eq_idx)
|
||||||
|
local val = seg.substring(eq_idx + 1, seg.length())
|
||||||
|
if key != "" && val != "" {
|
||||||
|
map.set(key, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start = next_start
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_for_source(src_unused, modules_raw) {
|
||||||
|
local entries = me._collect_using_entries(src_unused)
|
||||||
|
if entries == null { return 0 }
|
||||||
|
local modules_map = me._build_module_map(modules_raw)
|
||||||
|
local prefix = ""
|
||||||
|
local i = 0
|
||||||
|
local n = entries.length()
|
||||||
|
loop(i < n) {
|
||||||
|
local next_i = i + 1
|
||||||
|
local entry = entries.get(i)
|
||||||
|
local name = "" + entry.get("name")
|
||||||
|
if name != "" {
|
||||||
|
prefix = prefix + name
|
||||||
|
if modules_map.has(name) {
|
||||||
|
prefix = prefix + modules_map.get(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = next_i
|
||||||
|
}
|
||||||
|
return prefix.length()
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
// modules_map: A→/a, B→/b, 末尾デリミタ付き
|
||||||
|
return me.resolve_for_source("unused", "A=/a|||B=/b|||")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let ast: ASTNode = NyashParser::parse_from_string(src).expect("parse ok");
|
||||||
|
let mut mc = MirCompiler::with_options(false);
|
||||||
|
let cr = mc.compile(ast).expect("compile");
|
||||||
|
|
||||||
|
let mut verifier = MirVerifier::new();
|
||||||
|
if let Err(errors) = verifier.verify_module(&cr.module) {
|
||||||
|
for e in &errors {
|
||||||
|
eprintln!("[rust-mir-verify] {}", e);
|
||||||
|
}
|
||||||
|
panic!("MIR verification failed for Stage1UsingResolverResolveWithMap");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ pub mod mir_loopform_conditional_reassign;
|
|||||||
pub mod mir_loopform_exit_phi;
|
pub mod mir_loopform_exit_phi;
|
||||||
pub mod mir_loopform_complex;
|
pub mod mir_loopform_complex;
|
||||||
pub mod mir_static_box_naming;
|
pub mod mir_static_box_naming;
|
||||||
|
pub mod mir_stage1_cli_emit_program_min;
|
||||||
pub mod mir_stage1_using_resolver_verify;
|
pub mod mir_stage1_using_resolver_verify;
|
||||||
pub mod stage1_cli_entry_ssa_smoke;
|
pub mod stage1_cli_entry_ssa_smoke;
|
||||||
pub mod mir_stageb_like_args_length;
|
pub mod mir_stageb_like_args_length;
|
||||||
|
|||||||
Reference in New Issue
Block a user