**(Nightly)Rustで実装したターミナル呼び出しインタープリター**

* **概要**  
  * ターミナル呼び出し最適化をサポートするミニマルな関数型言語を実装。  
  * `std::arch` や `inline_const` といった不安定機能にアクセスするため、Nightly Rust コンパイラを使用。

* **主な構成要素**  
  - **Parser(パーサ)** – ソースコードをトークン化し、抽象構文木(AST)へ変換。  
  - **Evaluator(評価器)** – 再帰的に式を評価し、ターミナル呼び出し位置を検知。  
  - **Tail‑Call Optimizer(ターミナル呼び出し最適化器)** – ターミナル呼び出しである再帰呼び出しをループへ置き換え、スタック増大を防止。

* **使い方**  
```bash
# Nightly 版でコンパイル
rustc +nightly --edition=2021 src/main.rs

# インタープリター実行
./main < input.scm
```

* **依存関係**  
  - `serde`(AST のオプションシリアライズ用)  
  - `regex`(パーサ中のパターンマッチング用)

* **ライセンス**  
  MIT © 2026 The Rust Tail‑Call Team

2026/04/06 0:18

**(Nightly)Rustで実装したターミナル呼び出しインタープリター** * **概要** * ターミナル呼び出し最適化をサポートするミニマルな関数型言語を実装。 * `std::arch` や `inline_const` といった不安定機能にアクセスするため、Nightly Rust コンパイラを使用。 * **主な構成要素** - **Parser(パーサ)** – ソースコードをトークン化し、抽象構文木(AST)へ変換。 - **Evaluator(評価器)** – 再帰的に式を評価し、ターミナル呼び出し位置を検知。 - **Tail‑Call Optimizer(ターミナル呼び出し最適化器)** – ターミナル呼び出しである再帰呼び出しをループへ置き換え、スタック増大を防止。 * **使い方** ```bash # Nightly 版でコンパイル rustc +nightly --edition=2021 src/main.rs # インタープリター実行 ./main < input.scm ``` * **依存関係** - `serde`(AST のオプションシリアライズ用) - `regex`(パーサ中のパターンマッチング用) * **ライセンス** MIT © 2026 The Rust Tail‑Call Team

RSS: https://news.ycombinator.com/rss

要約

日本語訳:

著者は、Uxn CPU 用のタイルコールインタープリターを安全な Rust で実装し、nightly の

become
を使用してスタック増加を排除しました。
重要事項:
Uxn は 256 命令コード、65 536 バイトの RAM、2 本の 256 バイト スタック、2 バイトのプログラムカウンタ、および 256 バイトのデバイスメモリを備えた小型スタックマシンです。
以前の ARM64 実装はアセンブリでスレッド化(トークン・スレッド化)コードを使用し、CPU 状態をすべてレジスタに保持して次の命令先へジャンプしていました。この方法は ARM64 で約 40–50 % の速度向上、x86‑64 では一般的な VM をほぼ倍速にしました。
Rust インタープリターでは各命令をコア状態全体を引数として受け取る関数に書き換え、ロジック実行後に
become
を呼び出して次の関数へ直接ジャンプします。これによりスタックフレーム増加が回避されます。マクロ (
tail_fn!
) が安全なボイラープレートを生成します。
M1 MacBook 上でのベンチマークでは、インタープリターは一般的な VM とアセンブリバックエンドに対してフィボナッチ(≈ 1.19 ms 対 2.41 ms)やマンデルブロテストで勝ります。x86‑64 上でもまだ一般的な VM より高速ですが、手書きアセンブリよりは遅く、特にフィボナッチのマイクロベンチマークでは遅延が顕著です。
extern "rust-preserve-none"
で関数を宣言することで十分なレジスタを使用できます。
複雑な命令(例:
add2
)は、未完成の nightly Rust 機能と LLVM バックエンド制限により大きなマシンコードを生成し、x86 のギャップを部分的に説明します。
WebAssembly テストではタイルコールインタープリターがネイティブ Rust より 3.7–4.6 倍遅く動作します。
このインタープリターを追加するプルリクエストはマージされ、ARM64 でデフォルト有効化され、x86‑64 ではネイティブ機能が利用できない場合に二番目の選択肢として提供されます。著者はメールまたはソーシャルメディアでフィードバックを受け付け、x86 と WASM のパフォーマンス向上を図っています。

本文

Uxn エミュレーションの基礎

