---
title: 命令選択
url: https://doc.liz6.com/ja/compilers/06-code-generation/01-instruction-selection
locale: ja
area: compilers
tags:
- compilers
- code-generation
date: 2026-06-30
modified: 2026-07-19
description: IR 演算をターゲットのマシン命令にマッピングする——1対1の対応ではなく、木のカバー問題である：命令パターンで IR 式木をマッチングさせ、最小コストのカバー方案を見つける。貪欲な maximal munch から動的計画法、そしてオートマトン化された BURS へ。
---

# 命令選択

> IR 演算をターゲットのマシン命令にマッピングする——1対1の対応ではなく、木のカバー問題である：命令パターンで IR 式木をマッチングさせ、最小コストのカバー方案を見つける。貪欲な maximal munch から動的計画法、そしてオートマトン化された BURS へ。

## 概要

[中間表現](/compilers/04-intermediate-representation/01-ssa-form.md) はマシン非依存である——`add i32 %a, %b`。一方、ターゲットマシンには独自の命令セットがある：x86-64 には `addl %eax, %ebx`、ARM64 には `add w0, w1, w2` があり、それらはオペコードだけでなく、アドレス指定モード、レジスタ制約、命令の組み合わせも考慮する。**命令選択(instruction selection)** とは、IR 演算をターゲットマシンの命令列にマッピングすることである。これは1対1のマッピングではない——1つの IR 命令が複数のマシン命令に対応する場合（複雑な操作が分解される）もあれば、複数の IR 命令が1つのマシン命令に対応する場合（複合アドレス指定モードや融合命令）もある。

## 問題モデル：木タイル張り(tree tiling)

IR は**式木**(各木が計算を表す。例：`a[i] = x + 2` は木に展開できる)として整理される。ターゲットマシンの各命令も木形パターンとして記述される：

