Slide 1

Slide 1 text

JavaScriptエンジンから見る ランタイム @shqld (2024/04/25)

Slide 2

Slide 2 text

自己紹介 - あざらし - @shqld - 勉強会の参加はコロナ禍前以来なので 緊張しています - 上野でお酒を飲みながらJSの話をしている あざらしがいたらそれはおそらく自分です

Slide 3

Slide 3 text

- 近年、ランタイム(サーバサイド)が増えている - Cloudflare Workers (workerd), Bun, LLRT, WinterJS, … - これまでは採用されるエンジンV8が支配的だった(Node, Deno) - 実行パフォーマンス、セキュリティ、 API、... - 目的に照らして最適なエンジンを選択する時代に(?) - 起動時間、メモリフットプリント、ポータビリティ、カスタム性、 ... - エンジンの観点からランタイムを見てみる 👀 はじめに・まとめ

Slide 4

Slide 4 text

- 話すこと - エンジンの割と基本的な話 - エンジンとは, どういう構造なのか, etc. - 各エンジン・ランタイムの特徴 - 話さないこと - それぞれのトピックの詳細 - 処理系の最適化手法など - (発表時間が10mなので各話題 1 ページずつで軽く触れていきます) ⚠

Slide 5

Slide 5 text

- エンジン - 言語処理系 - e.g. V8, … - ランタイム - ホスト環境、実行環境 - e.g. Chrome(blink), Node.js, … - 処理系を動かす環境 - JSと外界を仲介しているとも言える?( e.g. DOM, fs, net, …) - 例えばconsoleも非同期処理も持っていないエンジンがある エンジンとランタイム

Slide 6

Slide 6 text

- 基本的にはランタイムがエンジンを埋め込む形 - e.g. V8 の C++ API をラップして Rust プログラムから呼ぶ - ランタイム兼エンジンもある( e.g. Hermes) - とはいえエンジン単体で動く CLI も大体ある(主にデバッグ・テスト用) - jsvu でまとめて簡単にインストールできる - ランタイム側でのカスタマイズ - host-definedな言語機能を実装する - e.g. イベントループ(非同期処理) - グローバルなAPIを生やす - e.g. Buffer, setTimeout, … エンジンとランタイムの関係

Slide 7

Slide 7 text

この発表で触れるエンジン / ランタイム - V8 - JavaScriptCore - SpiderMonkey - QuickJS - Hermes - Wasmtime - (Engine262) - Node.js - Deno - workerd - Bun - WinterJS - LLRT - Hermes

Slide 8

Slide 8 text

JSエンジンの外観

Slide 9

Slide 9 text

エンジンの構成 - JSを実行するための様々な機能を持つプログラム - インタープリタだったり JITコンパイラだったり - ブラウザに搭載されるエンジンは巨大 - JITコンパイラやインタプリタを複数持っている - 実行フェーズごとに使い分けている - ブラウザ非搭載の後発エンジンは比較的軽量なことが多い - 主にバイトコードインタプリタ( VM)のみの構成が多い - (そもそも新規でtest262をパスするエンジンを作るのは大変) - (V8レベルのエンジンを作るのはもっと大変、そもそも目的が違うのでモチベーションも少ない はず)

Slide 10

Slide 10 text

ブラウザ搭載エンジン - ウェブの進化に伴い、ブラウザ(レンダリングエンジン)に求められるものが増え た - 必然的にJSエンジンへの要求も多くなる - パフォーマンス - e.g. JIT機構 - セキュリティ - e.g. Sandbox機構, バグバウンティの実施

Slide 11

Slide 11 text

パフォーマンス: JIT コンパイル - tl;dr: 実行中にバイトコードの一部をネイティブコードにコンパイルして部分的に置き換えて実行 する - 大変 - アーキテクチャごとに対応が必要 - コンパイルできるコードとできないコードがある - プロファイリング - 最適化すべき処理を決めるための情報(実行回数 , サイズ, メモリ, …) - 型や引数などの情報 - 脱最適化 - 想定と異なるコードに遭遇したら最適化前のコードに戻す - 段階 (Execution Tier) がある - Baseline JIT, Optimizing JIT, etc.

Slide 12

Slide 12 text

パフォーマンス: Execution Tier - プロファイリング結果によって実行段階が上がっていく - Baseline - Optimizing - 例 - V8 - Ignition (int.) -> SparkPlug (baseline) -> Maglev (opt.) -> TurboFan (opt.2) - JavaScriptCore - LLInt (interpreter) -> Baseline (baseline) -> DFG (opt.) -> FTL (opt.2) -

Slide 13

Slide 13 text