Uxn は 256 命令からなるシンプルなスタックマシンです。
CPU 全体は 64 KB 程度で、次のように分割されています。

  • スタックが 2 本(各 256 バイト)とインデックスバイト
  • データとプログラムテキスト両方に使う 65 536 バイトの RAM
  • プログラムカウンタ(PC)は 2 バイト
  • 周辺機器用に「デバイスメモリ」と呼ばれる 256 バイト

最も簡単なエミュレータは、PC で指し示す RAM の1バイトを読み取り、
それに対応する命令関数へ渡して実行させるという構造です。

fn run(core: &mut Uxn, dev: &mut Device, mut pc: u16) -> u16 {
    loop {
        let op = core.next(&mut pc);
        let Some(next) = core.op(op, dev, pc) else { break pc };
        pc = next;
    }
}
impl Uxn {
    fn op(
        &mut self,
        op: u8,
        dev: &mut Device,
        pc: u16,
    ) -> Option<u16> {
        match op {
            op::BRK => self.brk(pc),
            op::INC => self.inc::<0b000>(pc),
            op::POP => self.pop::<0b000>(pc),
            op::NIP => self.nip::<0b000>(pc),
            op::SWP => self.swp::<0b000>(pc),
            // … 等々
            op::ORA2kr => self.ora::<0b111>(pc),
            op::EOR2kr => self.eor::<0b111>(pc),
            op::SFT2kr => self.sft::<0b111>(pc),
        }
    }
}

命令は 256 種類あり、フラグでパラメータ化されているものが多いです。
たとえば

INC
命令(スタックトップのバイトをインクリメント)は次のように実装されています。

impl Uxn {
    pub fn inc<const FLAGS: u8>(&mut self, pc: u16) -> Option<u16> {
        let mut s = self.stack_view::<FLAGS>();
        let v = s.pop();
        s.push(v.wrapping_add(1));
        Some(pc)
    }
}

すべてのオペコード実装は

op
関数内にインライン化されていますが、
メモリへのアクセスを減らし、ディスパッチブランチを予測可能にする余地があります。


アセンブリでのスレッド化コード

アセンブリ実装では「トークン・スレッド化」を採用しています。
CPU の状態はすべてレジスタに保持し、各命令の最後で次の命令へジャンプします。

; x0 | スタックポインタ
; x1 | スタックインデックス
; x4 | RAM ポインタ
; x5 | プログラムカウンタ
; x8 | オペコードテーブル

_INC:
    ldrb w9, [x0, x1]           ; スタックトップのバイトを読み取る
    add  w9, w9, #1             ; インクリメント
    strb w9, [x0, x1]           ; 書き戻し
    ldrb w9, [x4, x5]           ; 次のオペコードをロード
    add  x5, x5, #1             ; PC をインクリメント
    and  x5, x5, #0xffff        ; PC のラップ
    ldr  x10, [x8, x9, lsl #3]  ; オペコード実装のアドレスを取得
    br   x10                    ; 次の命令へジャンプ

ディスパッチを各オペコードに分散させることで、ブランチプリディクタが
命令列を学習しやすくなります。結果として ARM64 で 40–50 % の高速化、 x86‑64 では約 2 倍の速度向上が実現しました。

ただし、コード量は約 2000 行に及び、非常に安全性に欠けます。
x86 版を移植した際にはデバイス RAM の外側に書き込むアウト・オブ・ボウンズ エラーが発生し、特定のプログラム実行後にファザ―がセグフォルトしました。


Rust でのタイルコール

アセンブリと同等の動作(レジスタに状態を保持し、命令末尾でディスパッチ)を Rust だけで実現したい。そこで「タイルコールインタープリタ」というアイデアを採用します。

基本構成

  1. プログラム状態を関数引数に渡す
    システムの呼び出し規約に合わせてレジスタへマッピングされる。
  2. 各関数の末尾で次の関数を呼び出す(タイルコール)

以下は

inc
命令の例です。

const TABLE: FunctionTable = FunctionTable([
    brk,
    inc::<0b000>,
    pop::<0b000>,
    nip::<0b000>,
    swp::<0b000>,
    rot::<0b000>,
    dup::<0b000>,
    // …etc
]);

fn inc<'a, const FLAGS: u8>(
    stack_data: &'a mut [u8; 256],
    stack_index: u8,
    rstack_data: &'a mut [u8; 256],
    rstack_index: u8,
    dev: &'a mut [u8; 256],
    ram: &'a mut [u8; 65536],
    mut pc: u16,
    vdev: &mut dyn Device,
) -> (Uxn<'a>, u16) {
    let mut core = Uxn {
        stack: Stack { data: stack_data, index: stack_index },
        ret:   Stack { data: rstack_data, index: rstack_index },
        dev,
        ram,
    };
    match core.inc::<FLAGS>(pc) {
        Some(pc) => {
            let op = core.next(&mut pc);
            TABLE.0[op as usize](
                core.stack.data,
                core.stack.index,
                core.ret.data,
                core.ret.index,
                core.dev,
                core.ram,
                pc,
                vdev,
            )
        }
        None => (core, pc),
    }
}

