- Published on
コンパイラとインタプリタ:コードが実行される仕組み — 開発者が知るべき言語処理のすべて
- Authors

- Name
- Youngju Kim
- @fjvbn20031
1. なぜコンパイル過程(かてい)を理解(りかい)すべきか
「コードを書けば実行(じっこう)される」という単純な事実の裏には、数十年にわたって蓄積された言語処理技術が隠れています。コンパイラとインタプリタの動作原理を理解すると、以下の実践的なメリットが得られます。
パフォーマンス直感(ちょっかん) — V8のHidden Classがなぜ重要か、Pythonがなぜ遅いか、JVMウォームアップとは何かを理解できます。
デバッグ能力(のうりょく) — 「SyntaxError: Unexpected token」がパーサー段階(だんかい)で発生するエラーだと分かれば、デバッグが速くなります。
ツール活用(かつよう) — Babel、TypeScript、ESLint、Prettierはすべてコンパイラ技術(レキサー、パーサー、AST)を使用しています。原理を知ればカスタムプラグインも作れます。
面接(めんせつ)対策 — FAANG及び大手IT企業の面接で頻出のテーマです。
2. コンパイルパイプライン全体俯瞰(ふかん)
ソースコードが実行可能なプログラムになるまでの過程を段階的に見ていきましょう。
ソースコード → [レキサー] → トークン → [パーサー] → AST → [意味解析] → IR → [最適化] → [コード生成] → 実行
| 段階 | 入力 | 出力 | 核心的役割 |
|---|---|---|---|
| レキシング | ソース文字列 | トークンストリーム | 文字列を意味単位に分離 |
| パーシング | トークンストリーム | AST | 文法構造をツリーに変換 |
| 意味解析 | AST | 型チェック済みAST | 型検査、スコープ解析 |
| IR生成 | AST | 中間表現(IR) | プラットフォーム非依存の中間コード |
| 最適化 | IR | 最適化されたIR | 定数畳み込み、インライニング等 |
| コード生成 | 最適化されたIR | マシンコード/バイトコード | ターゲットプラットフォームコード生成 |
このパイプラインはGCC、LLVM、V8、JVMなど、ほぼすべての言語処理システムで共通して使用されています。各段階を詳しく見ていきましょう。
3. レキサー(Lexer)/ トークナイザー(Tokenizer)
レキサーはソースコード文字列をトークン(Token) という意味単位に分離します。空白やコメントを除去し、キーワード・識別子・リテラル・演算子を区別します。
トークンの種類
# 入力: "let x = 42 + y;"
# 出力トークン:
# [LET, "let"] [IDENT, "x"] [ASSIGN, "="] [NUMBER, "42"]
# [PLUS, "+"] [IDENT, "y"] [SEMICOLON, ";"]
シンプルなPythonレキサー実装
import re
from enum import Enum, auto
from dataclasses import dataclass
from typing import List
class TokenType(Enum):
NUMBER = auto()
PLUS = auto()
MINUS = auto()
STAR = auto()
SLASH = auto()
LPAREN = auto()
RPAREN = auto()
IDENT = auto()
ASSIGN = auto()
LET = auto()
EOF = auto()
@dataclass
class Token:
type: TokenType
value: str
line: int
col: int
KEYWORDS = {"let": TokenType.LET}
TOKEN_PATTERNS = [
(r'\d+(\.\d+)?', TokenType.NUMBER),
(r'[a-zA-Z_]\w*', TokenType.IDENT),
(r'\+', TokenType.PLUS),
(r'-', TokenType.MINUS),
(r'\*', TokenType.STAR),
(r'/', TokenType.SLASH),
(r'\(', TokenType.LPAREN),
(r'\)', TokenType.RPAREN),
(r'=', TokenType.ASSIGN),
]
def tokenize(source: str) -> List[Token]:
tokens = []
pos = 0
line = 1
col = 1
while pos < len(source):
if source[pos] in ' \t':
pos += 1
col += 1
continue
if source[pos] == '\n':
pos += 1
line += 1
col = 1
continue
matched = False
for pattern, token_type in TOKEN_PATTERNS:
regex = re.compile(pattern)
match = regex.match(source, pos)
if match:
value = match.group()
actual_type = KEYWORDS.get(value, token_type)
tokens.append(Token(actual_type, value, line, col))
pos = match.end()
col += len(value)
matched = True
break
if not matched:
raise SyntaxError(
f"Unexpected char '{source[pos]}' at {line}:{col}"
)
tokens.append(Token(TokenType.EOF, "", line, col))
return tokens
# テスト
source = "let x = 42 + y"
for tok in tokenize(source):
print(f"{tok.type.name:8} | {tok.value}")
出力結果:
LET | let
IDENT | x
ASSIGN | =
NUMBER | 42
PLUS | +
IDENT | y
EOF |
実際の言語のレキサーは文字列リテラル、コメント、Unicode処理などはるかに複雑ですが、核心原理は同じです。
4. パーサー(Parser)とAST(抽象構文木)
パーサーはトークンストリームを受け取り、AST(Abstract Syntax Tree) を生成します。ASTはコードの論理構造をツリー形式で表現したものです。
再帰下降(さいきかこう)パーサー
最も直感的なパーシング技法で、各文法規則を一つの関数として実装します。
@dataclass
class NumberNode:
value: float
@dataclass
class BinaryOpNode:
left: any
op: str
right: any
@dataclass
class AssignNode:
name: str
value: any
class Parser:
def __init__(self, tokens: List[Token]):
self.tokens = tokens
self.pos = 0
def current(self) -> Token:
return self.tokens[self.pos]
def eat(self, token_type: TokenType) -> Token:
token = self.current()
if token.type != token_type:
raise SyntaxError(
f"Expected {token_type}, got {token.type}"
)
self.pos += 1
return token
def parse_expression(self):
left = self.parse_term()
while self.current().type in (TokenType.PLUS, TokenType.MINUS):
op = self.eat(self.current().type).value
right = self.parse_term()
left = BinaryOpNode(left, op, right)
return left
def parse_term(self):
left = self.parse_factor()
while self.current().type in (TokenType.STAR, TokenType.SLASH):
op = self.eat(self.current().type).value
right = self.parse_factor()
left = BinaryOpNode(left, op, right)
return left
def parse_factor(self):
token = self.current()
if token.type == TokenType.NUMBER:
self.eat(TokenType.NUMBER)
return NumberNode(float(token.value))
elif token.type == TokenType.LPAREN:
self.eat(TokenType.LPAREN)
node = self.parse_expression()
self.eat(TokenType.RPAREN)
return node
elif token.type == TokenType.IDENT:
self.eat(TokenType.IDENT)
return token.value
raise SyntaxError(f"Unexpected token: {token}")
AST可視化
let x = 42 + y * 2 のAST:
AssignNode
├── name: "x"
└── value: BinaryOpNode(+)
├── NumberNode(42)
└── BinaryOpNode(*)
├── "y"
└── NumberNode(2)
Prattパーシング(演算子優先順位パーサー)
Prattパーサーは演算子の優先順位をエレガントに処理します。各トークンにバインディングパワーを割り当てます。
PRECEDENCE = {
TokenType.PLUS: 10,
TokenType.MINUS: 10,
TokenType.STAR: 20,
TokenType.SLASH: 20,
}
def pratt_parse(parser, min_bp=0):
left = parser.parse_factor()
while True:
op = parser.current()
bp = PRECEDENCE.get(op.type, 0)
if bp <= min_bp:
break
parser.eat(op.type)
right = pratt_parse(parser, bp)
left = BinaryOpNode(left, op.value, right)
return left
Prattパーシングは、Rustコンパイラ、ESLint、TypeScriptパーサーなど多くのプロダクションパーサーで使用されています。
5. コンパイラ vs インタプリタ vs JIT
比較表
| 特性 | 純粋コンパイラ | 純粋インタプリタ | JITコンパイラ |
|---|---|---|---|
| 代表言語 | C, C++, Rust, Go | シェルスクリプト, 初期BASIC | JavaScript(V8), Java(HotSpot) |
| 実行前段階 | 全体コンパイル必要 | なし | バイトコードコンパイル |
| 実行速度 | 非常に速い | 遅い | 速い(ウォームアップ後) |
| 起動時間 | 遅い(コンパイル時間) | 速い | 中間 |
| メモリ使用量 | 低い | 中間 | 高い(コンパイラ含む) |
| デバッグ | 難しい | 容易 | 中間 |
| 最適化レベル | 静的解析ベース | なし | ランタイムプロファイリングベース |
ハイブリッドアプローチの現実
現代の言語実装のほとんどはハイブリッドアプローチを使用しています:
- JavaScript (V8):パース → バイトコード(Ignition) → JIT(TurboFan)
- Python (CPython):パース → バイトコード → インタプリタ(PyPyはJIT追加)
- Java (HotSpot):javacコンパイル → バイトコード → インタプリタ + C1/C2 JIT
- C# (.NET):Roslynコンパイル → IL → JIT (RyuJIT)
純粋なコンパイラや純粋なインタプリタは、現代ではまれです。
6. V8エンジン:JavaScriptの実行(じっこう)原理
GoogleのV8エンジンはChromeとNode.jsの中核です。JavaScriptを高速に実行する仕組みを見ていきましょう。
V8パイプライン
JavaScriptソース
↓
パーサー → AST
↓
Ignition(バイトコードインタプリタ)
↓ (プロファイリングデータ収集)
TurboFan(最適化JITコンパイラ)
↓
最適化されたマシンコード
↓ (Deoptimization — 仮定が破られた場合に戻る)
Ignitionに復帰
Hidden Classes(ヒドゥンクラス)
V8はJavaScriptオブジェクトにHidden Class(Shape) を付与し、プロパティアクセスを最適化します。
// 良いパターン — 同じHidden Classを共有
function Point(x, y) {
this.x = x; // Hidden Class C0 → C1
this.y = y; // Hidden Class C1 → C2
}
const p1 = new Point(1, 2); // Shape: C2
const p2 = new Point(3, 4); // Shape: C2(同じ!)
// 悪いパターン — Hidden Classの不一致
const a = {};
a.x = 1;
a.y = 2; // Shape: S1
const b = {};
b.y = 2; // 順序が違う!
b.x = 1; // Shape: S2(異なるShape!)
プロパティを常に同じ順序で追加すると、V8が同じHidden Classを再利用してパフォーマンスが向上します。
Inline Caches(インラインキャッシュ)
V8はプロパティアクセスの場所ごとにキャッシュを維持します。
function getX(obj) {
return obj.x; // Inline Cacheがここに生成
}
// Monomorphic(1つのShape)— 最速
getX(p1); // Shape C2でキャッシュ
getX(p2); // Shape C2 — キャッシュヒット!
// Polymorphic(2-4つのShape)— やや遅い
// Megamorphic(5つ以上のShape)— 遅い、汎用パス使用
V8最適化のヒント
- オブジェクトのプロパティは常に同じ順序で初期化する
- 関数に渡す引数の型を一貫させる
- 配列の型を混在させない(数値配列に文字列を入れない)
delete obj.propの代わりにobj.prop = undefinedを使う- try-catchブロックを最小化し、別の関数に分離する
7. CPython:Pythonの実行原理
CPythonバイトコード
CPythonはソースコードをバイトコードにコンパイルした後、仮想マシンで実行します。
import dis
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# バイトコード確認
dis.dis(fibonacci)
出力(簡略化):
2 0 LOAD_FAST 0 (n)
2 LOAD_CONST 1 (1)
4 COMPARE_OP 1 (<=)
6 POP_JUMP_IF_FALSE 12
3 8 LOAD_FAST 0 (n)
10 RETURN_VALUE
4 >> 12 LOAD_GLOBAL 0 (fibonacci)
14 LOAD_FAST 0 (n)
16 LOAD_CONST 2 (1)
18 BINARY_SUBTRACT
20 CALL_FUNCTION 1
...
GIL(Global Interpreter Lock)
CPythonのGILは、一度に一つのスレッドのみがPythonバイトコードを実行できるようにします。
# GILのためCPUバウンドタスクでマルチスレッドが非効率
import threading
import time
def cpu_bound(n):
count = 0
for i in range(n):
count += i
return count
# シングルスレッド
start = time.time()
cpu_bound(50_000_000)
cpu_bound(50_000_000)
print(f"Single: {time.time() - start:.2f}s")
# マルチスレッド(GILのため遅くなる可能性あり)
start = time.time()
t1 = threading.Thread(target=cpu_bound, args=(50_000_000,))
t2 = threading.Thread(target=cpu_bound, args=(50_000_000,))
t1.start(); t2.start()
t1.join(); t2.join()
print(f"Multi: {time.time() - start:.2f}s")
# 解決策: multiprocessingを使用
from multiprocessing import Pool
start = time.time()
with Pool(2) as p:
p.map(cpu_bound, [50_000_000, 50_000_000])
print(f"Multi-process: {time.time() - start:.2f}s")
PyPy:PythonのJIT代替
PyPyはRPythonで書かれたPythonインタプリタで、JITコンパイルをサポートします。
CPython: fibonacci(35) → 約4.5秒
PyPy: fibonacci(35) → 約0.3秒(約15倍速い)
PyPyが速い理由:
- トレーシングJIT — 頻繁に実行されるループをマシンコードにコンパイル
- ガード(Guard) — 型の仮定が破られるとインタプリタに復帰
- GILは依然として存在 — ただしSTM(Software Transactional Memory)実験中
8. JVM:Javaの実行原理
javacからバイトコードまで
// Hello.java
public class Hello {
public static void main(String[] args) {
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += i;
}
System.out.println(sum);
}
}
# コンパイル
javac Hello.java
# バイトコード確認
javap -c Hello.class
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1 // sum = 0
2: iconst_0
3: istore_2 // i = 0
4: iload_2
5: sipush 1000
8: if_icmpge 21 // if i >= 1000 goto 21
11: iload_1
12: iload_2
13: iadd
14: istore_1 // sum += i
15: iinc 2, 1 // i++
18: goto 4
21: ... // println
HotSpot JIT:C1とC2コンパイラ
バイトコード実行(インタプリタ)
↓ (メソッド実行回数カウント)
C1コンパイラ(高速コンパイル、基本最適化)
↓ (より多く実行されると)
C2コンパイラ(低速コンパイル、積極的最適化)
↓
最適化されたマシンコード
| 特性 | C1(Client) | C2(Server) |
|---|---|---|
| コンパイル速度 | 速い | 遅い |
| 最適化レベル | 基本 | 積極的 |
| インライニング | 制限的 | 積極的 |
| ループ最適化 | 基本 | アンロール、ベクトル化 |
| 使用時点 | 初期 | ホットメソッド |
GraalVM:多言語ランタイム
GraalVMはJavaバイトコードだけでなく、JavaScript、Python、Ruby、R、LLVMベースの言語(C/C++、Rust)も実行できる汎用仮想マシンです。
# GraalVM Native Image — AOTコンパイル
native-image -jar myapp.jar -o myapp
# 結果: 起動時間数十ミリ秒のネイティブバイナリ
# JVM: 約2秒起動 → GraalVM Native: 約20ms起動
GraalVM Native Imageの特徴:
- AOT(Ahead-of-Time)コンパイル — ランタイムJITなしでネイティブコード生成
- 高速起動 — サーバーレス、CLIツールに最適
- 低メモリ使用 — JVMランタイム不要
- 制約 — リフレクション、動的クラスローディング等に制限あり
9. LLVM:モジュラー型コンパイラフレームワーク
LLVMは現代コンパイラの標準インフラです。Clang(C/C++)、rustc(Rust)、swiftc(Swift)がLLVMをバックエンドとして使用しています。
LLVMアーキテクチャ
フロントエンド ミドルエンド バックエンド
(言語別) (共通) (ターゲット別)
┌→ x86_64
Clang ─┐ ┌→ 最適化パス ────┤→ ARM
├→ LLVM IR ┤ └→ WASM
rustc ─┤ └→ 解析パス
│
swiftc ┘
LLVM IRの例
; 関数: add(a, b) = a + b
define i32 @add(i32 %a, i32 %b) {
entry:
%result = add i32 %a, %b
ret i32 %result
}
; 関数: factorial(n)
define i64 @factorial(i64 %n) {
entry:
%cmp = icmp sle i64 %n, 1
br i1 %cmp, label %base, label %recurse
base:
ret i64 1
recurse:
%n_minus_1 = sub i64 %n, 1
%sub_result = call i64 @factorial(i64 %n_minus_1)
%result = mul i64 %n, %sub_result
ret i64 %result
}
主要な最適化パス
# LLVM最適化パスの適用
opt -O2 input.ll -o optimized.ll
# 主要な最適化パス:
# -mem2reg : メモリアクセスをレジスタに昇格
# -inline : 関数インライニング
# -gvn : Global Value Numbering(冗長な計算の除去)
# -licm : Loop Invariant Code Motion(ループ外への移動)
# -loop-unroll : ループアンロール
# -sccp : Sparse Conditional Constant Propagation
# -dce : Dead Code Elimination
定数畳み込みの例
// 最適化前
int x = 3 + 4;
int y = x * 2;
// 定数畳み込み後
int x = 7;
int y = 14;
LLVMが重要な理由:
- モジュール性 — フロントエンドとバックエンドを独立して開発可能
- 共有最適化 — すべての言語が同じ最適化パスの恩恵を受ける
- 新言語開発が容易 — LLVM IRを生成するだけで多様なプラットフォームをサポート
10. ガベージコレクション(GC)アルゴリズム
Mark-and-Sweep
最も基本的なGCアルゴリズムです。
1. Markフェーズ: ルートから到達可能なすべてのオブジェクトをマーキング
2. Sweepフェーズ: マーキングされていないオブジェクトのメモリ解放
ルート(Root):
- グローバル変数
- スタックのローカル変数
- レジスタ
[A] → [B] → [C]
↓
[D]
[E] → [F] (ルートから到達不可 → 回収対象)
Generational GC
「ほとんどのオブジェクトはすぐ死ぬ」という世代仮説(せだいかせつ) に基づいています。
Young Generation(Minor GC — 頻繁、高速)
├── Eden Space(新しいオブジェクト割り当て)
├── Survivor 0
└── Survivor 1
Old Generation(Major GC — まれ、低速)
└── 長寿命のオブジェクト
GC過程:
1. 新オブジェクト → Edenに割り当て
2. Edenが満杯 → Minor GC
3. 生存オブジェクト → Survivor空間に移動
4. N回生存 → Old Generationに昇格
5. Old Generationが満杯 → Major GC
現代のGCアルゴリズム比較
| GC | 使用先 | 特徴 | 停止時間目標 |
|---|---|---|---|
| G1 GC | JVM(デフォルト) | リージョンベース、予測可能 | 200ms以下 |
| ZGC | JVM(最新) | 並行実行、ポインターカラーリング | 1ms以下 |
| Shenandoah | JVM(Red Hat) | 並行実行、Brooksポインタ | 10ms以下 |
| Orinoco | V8(JS) | 世代別、増分、並行マーキング | 最小化 |
| 参照カウント | CPython, Swift | 即時解放、循環参照に注意 | なし(分散) |
| 所有権システム | Rust | GCなし、コンパイル時メモリ管理 | なし |
ZGCの核心アイデア
従来のGC: Stop-the-World → Mark → Compact → Resume
(長い停止時間)
ZGC: アプリケーション実行とGCが同時に実行
(ポインターカラーリング + ロードバリアで一貫性維持)
ポインターカラーリング:
64ビットポインタの上位ビットをGCメタデータに使用
[metadata bits][actual address bits]
- Marked0ビット: 第1マーキングサイクル
- Marked1ビット: 第2マーキングサイクル
- Remappedビット: オブジェクトが再配置済み
- Finalizableビット: ファイナライザ保留中
11. ビルドツールもコンパイラだ
フロントエンド開発者が毎日使うツールもコンパイラ技術を活用しています。
Babel:JavaScriptトランスパイラ
// 入力: ES2022+
const greet = (name) => `Hello, ${name}!`;
const items = [1, 2, 3];
const doubled = items.map(x => x * 2);
// Babel変換後: ES5
"use strict";
var greet = function greet(name) {
return "Hello, " + name + "!";
};
var items = [1, 2, 3];
var doubled = items.map(function (x) {
return x * 2;
});
TypeScriptコンパイラ(tsc)
TypeScriptソース
↓
パーサー → AST
↓
バインダー → シンボルテーブル
↓
タイプチェッカー → 型エラー報告
↓
エミッター → JavaScript + .d.ts + .map
次世代バンドラー比較
| ツール | 言語 | 速度 | 特徴 |
|---|---|---|---|
| Webpack | JS | 基準 | 成熟したエコシステム、豊富なプラグイン |
| esbuild | Go | 10-100倍速い | 並列処理、少ないプラグイン |
| SWC | Rust | 20-70倍速い | Babel互換、Next.jsデフォルト |
| Turbopack | Rust | 増分ビルド最適化 | Vercel、大規模プロジェクト |
| Vite | JS (esbuild/Rollup) | 開発時非常に速い | ESMベースのdevサーバー |
| Rspack | Rust | Webpack互換、高速 | Webpack API互換 |
# esbuildベンチマーク例
# 10,000モジュールのバンドリング:
# Webpack: 40秒
# esbuild: 0.4秒
esbuild、SWCが速い理由:
- システム言語(Go/Rust) — GCオーバーヘッドなし、ネイティブ速度
- 並列処理 — マルチコアを積極活用
- 最小限のAST変換 — 不要な中間ステップの排除
- ゼロコピーアーキテクチャ — メモリコピーの最小化
12. 面接(めんせつ)質問10選
Q1. コンパイラとインタプリタの違いは?
コンパイラはソースコード全体を一度にマシンコードに変換し、インタプリタはコードを一行ずつ実行します。現代の言語のほとんどはハイブリッド方式(バイトコードコンパイル + JIT)を使用しています。
Q2. ASTとは何で、どこに使われますか?
AST(Abstract Syntax Tree)はソースコードの構造をツリーで表現したデータ構造です。コンパイラ、リンター(ESLint)、フォーマッター(Prettier)、リファクタリングツール、IDEの自動補完などで使用されます。
Q3. JITコンパイルの長所と短所は?
長所:ランタイムプロファイリングによる積極的な最適化、AOTより優れたパフォーマンスの可能性。短所:ウォームアップ時間が必要、メモリ使用量増加、コンパイラの複雑さ増加。
Q4. V8のHidden Classとは?
V8が動的型のJavaScriptオブジェクトに内部的に付与する構造情報です。同じShapeのオブジェクト同士はプロパティアクセスが高速になります。
Q5. PythonのGILとは?
Global Interpreter Lockで、CPythonで一度に一つのスレッドのみがバイトコードを実行できるようにするミューテックスです。CPUバウンドタスクでマルチスレッドのメリットがなくなる原因です。
Q6. Generational GCの原理は?
「ほとんどのオブジェクトはすぐ死ぬ」という世代仮説に基づいています。Young Generationを頻繁に回収して効率を高め、Old Generationはまれに回収します。
Q7. LLVMが現代のコンパイラで重要な理由は?
モジュラー型アーキテクチャでフロントエンド(言語別)とバックエンド(プラットフォーム別)を分離します。新しい言語はIR生成さえすれば、すべてのプラットフォームと最適化を自動的に得られます。
Q8. esbuildがWebpackより速い理由は?
Goで書かれているためネイティブ速度と並列処理が可能で、GCオーバーヘッドがなく、最小限のAST変換のみを行います。
Q9. GraalVM Native Imageの長所と短所は?
長所:高速起動(約20ms)、低メモリ。短所:ビルド時間増加、リフレクション/動的機能の制限、ピーク性能はJITより低くなる可能性。
Q10. ReDoSとは?
Regular Expression Denial of Serviceの略で、非効率的な正規表現が指数的なバックトラッキングを引き起こし、CPUを過度に使用する攻撃です。正規表現エンジンもインタプリタの一種です。
13. クイズ
Q1. レキサー(Lexer)の出力は何ですか?
トークンストリーム(Token Stream)です。ソースコード文字列をキーワード、識別子、リテラル、演算子などの意味単位であるトークンに分離します。
Q2. V8でTurboFanが最適化したコードが無効化される状況は?
Deoptimizationが発生します。TurboFanが仮定した型情報がランタイムで破られる場合(例:常に整数だった変数に文字列が入る場合)、最適化されたコードを破棄してIgnitionバイトコードに復帰します。
Q3. JVMのC1とC2コンパイラの違いは?
C1(Client Compiler)は高速にコンパイルして基本最適化を適用します。C2(Server Compiler)は時間がかかりますが、インライニング、ループアンロール、ベクトル化などの積極的最適化を行います。Tiered CompilationではC1が先に適用され、ホットメソッドにC2が後から適用されます。
Q4. LLVM IRがSSA形式を使用する理由は?
SSA(Static Single Assignment)は各変数が正確に一回だけ代入される形式です。これによりデータフロー解析が簡素化され、定数伝播や不要コード除去などの最適化が容易になります。
Q5. ZGCのポインターカラーリングとは?
64ビットポインタの上位ビットをGCメタデータ(マーキング状態、再配置状態など)に使用する技法です。これにより、GCがアプリケーションと同時に実行されながらもオブジェクト参照の一貫性を維持でき、1ms未満の一時停止を達成します。
14. 参考資料
- Crafting Interpreters (Robert Nystrom) — インタプリタ実装のバイブル
- Compilers: Principles, Techniques, and Tools(ドラゴンブック) — コンパイラ理論の古典
- Engineering a Compiler (Cooper, Torczon) — 現代的コンパイラエンジニアリング
- V8 Blog — https://v8.dev/blog
- Inside the V8 Engine (Marja Holtta) — V8パイプラインの詳細説明
- CPython Internals (Anthony Shaw) — CPythonソースコード解説書
- The Architecture of Open Source Applications: LLVM — LLVMアーキテクチャ説明
- LLVM Language Reference Manual — https://llvm.org/docs/LangRef.html
- Understanding the JVM — JVM内部構造の詳細
- ZGC: Scalable Low-Latency Garbage Collector — Oracleドキュメント
- GraalVM Documentation — https://www.graalvm.org/docs/
- esbuild Architecture — https://esbuild.github.io/faq/
- SWC: Speedy Web Compiler — https://swc.rs/
- Modern Parser Generator (Laurence Tratt) — パーサージェネレーター比較
- Tiered Compilation in JVM — https://docs.oracle.com/en/java/
- PyPy Documentation — https://doc.pypy.org/