https://cabulous.medium.com/how-v8-javascript-engine-works-5393832d80a7 *黒い枠線は筆者( @shqld)

Slide 14

Slide 14 text

一般的なエンジンの構造 (JITがある場合) - 内部処理系 (Execution Tier) が複数ある - Baseline Interpreter -> Bytecode Interpreter -> JIT Compiler - それに伴い内部表現も複数ある - AST -> Bytecode -> IR (1 … n) -> Native Code - プログラムの実行フェーズの移行に伴い処理系・表現も移行

Slide 15

Slide 15 text

- 多くのエンジンは1つのプロセスから複数の実行インスタンスを起動できる - ブラウザにおいては、例えばページ・ frame・workerごとにインスタンスが分かれている - ページごとにJS実行のためにエンジンのプロセスを立ち上げていたら遅い - 実行コンテキストを隔離する必要がある - もし仮に隔離されていなければ、悪意のあるサイトが iframe経由で親frameのコンテキストを盗 み見できたりコードを実行できたりする - * ブラウザのレンダリングプロセスの話も関わるので一概には言えないが ... - (そもそもES仕様に実行コンテキスト間の通信は規定されていない) - Realmのような同一実行コンテキスト内の話とは別 セキュリティ: Sandbox

Slide 16

Slide 16 text

JSエンジンとランタイムの紹介

Slide 17

Slide 17 text

V8 ランタイム - Chromium (blink) - Node.js - Deno - workerd - …

Slide 18

Slide 18 text

V8 - ブラウザ搭載 - 超高速 - 複数のExecution Tierを持つ - JITを持つ - (有名なので今更あまり書くことがない) - カスタムスナップショットAPI - (wasmtime + wizer のくだりで必要なので話したかったが、時間の関係上割愛) - Node.js も対応している (experimental)

Slide 19

Slide 19 text

Node.js - 非同期処理のバックエンドはlibuv - (割愛)

Slide 20

Slide 20 text

Deno - 非同期処理のバックエンドはTokio - (割愛)

Slide 21

Slide 21 text

workerd - Cloudflare Workers で使われるランタイム - * 正確にはコアの部分はworkerdと共通だが違うコードベースとのこと - JS on Edge の先駆け・代表格 - サンドボックス環境 - エッジでは、独立した大量のアプリケーションを高速に実行する必要がある - 従来の(VM、)コンテナの代わりに V8 Isolate を利用している - ServiceWorker API - 通常のサーバサイドランタイムとは異なり、 ブラウザのServiceWorker APIに近いAPIを 実装している - req/resやCacheの操作など、エッジとSWで 共通する箇所が多く理にかなっている https://blog.cloudflare.com/introducing-cloudflare-workers/

Slide 22

Slide 22 text

- 実行インスタンス - 1つのコンテキストを持つ - コンテキスト: 実行に必要なデータ - e.g. グローバル、ランタイムのカスタム API、スナップショット - 高速かつ軽量 - コンテナと比べて起動時間やメモリ使用量 が大幅に小さい - CF Workersの各アプリケーションは ブラウザでいうページ、 frame (内のJS部分)に当たる V8 Isolate https://developers.cloudflare.com/workers/reference/how-workers-works/

Slide 23

Slide 23 text

JavaScriptCore ランタイム - Safari (WebKit) - Bun (new!) - …

Slide 24

Slide 24 text

JavaScriptCore - ブラウザ搭載 - (割愛)

Slide 25

Slide 25 text

- NodeやDenoと同じく一般的なサーバサイドランタイム - JavaScriptCoreを利用している理由は不明 ... - Bunが速い理由にエンジンが JSCであることは関係ないらしい - 単にランタイムのコードであらゆる工夫・最適化をしているだけとのこと - Zig 製 (🦭ランタイム部分でのトピックは色々あるが、エンジンとの関連でいうとあまり思いつかなかった) Bun

Slide 26

Slide 26 text

ランタイム - FireFox (Gecko) - WinterJS (new!) - … SpiderMonkey

Slide 27

Slide 27 text

SpiderMonkey - C++, Rust 製 - ブラウザ搭載 - MDNによるとBrendan Eich氏によって作られた世界初の JSエンジン - https://developer.mozilla.org/en-US/docs/Web/JavaScript/JavaScript_technologies_overview#javascript_implementations - Wasmにコンパイルできる - が、JITはあまり効かない - 代わりに Portable Baseline Interpreter が色々最適化

Slide 28

Slide 28 text

WinterJS - WinterCG 準拠 - Wasm にコンパイルできる - Rust で書かれている & SpiderMonkey を利用している - 非同期バックエンドは Tokio - 開発元であるWasmerでは WinterJS をWasmにコンパイルしてから実行される 🤯