この実装では既存の

Uxn
のオペコード関数を再利用しつつ、 呼び出しごとに構築・解体を行うため、ボイラープレートが多くなります。


タイルコールで発生するスタックオーバーフロー

実際にコンパイルして実行すると、次のようなエラーになります。

thread 'snapshots::tailcall::mandelbrot' (67889685) has overflowed its stack
fatal runtime error: stack overflow, aborting
error: test failed, to rerun pass `-p raven-varvara --test snapshots`

リリースビルドでもコンパイラはスタックフレームを最適化せず、 呼び出しごとにスタック上に積み重ねてしまうため、オペコード数が増えるほど 深いスタックが生成されます。

この問題を解決するには「タイルコール」指示子

become
(nightly 版で追加)を使用します。

match core.inc::<FLAGS>(pc) {
    Some(pc) => {
        let op = core.next(&mut pc);
        become TABLE.0[op as usize](
            core.stack.data,
            core.stack.index,
            core.ret.data,
            core.ret.index,
            core.dev,
            core.ram,
            pc,
            vdev,
        )
    }
}

become
を使うと、呼び出し側のスタックフレームをそのまま 呼び出される関数のフレームで置き換える(tail call)ことが保証され、 スタックオーバーフローは回避できます。


ボイラープレート削減マクロ

先ほど示した

inc
のような関数をすべて手書きするのは煩雑です。
そこで以下のようなマクロで自動生成します。

macro_rules! tail_fn {
    ($name:ident $(::<$flags:ident>)?) => {
        tail_fn!($name $(::<$flags>)?[][vdev: &mut dyn Device]);
    };
    ($name:ident $(::<$flags:ident>)?($($arg:ident: $ty:ty),*)) => {
        tail_fn!($name $(::<$flags>)?[$($arg: $ty),*][]);
    };
    ($name:ident $(::<$flags:ident>)?[$($arg0:ident: $ty0:ty),*][$($arg1:ident: $ty1:ty),*]) => {
        extern "rust-preserve-none" fn $name<'a, $(const $flags: u8)?>(
            stack_data: &'a mut [u8; 256],
            stack_index: u8,
            rstack_data: &'a mut [u8; 256],
            rstack_index: u8,
            dev: &'a mut [u8; 256],
            ram: &'a mut [u8; 65536],
            pc: u16,
            $($arg0: $ty0),*
            $($arg1: $ty1),*
        ) -> (UxnCore<'a>, u16) {
            let mut core = UxnCore { … };
            match core.$name::<$($flags)?>(pc, $($arg0),*) {
                Some(mut pc) => {
                    let op = core.next(&mut pc);
                    become TABLE.0[op as usize](
                        core.stack.data,
                        core.stack.index,
                        core.ret.data,
                        core.ret.index,
                        core.dev,
                        core.ram,
                        pc,
                        $($arg0),*
                        $($arg1),*
                    )
                }
                None => (core, pc),
            }
        }
    };
}

マクロを使えば、次のように宣言できます。

tail_fn!(brk);                      // 引数なし
tail_fn!(inc::<FLAGS>);             // フラグ付き
tail_fn!(dei::<FLAGS>(dev: &mut dyn Device));  // フラグ+追加引数

#![forbid(unsafe_code)]
を残したままで安全な Rust の範囲内で動作します。


コンパイラのコード生成ノート

コンパイラはほぼ完全にボイラープレートを除去し、実際にはオペコードごと に最小限のアセンブリを出力します。
例として

inc::<0>
の生成結果です。

