パーサーのアーキテクチャ
Oxc は独自の AST とパーサーを保持しており、これは現在、Rust で書かれた最も高速かつ準拠度の高い JavaScript および TypeScript(JSX および TSX を含む)パーサーです。
パーサーは、多くの場合、JavaScript ツールチェインにおける重要な性能ボトルネックとなるため、わずかな改善でも、下流のツールにまで連鎖的な影響を与えることがあります。このパーサーを開発することで、よく研究された性能向上技術を探索し実装する機会があります。
AST の設計思想
多くの既存の JavaScript ツールが estree という AST の仕様に依存している一方で、その大きな欠点として、曖昧なノードが多く存在することが挙げられます。このような曖昧さは、estree を使用する開発時に混乱を招きやすいです。
Oxc の AST は estree のそれとは異なり、曖昧なノードを削除し、明確な型を導入しています。たとえば、汎用的な estree Identifier の代わりに、BindingIdentifier、IdentifierReference、IdentifierName といった特定の型を提供しています。
この明確な区別により、ECMAScript の仕様に近い形で開発体験が大幅に向上します。
AST ノードの種類
rust
// 汎用的な Identifier ではなく
pub struct BindingIdentifier<'a> {
pub span: Span,
pub name: Atom<'a>,
}
pub struct IdentifierReference<'a> {
pub span: Span,
pub name: Atom<'a>,
pub reference_id: Cell<Option<ReferenceId>>,
}
pub struct IdentifierName<'a> {
pub span: Span,
pub name: Atom<'a>,
}意味の明確化
このアプローチにより、意味の明確性が得られます:
BindingIdentifier: 変数宣言 (let x = 1)IdentifierReference: 変数の使用 (console.log(x))IdentifierName: プロパティ名 (obj.property)
性能アーキテクチャ
なぜこれほど高速なのか
- メモリ アレーン: AST は memory arena に割り当てられ、高速な割り当て・解放が可能
- 文字列最適化: 短い文字列は CompactString によってインライン化される
- 最小限のヒープ使用: 上記2つのもの以外には、他のヒープ割り当てを行わない
- 責務の分離: スコープバインディング、シンボル解決、一部の構文エラーはセマンティックアナライザーに委譲される
メモリ管理の詳細
アレーン割り当て
rust
use oxc_allocator::Allocator;
// すべての AST ノードはこのアレーンに割り当てられる
let allocator = Allocator::default();
let ast_node = allocator.alloc(Expression::NumericLiteral(
allocator.alloc(NumericLiteral { value: 42.0, span: SPAN })
));メリット:
- O(1) の割り当て: 単純なポインタのインクリメント
- O(1) の解放: 一度にアレーン全体をドロップ
- キャッシュフレンドリー: 線形メモリレイアウト
- フラグメンテーションなし: 継続的なメモリ利用
CompactString による文字列内蔵
rust
// 24バイト以下の文字列はインラインで保存(ヒープ割り当てなし)
let short_name = CompactString::from("variableName"); // スタックに割り当て
let long_name = CompactString::from("a_very_long_variable_name_that_exceeds_limit"); // ヒープに割り当てこれにより、大多数の JavaScript インデントや文字列リテラルに対するメモリ割り当てが削減されます。
パーサーのアーキテクチャ
二段階設計
Oxc パーサーは二段階のアプローチに従います:
- 解析段階: 最小限のセマンティック解析で AST 構造を構築
- セマンティック段階: スコープ解析、シンボル解決、高度なエラーチェックを実行
rust
// 段階1:AST への解析
let parser_result = Parser::new(&allocator, &source_text, source_type).parse();
// 段階2:セマンティック解析
let semantic_result = SemanticBuilder::new()
// パーサーが行わない追加の構文チェックを有効化
.with_check_syntax_error(true)
.build(&parser_result.program);パーサーのコンポーネント
リッパー(トークン生成)
- トークン生成: ソーステキストを構造化されたトークンに変換
- SIMD 最適化: 発見的な空白スキップに SIMD 指令を使用
- 文脈対応: 正規表現と除算演算子の曖昧さを解消
再帰下降パーサー
- 手書き: 最大限のパフォーマンスを得るためのカスタム実装
- エラー回復: 詳細なメッセージを持つ高度なエラー処理
- 文法準拠: ECMAScript 規格を正確に遵守
AST ビルダー
- 型安全: Rust の型システムを利用して正しさを確保
- メモリ効率: 直接アレーンに割り当て
- ビルダーパターン: ノード構築の便利なメソッドを提供
準拠戦略
テスト セットのカバレッジ
- Test262: ECMAScript 準拠テストで 100% のパス率
- Babel: Babel パーサーのテストで 99.62% の互換性
- TypeScript: TypeScript コンパイラのテストで 99.86% の互換性
エラー処理の哲学
rust
// ソース位置付きの意味のあるエラーメッセージ
pub struct OxcDiagnostic {
pub message: String,
pub span: Span,
pub severity: Severity,
pub help: Option<String>,
}パーサーが提供するもの:
- 正確なエラー位置: 精確なソース位置
- 回復戦略: エラー後も解析を継続
- 役立つ提案: 実行可能なエラーメッセージ
高度な機能
TypeScript 対応
- 型の除去: TypeScript 特有の構文を削除
- デコレータ解析: 検証中のデコレータを処理
- ネームスペース対応: モジュールおよびネームスペースの完全な解析
- JSX インテグレーション: TypeScript + JSX (TSX) 対応
研究領域
- SIMD テキスト処理: ベクトル化された文字列操作
- キャッシュ最適化: メモリアクセスパターンの最小化
- 分岐予測: ホットなパースパスの最適化
- ゼロコピー解析: 不要な文字列コピーの排除