Slide 29

Slide 29 text

ランタイム - LLRT (new!) - … QuickJS

Slide 30

Slide 30 text

QuickJS - C 製 - 軽量 - JIT なし・BytecodeInterpreterのみ - 参照カウントベースのGC - 起動時間が高速・メモリ使用量が少ない - シンプルで使いやすいAPI - これからQuickJSを利用するエンジンが増えていくのでは - Wasmにコンパイルできる - QuickJS とアプリケーションコードをまとめて Wasm ランタイム上でJSを実行するプロジェクトもある (e.g. javy) - node:vmの代わりに Wasm にコンパイルした QuickJS を読み込んでアプリケーションで起動・実行する例 もある

Slide 31

Slide 31 text

* ./sample.js は空文字列

Slide 32

Slide 32 text

- Rust 製 - AWS が開発、主に Lambda での実行を想定 - 起動時間やメモリ使用量を高速化する狙い - デプロイされている既存コード (Node.js) との互換性もある - 起動が高速 - 一番の要因はJITを持たず軽量なQuickJSを採用したこと? - CPU-intensiveな処理や長時間動くアプリケーション以外では、 JIT が活きることは少なさそう - 🦭: サンドボックスといったセキュリティ面の担保はどうなってるのか気になる LLRT (Low Latency Runtime)

Slide 33

Slide 33 text

Hermes ランタイム - ReactNative

Slide 34

Slide 34 text

- ReactNative 向けに作られたエンジン(すごい) - AOT コンパイラ + バイトコードインタプリタ - 最適化済みのバイトコードを事前に出力する - ブラウザとは異なり、RNは実行する対象コードが事前に与えられている - SSAまで落とし込んで解析・最適化する( V8の Maglev レベル?) - JITなし - 事前に最適化済みバイトコードがあるためそこまでモチベーションがなさそう - JITがあるとiOSで使えない(e.g. セキュリティ、消費電力) - experimentalで開発されていたが、現在は開発がストップしている(っぽい) - Hermes Static という AOT Compiler も開発中 - コンパイル対象はバイトコードではなくネイティブコード - その他のランタイムとは異なり実行マシンも決まっている Hermes

Slide 35

Slide 35 text

Static Hermes - 絶賛開発中でまだ使われていないが、今後 Hermes に導入されるっぽい? - https://github.com/facebook/hermes/tree/static_h - エンジンではなくTS/Flowの静的コンパイラ - AssemblyScript に近い存在 - ネイティブコードを出力するので C/C++ 並のパフォーマンスとなる - コード生成は LLVM - shermes = LLVM フロントエンド - 🦭 個人的に昨年からずっと気になっているプロジェクト - 時間が足りないので詳しくは開発者の発表を見てください https://speakerdeck.com/tmikov2023/static-hermes-react-native-eu-2023-announcement

Slide 36

Slide 36 text

おまけ: Engine262 - 新しい仕様の試験実装などのために開発されているエンジン - 言語はJS - 仕様の記述に忠実に実装されていて分かりやすい - 仕様は抽象的な記述が多いので、実装コードとの対応関係が分かりづらいことが多い - Completion Record や Execution Contexts といった仕様上の概念 (?)も忠実に実装されている - Abstract Operations はそれぞれ一つの関数として実装されている

Slide 37

Slide 37 text

- WebAssemblyランタイムの一つ - WASIをサポートしている - wasmtimeを利用してJSを実行するプラットフォームが存在する - e.g. Shopify function, Fastly Compute@Edge, … - JSを実行するといっても内部ではJSエンジンが実行されている - 1. JSエンジンをプログラムとして( Wasmにコンパイルして).wasmファイルに埋め込む - 2. JSソースコードをデータとして .wasmファイルに埋め込む - 3. JSソースコードを引数に JSエンジンを実行するWasmプログラムが完成する おまけ: wasmtime

Slide 38

Slide 38 text

おまけ: wasmtime + wizer - JS on Wasm で使われている最適化 - JSエンジンのスナップショットと同じ方式

Slide 39

Slide 39 text

おまけ: WASI + JS Runtime = V8 Isolate ?

Slide 40

Slide 40 text

- 近年、ランタイム(サーバサイド)が増えている - Cloudflare Workers, Bun, LLRT, WinterJS, … - これまでは採用されるエンジンV8が支配的だった(Node, Deno) - 実行パフォーマンス、セキュリティ、 API、... - 目的に照らして最適なエンジンを選択する時代に(?) - 起動時間、メモリフットプリント、ポータビリティ、カスタム性、 ... - エンジンの観点からランタイムを見てみる 👀 はじめに・まとめ