# メモリ管理モデルリファレンス

このリファレンスは、Taida の「完全自動メモリ管理」という哲学が、
各バックエンドでどのような実装戦略として具体化されているかを定義します。
Taida のユーザーコードはメモリ確保・解放を一切書きませんが、その下で
動くバックエンドは互いに異なる方式を採用しています。本書はその差分を
公開仕様として明示し、アドオン作者・パフォーマンス評価者・言語学習者の
それぞれが必要とする観点に答えることを目的とします。

哲学そのものは [PHILOSOPHY.md](../../PHILOSOPHY.md) で固定されています。
本書は実装モデルの説明であり、PHILOSOPHY.md の抽象を書き換えるもの
ではありません。観測ゲートの定義は [パフォーマンスゲート](perf_gates.md)、
WASM プロファイル境界は [WASM プロファイル](wasm_profiles.md) を
参照してください。

---

## バックエンド一覧

| バックエンド | 実装方式 | 寿命戦略 |
|--------------|----------|----------|
| インタプリタ | アトミック参照カウントで共有する不変構造 | 参照カウント、`clone` 操作は O(1) |
| ネイティブ (cdylib via AOT) | スレッドローカルの bump アリーナ + 小オブジェクト freelist + 参照カウント | リクエスト境界でアリーナを巻き戻す。1 スレッド当たりのアリーナ上限は 256 MiB |
| WASM (`wasm-min` / `wasm-wasi` / `wasm-edge` / `wasm-full`) | リニアメモリ上の bump allocator と watermark 形式のアリーナ API | bump カーソルの巻き戻し。WebAssembly の `memory.grow` は行うが `memory.shrink` は行わない |

これらは「完全自動」という抽象表現の下に隠れている技術選択です。
ユーザープログラムから観測できる値の振る舞い (不変性、デフォルト値の
存在、`Bytes` のゼロコピー不変条件) はバックエンド間で完全に一致
しますが、内部で値がどう生存するかは上表のとおり大きく異なります。

---

## インタプリタ — 参照カウントによる不変共有

インタプリタは言語意味論の基準実装です。値はアトミック参照カウント付き
の不変構造として共有されます。

- 文字列 (`Str`) は内部で「短い文字列をそのまま持つ表現」と「長い文字列
  の連結を差分蓄積で扱う rope 表現」の 2 形態を切り替え、`+` などの
  連結操作で `O(N²)` を起こさないようにしています。
- リスト (`@[T]`)、ぶちパック (`@(...)`)、`Bytes` も同様にアトミック
  参照カウント付きの不変構造です。
- 関数間で値を受け渡したり、リスト・ぶちパックを入れ子にして伝播
  させたりするときの値クローンは、内部バッファの複製ではなくアトミック
  カウントの 1 回増加で完了します。

### `Bytes` のゼロコピー

`Bytes` は内部で共有バイトバッファに対するビューとして表現されます。
バッファ本体・ビュー開始位置・ビュー長の 3 要素を保持し、`Slice[bytes(b), s, e]`
は同じバッファを共有したままビュー範囲を調整した新しい `Bytes` を
返します。

等価判定・順序付け・ハッシュはビュー範囲のバイト列を対象に行うため、
別のバッファに同一バイト列を持つ 2 つのビューは等しいと判定されます。

この仕様は [パフォーマンスゲート](perf_gates.md) の Bytes I/O
不変条件 (「`Bytes` カーソルのスライスはチャンクごとにバッファ全体を
複製しない」) を実装側で支える根拠です。

---

## ネイティブ — スレッドローカルアリーナ + freelist + 参照カウント

ネイティブバックエンドはユーザーコードを cdylib に AOT コンパイル
します。生成されたコードは下記の 3 層を組み合わせてメモリを管理
します。

### 1. スレッドローカル bump アリーナ

- チャンクサイズは 2 MiB、最大チャンク数は 128 個。1 スレッド当たり
  の上限は **256 MiB** です。
- アロケーションは 16 バイト境界に切り上げて bump 配置します。
- 1024 バイトを超える要求はアリーナを経由せず、ヒープ確保にフォール
  バックします。
- 解放時はポインタがアリーナ範囲内かを判定し、内側ならばアリーナの
  巻き戻しに委ね、外側ならばヒープ解放を呼びます。

### 2. 小オブジェクト freelist

ホットパスの確保コストを抑えるため、サイズ別の freelist を保持します。

- 4 フィールドのぶちパック
- 容量 16 のリスト
- 文字列バケット (32 / 64 / 128 / 256 / 512 / 1024 バイトの 6 段階)

各 freelist のエントリはアリーナ由来かもしれず、ヒープ由来かも
しれません。参照カウントが 0 に落ちたタイミングで該当バケットに
返却し、次の確保で同じバケットから取り出します。サイズ不一致はヘッダ
のキャパシティ確認で弾かれます。

### 3. リクエスト境界でのアリーナリセット