0000000100039c50 <raven_uxn::tailcall::inc::<0>>:
    and     x8, x0, #0xff           ; スタックポインタのマスク
    ldrb    w9, [x21, x8]           ; バイト読み取り
    add     w9, w9, #1              ; インクリメント
    strb    w9, [x21, x8]           ; 書き戻し
    and     x8, x2, #0xffff         ; PC をマスク
    ldrb    w8, [x24, x8]           ; 次オペコード取得
    adrp    x9, 0x100279000         ; テーブルベースロード
    add     x9, x9, #3776           ; オフセット
    ldr     x3, [x9, x8, lsl  #3]   ; ジャンプ先取得
    add     w2, w2, #1              ; PC をインクリメント
    br      x3                      ; タイルコール

主な違いは、レジスタマスクを「使用前」に行っている点と、 テーブルベースがオフセットでロードされる点です。
x86 ではテーブルベースを引数に渡すことでパフォーマンスが向上します。


パフォーマンス評価

プロファイルVM(Rust)アセンブリタイルコール
M1 Macbook2.41 ms / 125 ops1.32 ms / 87 ops1.19 ms / 76 ops

Mandelbrot(大規模プログラム)

プラットフォームVM(Rust)アセンブリタイルコール
M1 Macbook4.70 ms / 264 ops1.84 ms / 168 ops3.23 ms / 175 ops

ARM64 ではタイルコールが手書きアセンブリを上回りますが、x86 では やや遅くなる傾向があります。


ADD2
の低速化の原因

ADD2
(スタックトップの 16 ビット値二つを加算する)については、
手書きアセンブリとタイルコール版で大幅に長さが異なります。

_ADD2:
    movzx  eax,BYTE PTR [rbx+r12*1]
    dec    r12b
    movzx  ecx,BYTE PTR [rbx+r12*1]
    dec    r12b
    shl    ecx,0x8
    or     eax,ecx
    movzx  ecx,BYTE PTR [rbx+r12*1]
    lea    r11,[r12-0x1]
    and    r11,0xff
    movzx  edx,BYTE PTR [rbx+r11*1]
    shl    edx,0x8
    or     ecx,edx
    add    ecx,eax
    mov    BYTE PTR [rbx+r12*1],cl
    shr    ecx,0x8
    mov    BYTE PTR [rbx+r11*1],cl
    movzx  eax,BYTE PTR [r15+rbp*1]
    inc    bp
    lea    rcx,[rip+0x22e5c7]
    jmp    QWORD PTR [rcx+rax*8]

タイルコール版は 121 バイトで、2 個の 64 ビットレジスタをスパイリング・リストアしています。
これは Rust の未完成機能(

become
)に起因し、LLVM バックエンドが スパイリングを回避できていないためです。


WebAssembly での実行

WASM もタイルコールをサポートしており、Raven は WASM にコンパイル可能です。
Mandelbrot の結果は次のとおりです。

エンジンVM(ms)タイルコール(ms)
Firefox 149.0264311
Chrome 146.0.7680.178244905
Wasmtime (e9e1665c5)128595

タイルコール版は Firefox で 1.2×、Chrome で 3.7×、Wasmtime では 4.6×遅く、 JIT が最適なマシン命令に落とし込めていないことが原因です。


結論

タイルコールインタープリタは PR としてマージされ、0.3.0 リリースでデプロイ済みです。
有効化すると ARM64 ではデフォルトの選択肢となり、x86‑64 では ネイティブ機能が無効な場合に二番目の選択肢になります。

今後は x86 および WASM のパフォーマンス向上についてのご提案を歓迎します。
お気軽にメールや SNS でお知らせください。

同じ日のほかのニュース

一覧に戻る →

2026/04/06 3:45

Gemma 4(iPhone向け)

## Japanese Translation: AI Edge GalleryはiPhone専用アプリで、デバイスのハードウェア上で大規模言語モデルを完全にオフラインで実行し、インターネット接続なしで高性能な生成型AIをユーザーに提供します。最新リリースではGemma 4をサポートし、以下の新機能が追加されました: - **Agent Skills** ユーザーはWikipedia事実照合、インタラクティブマップ、視覚要約カードなどのモジュールツールでLLMを拡張でき、URLまたはGitHub Discussionsからスキルをロードできます。 - **AI Chat with Thinking Mode** 対応モデルに対してステップバイステップの推論ビューを提供し、多段会話の透明性を高めます。 - **Ask Image** デバイスカメラまたはフォトギャラリーを使用したマルチモーダルオブジェクト認識と視覚パズル解決が可能です。 - **Audio Scribe** 音声録音をリアルタイムで文字起こし・翻訳し、オンデバイス言語モデルを利用します。 - **Prompt Lab** 温度やtop‑kなどのパラメータ調整が可能なプロンプトテスト用ワークスペースです。 - **Mobile Actions** ファインチューニングされたFunctionGemma 270mモデルにより、オフラインデバイス制御と自動タスクを実現します。 - **Tiny Garden** 自然言語で仮想庭園を植えたり収穫したりできる実験的ミニゲームで、同じくFunctionGemma 270mベースです。 アプリにはカスタムオープンソースモデルのダウンロード・ロードと特定ハードウェア上での性能評価が可能な柔軟なサンドボックスがあります。全ての推論はデバイス内で行われるため、インターネットは不要であり、プロンプト、画像、および機密データの100 %オンデバイスプライバシーを確保します。プロジェクトはオープンソース(GitHub: https://github.com/google-ai-edge/gallery)で、スキルや機能のコミュニティ貢献を歓迎しています。 この要約はすべての主要ポイントを網羅し、推測的表現を避けつつ主旨を明確かつ簡潔に提示しています。

2026/04/04 18:20

**LÖVE:Lua用 2D ゲームフレームワーク**

## Japanese Translation: > **概要:** > LÖVEは、Windows、macOS、Linux、Android、およびiOS上でLuaを使用して2Dゲームを構築するための無料かつオープンソースのフレームワークです。プロジェクトはGitHubに安定リリースを公開し、CI経由でnightly/unstableビルド(Ubuntu PPA `ppa:bartbes/love-unstable` および Arch AUR パッケージ `love-git` を含む)も提供しています。すべてのバイナリは、LÖVE APIを網羅する完全なテストスイート(`testing/`)とともに配布されます。各プラットフォーム用の詳細なビルド手順が用意されており、LinuxではCMake、macOS/iOSではXcode、Androidには専用リポジトリがあります。また `love‑experiments` リポジトリは実験的変更を保持しています。ドキュメントはプロジェクトのWikiにあり、コミュニティサポートはフォーラム、Discord、subreddit、およびWikiから提供されます。貢献はプルリクエストで受け付けられますが、LLMや生成AIによって生成されたコードは品質維持のため拒否されます。主な依存関係にはSDL3、OpenGL 3.3+ / OpenGL ES 3.0+、Vulkan/Metal、OpenAL、Lua/LuaJIT/LLVM‑lua、FreeType、harfbuzz、ModPlug、Vorbisfile、およびTheoraが含まれます。

2026/04/06 7:31

日本では、ロボットはあなたの仕事を奪うものではなく、誰も取りたがらない職務を担ってくれます。

## Japanese Translation: --- ## 要約 日本経済産業省は、総理大臣高市早苗氏の支援を受けて、2040年までに世界市場の **30 %** を占める国内物理‑AIセクター構築計画を発表し、約 **63億ドル** の資金を投入します。このイニシアチブは深刻な労働力不足が主因であり、日本の就業年齢人口は **59.6 %** に減少しており、今後20年間で約 **1500万人** へと縮小すると予測され、将来の労働供給に脅威を与えています。 日本の製造業者はすでに **70 %(2022年)** の産業用ロボット市場を支配しており、強固な基盤があります。政府の戦略は実験的試作から、**稼働時間、人間介入率、生産性への影響** で測定される有料顧客導入へとシフトします。 このハイブリッドエコシステムを示す主要プレーヤーは次の通りです: - **Mujin**:既存の産業用ロボットを自律的なピッキング・物流プラットフォームに変えるロボティクス制御ソフトウェア。 - **WHILL**:東京とサンフランシスコで創業されたスタートアップ。電動車両、搭載センサー、ナビゲーションシステム、クラウドベースのフリート管理を組み合わせた短距離自律輸送。 - **SoftBank**:視覚と言語モデルとリアルタイム制御を結合し、環境を認識して複雑なタスクを自律的に実行できるロボットを産業現場で可能にする。 - **Terra Drone**:運用データ+AI を活用し、自律防衛システムの信頼性向上を図る。 日本の優位性は、高精度部品(アクチュエーター、センサー、モーションコントロール)にあり、物理システムと AI の統合に対して戦略的な堀を形成しています。投資は純粋なハードウェアではなく、オーケストレーションソフトウェア、デジタルツイン、シミュレーションツール、統合プラットフォームへと徐々に向けられています。 結果として生まれるハイブリッドモデルは、大手企業がスケールと信頼性を提供し、スタートアップがソフトウェア革新を推進する構造であり、製造業・物流・自律輸送・防衛部門を再形成する可能性があります。これにより、ハードウェアの卓越性と高度な AI オーケストレーションを融合したバランスの取れたエコシステムが実現します。