<svg viewBox="0 0 720 366" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="IR 木とターゲットマシン命令パターン木の対照例(a[i] = x + 2)">
  <rect width="720" height="366" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">命令パターン木で IR 木の形状にマッチさせる(a[i] = x + 2)</text>
  <text x="150" y="48" text-anchor="middle" font-size="12" font-weight="700" fill="#3730a3">IR 木(式木)</text>
  <text x="535" y="48" text-anchor="middle" font-size="12" font-weight="700" fill="#0f766e">ターゲットマシン命令パターン木</text>
  <line x1="360" y1="40" x2="360" y2="300" stroke="#e2e8f0" stroke-width="1"/>

  <!-- IR 木リンク -->
  <line x1="150" y1="94" x2="100" y2="130" stroke="#94a3b8" stroke-width="1.5"/>
  <line x1="150" y1="94" x2="230" y2="130" stroke="#94a3b8" stroke-width="1.5"/>
  <line x1="100" y1="162" x2="70" y2="200" stroke="#94a3b8" stroke-width="1.5"/>
  <line x1="100" y1="162" x2="140" y2="200" stroke="#94a3b8" stroke-width="1.5"/>
  <line x1="70" y1="232" x2="70" y2="270" stroke="#94a3b8" stroke-width="1.5"/>

  <!-- IR 木ノード -->
  <rect x="110" y="60" width="80" height="34" rx="6" fill="#4f46e5"/>
  <text x="150" y="82" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">store</text>
  <rect x="75" y="130" width="50" height="32" rx="5" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="100" y="151" text-anchor="middle" font-size="12" font-weight="600" fill="#3730a3">[+]</text>
  <rect x="205" y="130" width="50" height="32" rx="5" fill="#e2e8f0"/>
  <text x="230" y="151" text-anchor="middle" font-size="12" fill="#475569">x</text>
  <rect x="45" y="200" width="50" height="32" rx="5" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="70" y="221" text-anchor="middle" font-size="12" font-weight="600" fill="#3730a3">load</text>
  <rect x="120" y="200" width="40" height="32" rx="5" fill="#e2e8f0"/>
  <text x="140" y="221" text-anchor="middle" font-size="12" fill="#475569">2</text>
  <rect x="50" y="270" width="40" height="30" rx="5" fill="#e2e8f0"/>
  <text x="70" y="290" text-anchor="middle" font-size="12" fill="#475569">a</text>

  <!-- 命令パターン木リンク -->
  <line x1="535" y1="94" x2="470" y2="130" stroke="#94a3b8" stroke-width="1.5"/>
  <line x1="535" y1="94" x2="610" y2="130" stroke="#94a3b8" stroke-width="1.5"/>
  <line x1="470" y1="162" x2="440" y2="200" stroke="#94a3b8" stroke-width="1.5"/>
  <line x1="470" y1="162" x2="520" y2="200" stroke="#94a3b8" stroke-width="1.5"/>

  <!-- 命令パターン木ノード -->
  <rect x="420" y="60" width="230" height="34" rx="6" fill="#0d9488"/>
  <text x="535" y="82" text-anchor="middle" font-size="11" font-weight="700" fill="#ffffff">MOV [base + offset], src</text>
  <rect x="445" y="130" width="50" height="32" rx="5" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="470" y="151" text-anchor="middle" font-size="12" font-weight="600" fill="#115e59">[+]</text>
  <rect x="585" y="130" width="50" height="32" rx="5" fill="#e2e8f0"/>
  <text x="610" y="151" text-anchor="middle" font-size="12" fill="#475569">src</text>
  <rect x="415" y="200" width="50" height="32" rx="5" fill="#e2e8f0"/>
  <text x="440" y="221" text-anchor="middle" font-size="12" fill="#475569">base</text>
  <rect x="490" y="200" width="60" height="32" rx="5" fill="#e2e8f0"/>
  <text x="520" y="221" text-anchor="middle" font-size="12" fill="#475569">offset</text>

  <rect x="60" y="318" width="600" height="34" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="76" y="339" font-size="12.5" fill="#3730a3">命令パターン木の形状が IR 木の部分木と一致する(store↔MOV,[+]↔[+])——一致する部分木は、この命令全体でタイル張り(tile)できる。</text>
</svg>

**命令選択 = 命令パターン木で IR 木をタイル張り(tile)し、各葉がちょうど1つのカバーに属し、総コストが最小になるようにする。**

これが **木タイル張り(tree tiling)** 問題である——各命令 `i` にはパターン木 `p_i` と実行コスト `c_i` があり、総コストを最小化するカバー方案を見つける。

## 3つのアルゴリズム、コストと精度が順に上昇

### 1. マクロ展開(macro expansion): 最も単純

IR 演算 `add` → 直接 `addl %src, %dst` を出力。ターゲットマシンの特性を一切利用せず、生成コードは明らかに劣る（例：`load + add` が2命令に分かれるが、CISC には `add [addr], %reg` がある）。ただし実装が単純で、JIT のベースラインコンパイルやコードサイズが重要でない場面で使われる。

### 2. maximal munch: 貪欲法、局所最適

木根から始め、**現在の部分木をカバーできる最大パターン**(最も多くのノード)を選び、残りに再帰する：

```
munch(node):
    for each pattern from largest to smallest:
        if pattern matches at node:
            emit instruction for that pattern
            for each leaf in pattern that is a subtree root:
                munch(that leaf)
            return
```

- 貪欲法は大域的最適を保証しないが、実際には大域的最近となる（マシン命令パターンは通常「別の命令の全ノードを飲み込むほど大きく」ならないため）。
- 計算量 O(n)、1回のスキャンで済み、JIT コンパイルに適している（例：V8 の baseline compiler）。

### 3. 動的計画法: 大域的最適

ボトムアップで、各ノードについて「そのノードを根とする部分木をカバーする最小コスト」を計算する：