長期実行サーバー (`httpServe` の keep-alive ループなど) では、
1 リクエストごとに大量の中間オブジェクトが生成され、freelist だけで
は回収しきれないシナリオがあります (パックのフィールド数や文字列長
が固定バケットに合わない場合)。このときワーカーはレスポンスを返した
直後にアリーナ境界のリセットを実施します。

リセットは次の手順で進みます。

1. 各サイズの freelist を走査し、ヒープ由来のエントリだけ解放する。
   アリーナ由来のエントリはアリーナ巻き戻しで一括解放されるため、
   ここでは解放しない。
2. 先頭以外のアリーナチャンクをすべて解放する。
3. 先頭チャンクを保持してオフセットを 0 に巻き戻し、次のリクエストを
   ウォーム状態で迎えられるようにする。

この設計により、ワーカースレッド数を `min(maxConnections, 16)` に
制限したサーバーでは、RSS のプラトーがほぼ `min(maxConnections, 16) × 256 MiB`
で頭打ちになります。

### 参照カウント

スレッドローカル設計の境界を越える値は、ヘッダの参照カウントで管理
されます。値の保持・解放はランタイムが提供する `retain` / `release`
ペアで増減され、参照カウントが 0 に落ちた値は freelist に戻すか、
アリーナの場合は何もせずアリーナリセットに委ねます。アリーナとヒープ
の判別はランタイム内部でアドレス範囲チェックを行って自動的に区別
されます。

---

## 旧 JS ターゲット — オブジェクトフリーズとホスト GC への委譲

旧 JS ターゲットは移行期間の互換機能です。リリースのパリティ契約には
含まれませんが、既存コードの理解と移行のために実装モデルを記録します。
JS トランスパイラはユーザーコードを生成 JavaScript に変換します。
Taida の不変性は、生成された ぶちパック / リスト / `Lax` / `Async` /
JSON 由来オブジェクトすべてに対して `Object.freeze()` を適用すること
で表現されます。

- ぶちパックはフリーズして返されるため、フィールド代入は strict
  mode の TypeError か no-op になります。
- リストはコピー後にフリーズされ、push や splice などの破壊的メソッド
  は TypeError を投げます。
- `Lax` / `Gorillax` / `Async` などのモールド型ラッパーもフリーズ
  されます。
- JSON 鋳造 (`JSON[raw, Schema]()`) の結果も凍結された配列・オブジェクトとして返ります。

メモリ寿命はホスト JS エンジンの GC (V8、SpiderMonkey、
JavaScriptCore など) に完全に委譲されます。Taida 側からは GC の
起動契機を制御せず、世代別 / インクリメンタル / 並行 GC の特性は
そのまま Taida プログラムの停止特性に伝わります。

---

## WASM — bump allocator と watermark アリーナ

WASM バックエンドは [WASM プロファイル](wasm_profiles.md) で定義された
4 プロファイルに分かれますが、メモリレイアウトはすべて単一の bump
allocator を基盤とします。

### 基本動作

- 初回呼び出し時にリンカが提供するヒープ基底をそろえてカーソルを
  初期化します。
- 各呼び出しは要求サイズを 8 バイト境界に切り上げてカーソルを前進
  させ、必要なら WebAssembly の `memory.grow` で 64 KiB ページを
  追加します。
- WebAssembly に `memory.shrink` 相当の操作は存在しないため、bump
  カーソルの巻き戻し以外でアドレス空間を縮小する手段はありません。

`wasm-min` プロファイルはこの bump allocator をそのまま使い、明示的な
解放を行いません。短命なプログラムでは十分です。

### Watermark アリーナ

長期実行ループや大規模なテンソル計算で bump カーソルの単調増加を
止めるため、watermark 形式のアリーナを提供します。

- 現在のカーソル位置をスナップショットして「アリーナ入場ハンドル」
  を返す操作。
- ハンドルを受け取ってカーソルをそこまで巻き戻す「アリーナ退出」
  操作。退出後、その時点までに確保された領域は次回確保で再利用
  されます (WebAssembly はホストにメモリを返却できないため、アドレス
  だけが再利用される形になります)。
- 初期化以降に確保した総バイト数を返す観測操作。リグレッションテスト
  でループのリーク無し性質を検証する用途に使えます。

これらの操作シンボルは `wasm-wasi` および `wasm-full` プロファイル
でホスト向けに export され、`wasm-min` / `wasm-edge` プロファイルでは
WASM リンカの dead-code 削除でモジュールから除去されます。
`wasm-min` のサイズゲート (`hello.wasm` が 512 バイト以下) はこの
除去に依存しています。

### アドオンディスパッチャとの関係

`wasm-full` プロファイルはホスト経由のアドオン呼び出しを受け付けます。
アドオン呼び出しの境界では、引数を bump アリーナにシリアライズして
ホストへ渡し、ホスト側で実行された結果を再びリニアメモリへコピー
して返します。詳細は [WASM プロファイル](wasm_profiles.md) を、
マニフェスト要件は [アドオンマニフェスト](addon_manifest.md) を参照
してください。

