Skip to content
Published on

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

Authors

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シェルスクリプト, 初期BASICJavaScript(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最適化のヒント

  1. オブジェクトのプロパティは常に同じ順序で初期化する
  2. 関数に渡す引数の型を一貫させる
  3. 配列の型を混在させない(数値配列に文字列を入れない)
  4. delete obj.prop の代わりに obj.prop = undefined を使う
  5. 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.5PyPy:     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 GCJVM(デフォルト)リージョンベース、予測可能200ms以下
ZGCJVM(最新)並行実行、ポインターカラーリング1ms以下
ShenandoahJVM(Red Hat)並行実行、Brooksポインタ10ms以下
OrinocoV8(JS)世代別、増分、並行マーキング最小化
参照カウントCPython, Swift即時解放、循環参照に注意なし(分散)
所有権システムRustGCなし、コンパイル時メモリ管理なし

ZGCの核心アイデア

従来のGC:     Stop-the-WorldMarkCompactResume
              (長い停止時間)

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

次世代バンドラー比較

ツール言語速度特徴
WebpackJS基準成熟したエコシステム、豊富なプラグイン
esbuildGo10-100倍速い並列処理、少ないプラグイン
SWCRust20-70倍速いBabel互換、Next.jsデフォルト
TurbopackRust増分ビルド最適化Vercel、大規模プロジェクト
ViteJS (esbuild/Rollup)開発時非常に速いESMベースのdevサーバー
RspackRustWebpack互換、高速Webpack API互換
# esbuildベンチマーク例
# 10,000モジュールのバンドリング:
# Webpack:   40秒
# esbuild:    0.4秒

esbuild、SWCが速い理由:

  1. システム言語(Go/Rust) — GCオーバーヘッドなし、ネイティブ速度
  2. 並列処理 — マルチコアを積極活用
  3. 最小限のAST変換 — 不要な中間ステップの排除
  4. ゼロコピーアーキテクチャ — メモリコピーの最小化

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. 参考資料

  1. Crafting Interpreters (Robert Nystrom) — インタプリタ実装のバイブル
  2. Compilers: Principles, Techniques, and Tools(ドラゴンブック) — コンパイラ理論の古典
  3. Engineering a Compiler (Cooper, Torczon) — 現代的コンパイラエンジニアリング
  4. V8 Bloghttps://v8.dev/blog
  5. Inside the V8 Engine (Marja Holtta) — V8パイプラインの詳細説明
  6. CPython Internals (Anthony Shaw) — CPythonソースコード解説書
  7. The Architecture of Open Source Applications: LLVM — LLVMアーキテクチャ説明
  8. LLVM Language Reference Manualhttps://llvm.org/docs/LangRef.html
  9. Understanding the JVM — JVM内部構造の詳細
  10. ZGC: Scalable Low-Latency Garbage Collector — Oracleドキュメント
  11. GraalVM Documentationhttps://www.graalvm.org/docs/
  12. esbuild Architecturehttps://esbuild.github.io/faq/
  13. SWC: Speedy Web Compilerhttps://swc.rs/
  14. Modern Parser Generator (Laurence Tratt) — パーサージェネレーター比較
  15. Tiered Compilation in JVMhttps://docs.oracle.com/en/java/
  16. PyPy Documentationhttps://doc.pypy.org/