Span trace utilities and runner source hint
This commit is contained in:
@ -14,6 +14,26 @@
|
||||
- JoinIR は「PHI/SSA の実装負担を肩代わりする層」として導入し、ヘッダ/exit φ や BodyLocal の扱いを **関数の引数と継続** に吸収していく。
|
||||
- PHI 専用の箱(HeaderPhiBuilder / ExitPhiBuilder / BodyLocalPhiBuilder など)は、最終的には JoinIR 降ろしの補助に縮退させることを目標にする。
|
||||
|
||||
### JoinIR ロワーが「やらないこと」チェックリスト(暴走防止用)
|
||||
|
||||
JoinIR への変換はあくまで「LoopForm で正規化された形」を前提にした **薄い汎用ロワー** に寄せる。
|
||||
このため、以下は **JoinIR ロワーでは絶対にやらない**こととして明示しておく。
|
||||
|
||||
- 条件式の **中身** を解析しない(`i < n` か `flag && x != 0` かを理解しない)
|
||||
- 見るのは「header ブロックの succ が 2 本あって、LoopForm が body/exit を教えてくれるか」だけ。
|
||||
- 多重ヘッダ・ネストループを自力で扱おうとしない
|
||||
- LoopForm 側で「単一 header / 単一 latch のループ」として正規化できないものは、JoinIR 対象外(フォールバック)。
|
||||
- LoopForm/VarClass で判定できない BodyLocal/ExitLiveness を、JoinIR 側で推測しない
|
||||
- pinned/carrier/exit 値は LoopVarClassBox / LoopExitLivenessBox からだけ受け取り、独自解析はしない。
|
||||
- 各ループ用に「特別な lowering 分岐」を増やさない
|
||||
- `skip_ws` / `trim` / `stageb_*` / `stage1_using_resolver` 向けの per-loop lowering は Phase 27.x の実験用足場であり、最終的には Case A/B/D 向けの汎用ロワーに吸収する。
|
||||
|
||||
> 要するに: JoinIR は「LoopForm+変数分類の結果」だけを入力にして、
|
||||
> ループ/if の **構造(続行 or exit の2択+持ち回り変数)** を関数呼び出しに写す箱だよ。
|
||||
> 構文パターンや条件式そのものを全網羅で理解しに行くのは、この層の責務から外す。
|
||||
|
||||
Phase 28 メモ: generic_case_a ロワーは LoopForm / LoopVarClassBox / LoopExitLivenessBox から実データを読み、minimal_skip_ws の JoinIR を組み立てるステップに進化中。Case A/B/D を汎用ロワーに畳み込む足場として扱う。
|
||||
|
||||
位置づけ
|
||||
- 変換パイプラインにおける位置:
|
||||
|
||||
@ -258,6 +278,115 @@ enum JoinInst {
|
||||
| 構文/概念 | JoinIR での表現 | φ/合流の扱い |
|
||||
|-----------|-----------------|--------------|
|
||||
| if/merge | join 関数呼び出し | join 関数の引数が φ 相当 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 汎用 LoopForm→JoinIR ロワー設計(Case A/B ベース案)
|
||||
|
||||
Phase 28 以降は、`skip_ws` や `trim` のような「ループごとの lowering」を増やすのではなく、
|
||||
LoopForm v2 と変数分類箱を入力にした **汎用ロワー** で Case A/B 型ループをまとめて JoinIR に落とすのがゴールになる。
|
||||
|
||||
ここでは、その v1 として **単一ヘッダの Case A/B ループ** を対象にした設計をまとめておく。
|
||||
|
||||
### 7-1. 入力と前提条件
|
||||
|
||||
汎用ロワーが見るのは、次の 4つだけに限定する。
|
||||
|
||||
- `LoopForm` / `ControlForm`(構造)
|
||||
- `preheader`, `header`, `body`, `latch`, `exit`, `continue_merge` の各ブロック ID
|
||||
- `header` ブロックの succ がちょうど 2 本であること
|
||||
- LoopForm が「どちらが body 側 / exit 側か」を与えてくれていること
|
||||
- `LoopVarClassBox`(変数分類)
|
||||
- pinned: ループ中で不変な変数集合
|
||||
- carriers: ループごとに更新される LoopCarried 変数集合
|
||||
- body-local: ループ内部だけで完結する変数集合(基本は JoinIR では引数にしない)
|
||||
- `LoopExitLivenessBox`(Exit 後で必要な変数)
|
||||
- exit ブロック以降で実際に参照される変数集合 `E`
|
||||
- 条件式の形そのもの(`i < n` か `1 == 1` か等)は **見ない**
|
||||
- 汎用ロワーが使うのは「header の succ が body/exit に分かれている」という事実だけ。
|
||||
|
||||
前提条件として、v1 では次のようなループだけを対象にする。
|
||||
|
||||
- LoopForm が「単一 header / 単一 latch の loop」として構築できていること。
|
||||
- header の succ が 2 本で、ControlForm が `(cond → {body, exit})` を一意に教えてくれること。
|
||||
- break/continue が LoopForm の設計どおり `exit` / `latch` に正規化されていること。
|
||||
|
||||
これを満たさないループ(多重ヘッダ・ネスト・例外的な jump 等)は **JoinIR 対象外(フォールバック)** とする。
|
||||
|
||||
### 7-2. 出力の形(ループごとの JoinFunction セット)
|
||||
|
||||
Case A/B 型の単一ヘッダ loop について、汎用ロワーは原則として次の 2 関数を生成する。
|
||||
|
||||
- `loop_step(pinned..., carriers..., k_exit)`
|
||||
- 引数:
|
||||
- pinned: LoopVarClassBox の pinned 変数(ループ外で初期化され、ループ中不変)
|
||||
- carriers: LoopVarClassBox の carriers(ループをまたいで値を持ち回る)
|
||||
- `k_exit`: ループを抜けたあとの処理を表す継続
|
||||
- 本体:
|
||||
- header 条件の判定(`header` ブロック)
|
||||
- body/latch の処理(`body`/`latch` ブロックから拾った Compute/BoxCall 等)
|
||||
- break/continue の分岐を、それぞれ `k_exit(exit_args...)` / `loop_step(next_carriers..., k_exit)` に変換したもの
|
||||
- `k_exit` 相当の関数(もしくは呼び出し先関数の entry)
|
||||
- ExitLiveness が教えてくれる `E`(exit 後で必要な変数)を引数とし、
|
||||
exit ブロック以降の処理をそのまま MIR→JoinIR で表現したもの。
|
||||
|
||||
これにより:
|
||||
|
||||
- header φ: `LoopHeaderShape` / carriers 引数として `loop_step` に吸収される。
|
||||
- exit φ: `LoopExitShape` / exit 引数として `k_exit` に吸収される。
|
||||
- LoopCarried 変数は常に `loop_step` の引数経由で再帰されるので、PHI ノードは JoinIR 側では不要になる。
|
||||
|
||||
### 7-3. 汎用ロワー v1 のアルゴリズム(Case A/B)
|
||||
|
||||
Case A/B を対象にした最初の汎用ロワーは、次のような流れになる。
|
||||
|
||||
1. **対象ループかどうかのチェック**
|
||||
- LoopForm が単一 header / 単一 latch を持つことを確認。
|
||||
- header の succ が 2 本で、ControlForm が body/exit を特定していることを確認。
|
||||
- 条件: LoopForm の invariants を満たすループだけを対象にし、それ以外は `None` でフォールバック。
|
||||
|
||||
2. **変数セットの決定**
|
||||
- pinned 集合 `P` と carriers 集合 `C` を LoopVarClassBox から取得。
|
||||
- ExitLiveness から exit 後で必要な変数集合 `E` を取得。
|
||||
- `LoopHeaderShape` と `LoopExitShape` を構築しておき、`loop_step` / `k_exit` の引数順を固定。
|
||||
|
||||
3. **`loop_step` 関数の生成**
|
||||
- JoinFunction を新規に作成し、`params = P ∪ C`(+必要なら `k_exit`)とする。
|
||||
- header ブロックの Compare/BinOp から「続行 or exit」の判定命令を MirLikeInst として移植。
|
||||
- body/latch ブロックの Compute / BoxCall を順に MirLikeInst へ写し、carrier 更新を `C_next` として集約。
|
||||
- break:
|
||||
- LoopForm/ControlForm が break 経路としてマークしたブロックからは、`Jump { cont: k_exit, args: exit_values }` を生成。
|
||||
- continue:
|
||||
- latch への backedge 経路からは、`Call { func: loop_step, args: pinned..., carriers_next..., k_next: None/dst=None }` を生成。
|
||||
|
||||
4. **エントリ関数からの呼び出し**
|
||||
- 元の MIR 関数の entry から、LoopForm が示す preheader までの処理を MirLikeInst として保持。
|
||||
- preheader の最後で `loop_step(pinned_init..., carriers_init..., k_exit)` 呼び出しを挿入。
|
||||
|
||||
5. **Exit 継続の構築**
|
||||
- exit ブロック以降の MIR 命令を、`k_exit` 相当の JoinFunction か、呼び出し先関数の entry に写す。
|
||||
- `E` の各変数を引数として受け取り、そのまま下流の処理に流す。
|
||||
|
||||
### 7-4. 既存 per-loop lowering との関係
|
||||
|
||||
Phase 27.x で実装した以下の lowering は、この汎用ロワーの「見本」として扱う。
|
||||
|
||||
- `skip_ws` 系: minimal_ssa_skip_ws(Case B: `loop(1 == 1)` + break)
|
||||
- `FuncScanner.trim_minimal`: Case D の簡易版(`loop(e > b)` + continue+break)
|
||||
- `FuncScanner.append_defs_minimal`: Case A(配列走査)
|
||||
- `Stage1UsingResolver minimal`: Case A/B の混合(Region+next_i 形)
|
||||
- StageB minimal ループ: Case A の defs/body 抽出
|
||||
|
||||
Phase 28 では:
|
||||
|
||||
- まず minimal_ssa_skip_ws だけを対象に、`generic_case_a` のような汎用ロワー v1 を実装し、
|
||||
既存の手書き JoinIR と同じ構造が得られることを確認する(実装済み: `lower_case_a_loop_to_joinir_for_minimal_skip_ws`)。
|
||||
- そのあとで `trim_minimal` / `append_defs_minimal` / Stage‑1 minimal / StageB minimal に順に適用し、
|
||||
per-loop lowering を「汎用ロワーを呼ぶ薄いラッパー」に置き換えていく。
|
||||
- 最終的には per-loop lowering ファイルは削減され、LoopForm+変数分類箱から JoinIR へ落とす汎用ロワーが SSOT になることを目指す。
|
||||
|
||||
このセクションは「汎用ロワー設計のターゲット像」として置いておき、
|
||||
実装は Phase 28-midterm のタスク(generic lowering v1)で少しずつ進めていく。
|
||||
| loop | step 再帰 + k_exit 継続 | LoopCarried/Exit の値を引数で渡す |
|
||||
| break | k_exit 呼び出し | φ 不要(引数で値を渡す) |
|
||||
| continue | step 呼び出し | φ 不要(引数で値を渡す) |
|
||||
|
||||
@ -2,6 +2,14 @@
|
||||
|
||||
Status: design+partial implementation(Stage1 ビルド導線の初期版まで)
|
||||
|
||||
## Stage‑1 CLI 実験メモ(2025-XX)
|
||||
|
||||
- 2025-XX: `NYASH_USE_STAGE1_CLI=1 STAGE1_EMIT_PROGRAM_JSON=1 ./target/release/hakorune apps/tests/minimal_ssa_skip_ws.hako` を実行。
|
||||
- ブリッジ自体は発火し、`stage1-cli/debug` ログと `Stage1CliMain.main/0` が生成されることを確認。
|
||||
- しかし VM 側で `vm step budget exceeded`(max_steps=2000000)で終了。プラグイン未ロード警告あり(FileBox/ArrayBox など)。
|
||||
- 対応メモ: ① NYASH_DISABLE_PLUGINS=1 + core-ro で FileBox は代替読込、② `HAKO_VM_MAX_STEPS` をさらに引き上げる or パーサ前処理を削る検討、③ Stage‑B 自前ビルドは `Unknown method 'main' on InstanceBox` で失敗中(StageBDriverBox 呼び出しが壊れている)。
|
||||
- Rust VM 側で `vm step budget exceeded` に **fn / bb / last_inst 情報** + Span(MIR に付いていれば .hako 行番号)を付与。今回の Stage‑1 CLI 実行では `fn=ParserBox.parse_program2/1 ... (lang/src/runner/stage1_cli.hako:1:1)` まで出力された(Span は通ったが行位置はまだ粗い)。
|
||||
|
||||
## 25.1 サブフェーズの整理(a〜e 概要)
|
||||
|
||||
- **25.1a — Stage1 Build Hotfix(配線)**
|
||||
|
||||
5
docs/development/roadmap/phases/phase-25.1/span-trace.md
Normal file
5
docs/development/roadmap/phases/phase-25.1/span-trace.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Phase 25.1 — Span Trace Mini Note
|
||||
|
||||
- 方針: MIR 命令に AST Span を持たせ、VMError (StepBudgetExceeded) で fn/bb/inst に加えて .hako 行番号を出す。
|
||||
- 実装: MirInstruction 生成時に current_span を保存し、VM 側で last_inst_idx から Span を引いてエラーに埋め込む。Span が無い場合は従来どおり fn/bb/inst のみ。
|
||||
- 状態: Stage‑1 CLI の MIR には Span 未付与なので行番号はまだ出ていないが、Span 付き MIR なら `... (file.hako:line:col)` まで表示できる。
|
||||
@ -0,0 +1,97 @@
|
||||
# Phase 25.1 — Stage‑1 CLI / BuildBox ループ & Box 依存分析メモ
|
||||
|
||||
目的
|
||||
- `Stage‑1 CLI → BuildBox.emit_program_json_v0` 実行時に VM が step budget を食い尽くしている原因を、
|
||||
ループ構造と BoxCall 依存の観点から整理しておくためのメモだよ。
|
||||
|
||||
## 1. 観測された症状
|
||||
|
||||
- コマンド例:
|
||||
```bash
|
||||
NYASH_CLI_VERBOSE=2 \
|
||||
NYASH_USE_STAGE1_CLI=1 \
|
||||
STAGE1_EMIT_PROGRAM_JSON=1 \
|
||||
./target/release/hakorune apps/tests/minimal_ssa_skip_ws.hako
|
||||
```
|
||||
- 状態:
|
||||
- `[stage1-bridge/trace]` + `[stage1-cli/debug] emit_program_json ENTRY` が出ており、
|
||||
Stage1CliMain.main/0 〜 stage1_cli.hako 実行までは到達している。
|
||||
- その後 `BuildBox.emit_program_json_v0` 実行中に VM が `vm step budget exceeded`(max_steps=1_000_000→2_000_000 でも NG)。
|
||||
- FileBox/ArrayBox などの plugin 未ロード警告も併発。
|
||||
- 現状は **ステップ上限+プラグイン依存** が阻害要因となって、program-json 自体は得られていない。
|
||||
|
||||
## 2. emit_program_json_v0 周辺のループ構造(概観)
|
||||
|
||||
調査時点でのループ構造のざっくり分類(ファイルは概略)だよ。
|
||||
|
||||
| ループ | ファイル | 複雑度 | 危険度 |
|
||||
|------------------------------|--------------------------|-------------|--------|
|
||||
| `ParserBox.parse_program2` | stage1_cli.hako:84 あたり | exponential | 🔴 最有力 |
|
||||
| alias_table パース (opts) | build_box.hako:60-73 | O(n²) | 🟡 中 |
|
||||
| alias_table パース (env) | build_box.hako:94-107 | O(n²) | 🟡 中 |
|
||||
| `FileBox._read_file` | stage1_cli.hako:67 | I/O bound | ⚠️ 注視 |
|
||||
| bundle_names 重複チェック | build_box.hako:41-51 | O(n²) | 🟢 許容 |
|
||||
|
||||
直感:
|
||||
- alias_table や bundle_names の O(n²) 系ループは、入力サイズが小さい間は budget を食い尽くすレベルではない。
|
||||
- `ParserBox.parse_program2` は Stage‑3 parser の statement/expression を全部受け持つため、
|
||||
実装によっては **指数的** にブロック数・ステップ数が増え得る。
|
||||
- FileBox は I/O bound なので、budget ではなくタイムアウト側のリスクが大きい。
|
||||
|
||||
## 3. Box 依存度マトリックス
|
||||
|
||||
Stage‑1 CLI / BuildBox ラインで使われている Box の洗い出しと、
|
||||
「core だけで足りるか/外部プラグインが要るか」のざっくり分類だよ。
|
||||
|
||||
| Box | 用途 | プラグイン依存 | 影響度 |
|
||||
|-----------|-------------------------------|---------|-------------|
|
||||
| FileBox | `_read_file` で source 読み込み | YES | 🔴 CRITICAL |
|
||||
| ParserBox | `parse_program2` AST parse | NO | 🟡 MEDIUM |
|
||||
| ArrayBox | bundles 配列格納 | NO | 🟢 LOW |
|
||||
| StringBox | alias_table / bundle_names parse | NO | 🟢 LOW |
|
||||
|
||||
メモ:
|
||||
- FileBox は core-ro だけでは厳しく、実際には plugin / env 設定に依存するケースが多い。
|
||||
- ParserBox は Stage‑3 parser の仕様そのものなので、LoopForm/JoinIR と独立に「parser の loop 形」問題として見る必要がある。
|
||||
|
||||
## 4. 今後の観測テンプレ(提案)
|
||||
|
||||
ステップ budget 溶けの原因をもう少し切り分けるために、次のようなコマンドで実験できるよ。
|
||||
|
||||
```bash
|
||||
# Test 1: FileBox なしで inline source を直書き
|
||||
NYASH_STAGE1_MODE=emit-program \
|
||||
STAGE1_SOURCE_TEXT='static box Main { main(args) { return 0 } }' \
|
||||
./target/release/nyash lang/src/runner/stage1_cli.hako
|
||||
|
||||
# Test 2: プラグイン OFF で挙動を見る
|
||||
NYASH_DISABLE_PLUGINS=1 \
|
||||
NYASH_STAGE1_MODE=emit-program \
|
||||
STAGE1_SOURCE_TEXT='static box Main { main(args) { return 0 } }' \
|
||||
./target/release/nyash lang/src/runner/stage1_cli.hako
|
||||
|
||||
# Test 3: ステップトレースを有効化
|
||||
NYASH_VM_STATS=1 \
|
||||
NYASH_STAGE1_MODE=emit-program \
|
||||
STAGE1_SOURCE_TEXT='static box Main { main(args) { return 0 } }' \
|
||||
./target/release/nyash lang/src/runner/stage1_cli.hako 2>&1 | grep -i step
|
||||
```
|
||||
|
||||
目的:
|
||||
- FileBox を完全にバイパスできる inline source モードで試し、
|
||||
それでも budget が溶けるかを見る(ParserBox 起因かどうかの切り分け)。
|
||||
- プラグイン OFF/ON で挙動がどれだけ変わるか確認する。
|
||||
|
||||
## 5. 方針メモ
|
||||
|
||||
- Stage‑1 CLI の「ブリッジまでは動いているが、そのあとで止まる」という現象は、
|
||||
- Stage‑B/BuildBox 経由の Program JSON emit
|
||||
- ParserBox / FileBox のループ
|
||||
のどこに問題があるかを 1 ループずつ切り出していけば、局所化できる見込み。
|
||||
- joinIR 側とは独立に、このファイルでは「Stage‑1 CLI 実行ルートのうち、どのループと Box が危ないか」の観測結果を積み上げていくよ。
|
||||
|
||||
## 6. VM エラー出力改善メモ(2025-XX)
|
||||
|
||||
- Rust MIR Interpreter の step budget エラーに **fn / bb / last_inst / steps** + Span を付与したよ。
|
||||
- 例(Span 付き MIR の場合): `vm step budget exceeded (max_steps=2000000, steps=2000001) at bb=bb49 fn=ParserBox.parse_program2/1 last_inst_idx=12 last_inst_bb=bb48 last_inst=... (file.hako:312:7)`
|
||||
- Stage‑1 CLI の現行 MIR には Span が載っていないため、いまは fn/bb/inst だけが出る(`apps/tests/minimal_ssa_skip_ws.hako` 実行で確認)。Span 付与を通せば .hako 行まで出る想定。
|
||||
@ -0,0 +1,75 @@
|
||||
# Phase 25.1 — Stage‑B Selfhost main 解決メモ(InstanceBox / Static Box 呼び出し)
|
||||
|
||||
目的
|
||||
- `tools/selfhost/build_stage1.sh` 実行時に発生している
|
||||
`Unknown method 'main' on InstanceBox` の根本原因を整理し、
|
||||
どの Box / メソッド名のズレかを明確にするための観測メモだよ。
|
||||
|
||||
## 1. 現状の観測結果
|
||||
|
||||
- エラー発生箇所:
|
||||
- `src/backend/mir_interpreter/handlers/boxes.rs:105` 付近(InstanceBox 用のメソッドディスパッチ)。
|
||||
- 症状:
|
||||
- VM が **InstanceBox** という generic 型に対して `"main"` を探している。
|
||||
- 期待されるのは `StageBDriverBox.main(...)` のような「ユーザー定義 static box 上の main」。
|
||||
- Rust の `type_name()` macro がユーザー定義 Box を generic 型名(InstanceBox)として返しているため、
|
||||
ログ上は常に `box_type_name=InstanceBox` となり、「どの box の main を探しているか」が見えづらい。
|
||||
|
||||
## 2. StageBDriverBox 側の定義
|
||||
|
||||
- 定義ファイル:
|
||||
- `lang/src/compiler/entry/compiler_stageb.hako:1141-1367`
|
||||
- 構造:
|
||||
- `static box StageBDriverBox { main(args) { ... } }` という形で main が定義されている。
|
||||
- エントリポイント:
|
||||
- `Main.main()` から `StageBDriverBox.main()` を呼ぶ導線が 1373-1377 行あたりに存在。
|
||||
- 直感的には:
|
||||
- **`.hako` 側は正しく main を定義している**が、
|
||||
- VM 側の `InstanceBox` 呼び出し時に **メソッド名の解決 or Box 名の正規化** でズレている可能性が高い。
|
||||
|
||||
## 3. ログ追加の案(型名+メソッド名の観測)
|
||||
|
||||
次のようなログを handlers/boxes.rs の Unknown method 分岐直前に入れておくと、
|
||||
どの Box / 関数名の組み合わせが原因かが見やすくなるよ。
|
||||
|
||||
```rust
|
||||
eprintln!(
|
||||
"[vm/method-dispatch] Unknown method on Box: box_type_name={}, method_name={}",
|
||||
recv_box.type_name(), // InstanceBox 側の type_name
|
||||
method, // 探しにいったメソッド名(例: \"main\" / \"StageBDriverBox.main/0\")
|
||||
);
|
||||
```
|
||||
|
||||
※ 実際には `type_name()` は常に `InstanceBox` を返すので、
|
||||
実 box 名は `BoxDeclaration` / `InstanceBox` 内のフィールドから取り出す必要があるかもしれない。
|
||||
|
||||
## 4. 次に見ると良い箇所
|
||||
|
||||
### 4-1. MIR 側の BoxCall 受け側
|
||||
|
||||
- 目的:
|
||||
- Stage‑B selfhost の MIR で、「どの global / boxcall 名で StageBDriverBox.main を呼んでいるか」を確認する。
|
||||
- 手順の目安:
|
||||
- Stage‑B ランチャの MIR を `--dump-mir` 等でダンプ。
|
||||
- `call_global "StageBDriverBox.main/..."` のような形になっているかを見る。
|
||||
|
||||
### 4-2. MirBuilder の receiver / callee 解決
|
||||
|
||||
- ファイル候補:
|
||||
- `src/mir/builder/calls/unified_emitter.rs`
|
||||
- `src/backend/mir_interpreter/handlers/global.rs`
|
||||
- 見たいこと:
|
||||
- StaticMethodId / NamingBox を通った後に、
|
||||
`"StageBDriverBox.main/arity"` がどのように解決されているか。
|
||||
- InstanceBox に対するメソッド呼び出しのときに、
|
||||
メソッド名から Box 名が正しく分離されているか。
|
||||
|
||||
## 5. 方針メモ
|
||||
|
||||
- joinIR ラインとは独立して、この問題は「Stage‑B driver / InstanceBox / naming」の層で解決する。
|
||||
- まずは:
|
||||
- どの InstanceBox/関数名の組み合わせで Unknown が出ているかをログで観測。
|
||||
- `.hako` 側の StageBDriverBox 定義と、Rust 側のメソッド名解決ロジックの差分を見る。
|
||||
- 修正自体(命名・正規化の調整)は Phase 25.1 の別サブフェーズで扱う前提として、
|
||||
このファイルは「現状の観測結果と差分の候補」を残しておくためのメモだよ。
|
||||
|
||||
Reference in New Issue
Block a user