---

## バックエンド間で観測される挙動の同一性

メモリ管理の実装は上記のとおりバックエンドごとに異なりますが、
ユーザープログラムから観測される値の挙動は完全に一致します。

- すべての型にデフォルト値が存在し、`null` / `undefined` は発生しない。
- 値は不変であり、共有しても他のスコープから書き換えられない。
- アイデンティティ比較は行わない。等価性は値の内容で判定する。
- `Bytes` のスライスやリストのスライスは共有バッファを保持し、
  同一バッファに対する複数ビューが同時に存在できる。
- 参照カウント / GC / アリーナいずれの戦略であっても、`Lax` /
  `Gorillax` / `Async` の状態判定 (`hasValue()` / `errorInfo()` /
  `getOrDefault()`) はバックエンド共通の意味を返す。

これらの不変条件は [パフォーマンスゲート](perf_gates.md) と
[モールド型ガイド](../guide/05_mold.md) で定義する観測契約に紐付けられ、
テストで継続的に検証されます。

---

## 観測ゲートとの接続

メモリモデルに紐付くゲートは次のとおりです。詳細は
[パフォーマンスゲート](perf_gates.md) を参照してください。

- **Valgrind `definitely-lost = 0`**: ネイティブバックエンドのアリーナ
  解放とヒープ解放経路が完全であることを保証するゲート。
- **ピーク RSS の EWMA 回帰判定**: 30 サンプル以上を蓄積した時点から
  10% 超過で失敗。ネイティブの per-thread アリーナ上限 (256 MiB) と
  リクエスト境界リセットが期待した RSS プラトーを保つことを継続観測
  します。
- **スループット EWMA 回帰判定**: 同じく 30 サンプル以上で 10% 超過
  失敗。freelist とアリーナのキャッシュ効率が低下していないかを観測
  します。
- **`Bytes` ゼロコピー不変条件**: スライスやチャンク化したカーソルが
  バッファ全体を複製しないことをテストで検証します。

これらは「観測ゲート」であり、メモリモデル自体ではありません。
モデルの実装は本書、観測の判定はゲートリファレンスにそれぞれ責務を
分けています。

---

## アドオン作者向け所有権規約

アドオン (cdylib) を `taida-addon` クレートを使って書くときは、
ネイティブバックエンドの寿命戦略を理解した上で次の規約を守って
ください。`wasm-full` プロファイルから同じアドオンを呼び出すケースを
含みます。

`taida-addon` クレートはホスト側ランタイムが提供する vtable
(`TaidaHostV1`) のラッパーを公開しており、アドオン作者はその vtable
経由で値を生成・解放します。低水準のアロケーター関数を直接呼び出す
ことは想定されていません。

### 引数

- アドオン関数が受け取る値はホスト (Taida ランタイム) が所有して
  います。アドオン側は借用しているだけで、ホスト向けの解放呼び出しを
  行ってはいけません。
- 引数のうち文字列・リスト・ぶちパック・`Bytes` ・ `Lax` ・ `Async` は
  内部的に参照カウントまたはアリーナ由来であり、関数戻り後もホストが
  管理を続けます。

### 戻り値

- 新しく値を確保して返す場合は、`taida-addon` クレートが公開する
  生成 API (文字列・リスト・ぶちパック・`Lax` 等のコンストラクタ) を
  使ってください。これにより freelist / アリーナと参照カウントの境界
  が崩れません。
- 大きい (1024 バイトを超える) バッファや、リクエスト境界をまたいで
  生存させたい値は、ランタイムが自動的にヒープ経由のアロケーション
  に倒します。アドオン側で `malloc` / `free` を直接呼ぶことは想定
  されていません。
- 戻り値の所有権はホスト (Taida ランタイム) に移ります。アドオンは
  戻り値に対して解放呼び出しを行いません。

### `wasm-full` プロファイルからの呼び出し

- `wasm-full` でアドオンを呼ぶ場合、引数と戻り値はリニアメモリと
  ホスト側メモリの境界をまたぎます。Taida ランタイムはこの境界で
  値を再シリアライズし、ホスト側で実行されたアドオンの戻り値を
  リニアメモリへコピーし直します。
- アドオン側のコードはネイティブ呼び出しと同じ関数シグネチャで書き
  ますが、リニアメモリへの直接アクセスを前提にしないでください。

### スレッドモデル

- ネイティブの per-thread アリーナと freelist はすべてスレッド
  ローカルです。アドオン関数は呼び出し側スレッドでそのまま実行
  されるため、別スレッドへ値を渡すことは想定されていません。
- どうしても境界を越える必要がある場合は、参照カウント値 (アリーナ
  外に確保された値) のみを境界で渡し、ホストが提供する retain /
  release ペアで管理してください。

これらの規約は [アドオンマニフェスト](addon_manifest.md) と
[アドオン作成ガイド](../guide/13_creating_addons.md) と併せて参照
してください。