```
for each node n in postorder:            ← ボトムアップ
    for each pattern p that matches at n:
        cost = p.cost + sum(minCost(leaf) for leaf in p.leaves)
        minCost[n] = min(minCost[n], cost)

その後、トップダウンでバックトラックし、各ノードで使うパターンを選択する
```

- 与えられた命令パターン集合とコストモデルの下で、大域的最適カバーを保証する。
- 計算量 O(n × |patterns|)、多くの IR 木と命令セットにおいて、|patterns| は数百程度で許容範囲内。
- **BURS(Bottom-Up Rewrite System)** は DP をオートマトンに組み込む——「木マッチング→状態→置換」の変換表を事前に計算し、実行時に O(n) で処理する。ただし BURS の構築は複雑であり、LLVM の SelectionDAG は多くのターゲットで DP（厳密な BURS ではない）を使用している。

## 命令選択の追加的な複雑さ

### 複数出力命令

`divmod x, y` は1命令で商と余数の両方を生成する——2つの IR 値、1つのマシン命令。木カバーモデルは**DAG カバー**(有向非巡回グラフ、同一ノードが複数の木で共有可能)に拡張する必要があり、この場合、貪欲法も DP も複雑になる。

### 可変長命令セット

x86-64 では同一操作に複数のエンコーディングがある：`addl $1, %eax`(3バイト) vs `addl $1, (%esp)`(4バイト)。命令選択は「どの命令か」だけでなく「どのバリアントを使うか」も考慮する必要がある——アドレス指定モードの選択が命令選択の一部となる。

### 合法化(legalization)

一部の IR 命令組み合わせは、ターゲットマシンが直接サポートしない。命令選択の前または中に、**合法化**を行う必要がある：

```
IR:  reg のアドレスに 64-bit 値を store
ARM64: 64-bit store をサポート → 合法
16-bit MCU: サポートしない → 2回の 32-bit store に分解 (legalize)
```

LLVM の SelectionDAG は合法化と命令選択を交互に行う——`LegalizeDAG` パスはサポートされていない操作を同等の列に分解し、SelectionDAG が命令を選択する。

## 命令スケジューリングとレジスタ割り当てとの干渉

命令選択の後、**命令スケジューリング**(パイプラインストールを減らすために順序を再配置)と**レジスタ割り当て**(IR の仮想レジスタ→限られた物理レジスタ)が残っている。これら3つは互いに干渉する：

```
命令選択: 命令を選択 → レジスタの使用を定義
命令スケジューリング: 順序を再配置 → アクティブ範囲を変更
レジスタ割り当て: スパリングの可能性 → load/store を挿入 → 命令列を変更
```

後者は [レジスタ割り当て](/compilers/05-optimization/03-register-allocation.md) で展開される。命令選択とレジスタ割り当ての間には位相の順序問題がある：先にレジスタを割り当ててから命令を選択するか（使わないレジスタを使う命令を選ぶ可能性がある）、先に命令を選択してから割り当てるか（選択後にレジスタが足りない可能性がある）。LLVM のアプローチは、SelectionDAG フェーズで「仮想レジスタは無限」と仮定して命令選択を行い、その後レジスタ割り当て器がスパリングを処理する。

## 参考文献

- **Cooper/Torczon**: "Engineering a Compiler", Chapter 11 (Instruction Selection)
- **Cattell (1978)**: Formalization and Automatic Derivation of Code Generators (木パターンマッチングの初期の取り組み)
- **LLVM**: `lib/CodeGen/SelectionDAG/` — 産業レベルの DP ベース命令選択; `lib/CodeGen/GlobalISel/` — 次世代のグローバル命令選択

*Keywords: 命令選択, 木パターンマッチング, タイル張り, maximal munch, 動的計画法, BURS, マクロ展開, 合法化, ローリング, SelectionDAG, GlobalISel, アドレス指定モード, 複数出力命令, 命令スケジューリング干渉*
