- MicroQuickJS(MQuickJS) は組み込みシステム向けに設計された超軽量 JavaScriptエンジン で、約10kBのRAMと100kBのROMだけで動作可能
- QuickJSに近い速度 を維持しつつメモリ使用量を減らすため、トレーシングガベージコレクタ と UTF-8文字列保存方式 を採用
- 対応言語は ES5に近い制限付きJavaScriptサブセット で、エラーになりやすい構文を禁止する strict mode のみを許可
- REPLツール
mqjs により、スクリプト実行、バイトコード保存、メモリ制限設定が可能で、生成されたバイトコードはROMから直接実行可能
- エンジン全体と標準ライブラリがROMに常駐し、高速な初期化と低メモリ消費 を実現、組み込み環境での JavaScript実行効率 を高める
紹介
- MicroQuickJS(MQuickJS) は組み込みシステムを対象とした JavaScriptエンジン で、10kBのRAMと100kBのROM(ARM Thumb-2コードを含む)で動作
- ES5に近いサブセット のみをサポートし、非効率またはエラーになりやすい構文を禁止 する strict mode でのみ動作
- QuickJSとコードの一部を共有するが、内部構造は メモリ節約のため完全に異なる設計 になっている
- トレーシングガベージコレクタ、CPUスタック不使用、UTF-8文字列保存 方式を使用
REPL
- REPLコマンドは
mqjs で、スクリプト実行、評価、インタラクティブモード、メモリ制限設定、バイトコード保存などをサポート
- 例:
./mqjs --memory-limit 10k tests/mandelbrot.js
-o オプションで コンパイル済みバイトコード をファイルに保存可能
- 保存したバイトコードは
./mqjs mandelbrot.bin で実行可能
- バイトコードはCPUの エンディアン および ワード長(32/64ビット) によって異なり、
-m32 オプションで32ビット向けバイトコードを生成可能
--no-column オプションで デバッグ情報の列番号を削除 可能
strict mode
- strict mode のみ許可され、
with キーワードは使用不可、グローバル変数は必ず var で宣言する必要がある
- 配列のホール(hole) は許可されない
- 例:
a[10] = 2 はTypeErrorを発生させる
- ホールのある配列が必要な場合は通常のオブジェクトを使用
- グローバルevalのみ対応 し、ローカル変数へのアクセスは不可
- 値のボクシング(value boxing) は未対応(
new Number(1) など)
JavaScriptサブセット
- strict mode ベースで、ES5互換性 を重視
- Arrayオブジェクト にはホールがなく、範囲外インデックスへのアクセスはエラー
for in はオブジェクトの 自身のプロパティのみを列挙 し、for of は配列のみサポート
- グローバルオブジェクト は存在するがgetter/setterは不可、直接作成したプロパティはグローバル変数として公開されない
- 正規表現(Regexp) はASCIIのみ大文字小文字を区別せず処理し、
/./ はUTF-16ではなくユニコードコードポイント単位でマッチ
- 文字列関数 はASCIIのみ処理(
toLowerCase, toUpperCase)
- Date は
Date.now() のみサポート
- 追加対応機能:
for of, Typed arrays, \u{hex} 文字列リテラル
- Math関数:
imul, clz32, fround, trunc, log2, log10
- 指数演算子、正規表現フラグ(s, y, u)、文字列関数(
replaceAll, trimStart, trimEnd)、globalThis
C API
- Cライブラリ依存を最小化 し、
malloc, free, printf は未使用
- メモリバッファを直接提供 する必要があり、エンジンはそのバッファ内でのみメモリを割り当てる
- 例:
ctx = JS_NewContext(mem_buf, sizeof(mem_buf), &js_stdlib)
- ガベージコレクション方式 により
JS_FreeValue() の呼び出しは不要
- オブジェクトアドレスは割り当てのたびに移動しうるため、
JSValue ポインタの使用を推奨
JS_PushGCRef() / JS_PopGCRef() で安全に参照管理
- 標準ライブラリ はROMに保存可能なC構造体としてコンパイルされ、高速な初期化と低RAM使用量 を実現
- バイトコード実行 はROMから可能で、
JS_RelocateBytecode() で再配置した後、JS_LoadBytecode() と JS_Run() で実行
- 数学ライブラリ(libm.c) と 浮動小数点エミュレータ を内蔵
内部構造とQuickJSとの比較
- ガベージコレクション: 参照カウントの代わりに トレーシング・コンパクションGC を使用
- 値表現: CPUのワードサイズ(32/64ビット)に合わせて設計
- 31ビット整数、ユニコードコードポイント、浮動小数点、メモリブロックポインタを格納可能
- 文字列はUTF-8で保存 され、QuickJSの8/16ビット配列方式より効率的
- C関数 は単一値として保存可能で、プロパティ追加は不可
- 標準ライブラリ はROMに常駐し、RAMオブジェクトを最小化することで 高速なエンジン初期化 が可能
- バイトコード はスタックベースで、間接参照テーブル により読み取り専用として扱う
- コンパイラ はQuickJSに近いが、非再帰パーサ を使用してCスタック使用量を抑制
テストとベンチマーク
- 基本テスト:
make test
- QuickJSマイクロベンチマーク:
make microbench
- Octaneベンチマーク(strict mode向け修正版)は別途ダウンロード可能
ライセンス
- MITライセンス で配布
- ソースコードの著作権は Fabrice Bellard と Charlie Gordon が保有
2件のコメント
Fabrice Bellard についての紹介は、以前私がコメントに書いたものを参考にしてください。この方は本当に一貫していて、驚くべき怪物です..
https://news.hada.io/comment?id=51
Hacker News のコメント
2010年にこれが存在していたら、Redis のスクリプト言語は Lua ではなく JavaScript になっていた気がする
Lua が選ばれたのは言語的な理由ではなく、実装上の制約(小さい、速い、ANSI-C ベース)によるものだった
Lua のいくつかのアイデアは良いが、個人的には Algol 系の文法 から外れた点は不要に感じていた
SmallTalk や FORTH のように、新しい抽象概念を学ぶ代償として生じる混乱には価値があるが、Lua の変化にはそれだけの理由がないと思う
Lua は tail call optimization(TCO) をサポートする唯一の軽量言語で、そのおかげでループなしに再帰だけでプログラムを書ける
JavaScript にはその最適化がないため、同じやり方はできない
Lua は コンパイラ実装 にも特に向いている。再帰的な構造が多いからだ
Redis のスクリプティングには JS の方が合っているかもしれないが、Lua をけなすのは残念だ
ブラジルでは C より Pascal や Ada の方が広く使われていたため、その影響を受けたのだろう
Ruby や Perl も同じくらいの時期に出たが、もっとはるかに急進的な文法の変化を試みていた
パーサーとレキサーを分離しておきながら、
{}の代わりにthen/endのようなトークンを差し替える試みはほとんど見たことがない関連議論: HN スレッド, Reddit の議論
このエンジンは、昔 JSC を触っていたときに自分が望んでいたやり方で JS を制限 している
Web では互換性のためにこうした制約は不可能だが、組み込み環境ではむしろこうした制約が うれしい設計 になり得る
ブラウザで MicroQuickJS をそのまま実行できる プレイグラウンド を作った
MicroQuickJS WebAssembly 版
参考までに元の QuickJS 版 もある
QuickJS は 2.28MB、MicroQuickJS は 303KB でずっと軽い
emcc -O3オプションや--closure 1を追加すればさらに削減できそうQuickJS はすでに最適化済みで、MicroQuickJS の方だけ改善余地がある
Jeff Atwood の有名な言葉どおり、「JavaScript で書けるあらゆるアプリは、最終的に JavaScript で書かれる」
いまやその言葉は組み込みシステムにも当てはまるようだ
Jeff Atwood のウィキ
JSLinux リンク
コミット履歴なしでアップロードされたのが惜しい
このレベルの開発者がどれだけの速さでプロジェクトを仕上げるのか見てみたかった
どうせ QuickJS ベースなので、比較自体に大きな意味はなさそうだが
これが yt-dlp の YouTube JS チャレンジ を解く最も軽量な方法になり得るのか気になる
yt-dlp EJS ドキュメント 参照
QuickJS はすでにサポートされている
YouTube の JS パズルはあまりに複雑で、Python 製の JS エミュレータですら断念したほどだ
組み込みシステムには詳しくないが、こうしたエンジンで ESP32 や Arduino を JavaScript でプログラム できるようになるのか気になる
MicroPython のようなものだ
MicroQuickJS は ES5 の一部しか実装しておらず、環境バインディングも提供しない
JS コードを Lua VM のバイトコードに変換して実行していて、かなり賢いアプローチだった
最近、あの古い Node 0.8 CLI を Rust で書き直してみたが、結局ハードウェアは引き出しに戻った
タイミングは本当に重要だ。昨夜投稿したときはまったく反応がなかった
米国の朝の時間帯に再投稿したり、定期的に再掲したりする戦略がある
Fabrice Bellard は現存する中でもっとも生産的で多才なプログラマーの一人だ
代表作: FFmpeg, QEMU, JSLinux, TCC, QuickJS
伝説的な人物だ
最小限の依存関係とツールで完全なプログラムを作るアプローチが印象的だ
本当に人間なら眠らないといけないはずだから
ts_server, TextSynth
ユーザーがパラメータを設定すると、プログラムが完結的に実行される構造を好んでいるように見える
IOCCC 受賞者一覧
「10kB の RAM でも JS をコンパイルして実行できる」 という点が印象的だ
今のように RAM が高価になっている時期 にちょうど合っている
これを Chromium や Electron に組み込めるのか気になる