【テクニカル・上級編】GCC/Clangの診断結果をAIで自動修正!LLMとコンパイラログを連携させるパイプライン構築 – 実行環境・ランタイム・コンパイラ生産性向上バイブル

コンパイラとLLMの完全融合:GCC/Clangの診断結果を自律修復するCI/CDパイプラインの構築

コンパイルエラーの赤文字を眺め、脳内でAST(抽象構文木)を展開し、型ミスマッチや曖昧なオーバーロードをデバッグする時代は終わった。現代のインフラストラクチャとAIの融合領域において、コンパイラは単なる静的解析・機械語翻訳機ではなく、「LLM(大規模言語模型)へ正確な文脈を流し込むためのトランスデューサー」として再定義されるべきである。

本稿では、GCC/Clangが吐き出す詳細な診断結果(Diagnostics)を構造化し、LLMのAPIへ最小トークン・最大コンテキストで流し込み、即座に修正パッチを生成してテスト自動化ループまで完結させる、極限まで洗練されたローカルおよびCI/CDパイプラインの構築手法を解説する。

—

1. アーキテクチャ概観:なぜ「生のログ」をLLMに投げてはいけないのか

C/C++のコンパイルエラーをそのままLLMに投げても、期待する修正コードは返ってこない。理由は以下の3点に集約される。

1. トークンの無駄遣い(Cost & Latency Explosion): 巨大なプロジェクトでは、1回のビルドで数万行のログが出力され、コンテキストウィンドウを圧迫してAPIコストと推論レイテンシが跳ね上がる。
2. ノイズの混入: インクルード階層の深さに起因する「エラーの連鎖(Cascading Errors)」により、真の原因(Root Cause)が数千行のノイズに埋もれる。
3. ハルシネーションの誘発: 存在しないヘッダーやAPIを使った修正案を平然と出力する。

これを解決するため、我々のパイプラインでは以下の4段階のトランスレーション層を設ける。

[GCC/Clang]
↓ (JSON Diagnostics / -fdiagnostics-format=json)
[ログフィルタリング & 抽象化エンジン (Python)]
↓ (構造化されたJSON: 該当ソース、行番号、エラーコード)
[LLM API (GPT-4o等) + プロンプトテンプレート]
↓ (Unified Diff パッチ)
[自動パッチ適用 & Sandboxed Test (Docker/Make)]

—

2. 診断結果の構造化:GCC/ClangのJSON出力機能の活用

GCC 9以降およびClangには、診断結果をJSON形式で出力するフラグ `-fdiagnostics-format=json` が備わっている。これを利用することで、正規表現による脆弱なパース作業から解放される。

まずは、コンパイルエラーを確実にキャッチし、JSONとしてファイルに保存するためのMakefile / CMakeの設定、およびそれを前処理するPythonスクリプトの核心部を構築する。

ログ抽出・整形エンジン (`compiler_medic.py`)

以下のスクリプトは、Clang/GCCのJSON診断ログを受け取り、LLMが理解しやすいように「ファイル名」「該当コードの周辺スニペット」「エラーメッセージ」を抽出し、最小限の構造体へコンパクト化する。

!/usr/bin/env python3
— coding: utf-8 —

import sys
import json
import os

def parse_diagnostics(json_path, source_dir):
“””
GCC/ClangのJSON診断ログをパースし、LLMに渡すための高密度なコンテキストを構築する。
“””
if not os.path.exists(json_path):
print(f”Error: Diagnostics file {json_path} not found.”, file=sys.stderr)
sys.exit(1)

with open(json_path, ‘r’, encoding=’utf-8′) as f:
try:
data = json.load(f)
except json.JSONDecodeError as e:
print(f”Error parsing JSON: {e}”, file=sys.stderr)
sys.exit(1)

issues = []

# GCC/ClangのJSON出力はリストまたは単一のオブジェクト構造を持つ
entries = data if isinstance(data, list) else [data]

for entry in entries:
# エラーや警告の基本情報を抽出
kind = entry.get(‘kind’, ‘unknown’)
if kind not in [‘error’, ‘fatal error’]:
continue # 今回はビルドを破壊するエラーにのみフォーカスする

message = entry.get(‘message’, ”)
ranges = entry.get(‘ranges’, [])
locations = entry.get(‘locations’, [])

# 発生源ファイルの特定
filepath = “”
line = 0
column = 0

if locations:
primary_loc = locations[0]
filepath = primary_loc.get(‘file’, ”)
line = primary_loc.get(‘line’, 0)
column = primary_loc.get(‘column’, 0)

# 該当箇所のソースコードスニペットを前後のコンテキスト付きで切り出す
snippet = extract_source_snippet(filepath, line, radius=5)

issues.append({
“file”: filepath,
“line”: line,
“column”: column,
“message”: message,
“snippet”: snippet
})

return issues

def extract_source_snippet(filepath, target_line, radius=5):
“””
エラー発生行の前後数行を抽出し、LLMが正確なインデントとスコープを把握できるようにする。
“””
if not filepath or not os.path.exists(filepath):
return “Source file not accessible.”

try:
with open(filepath, ‘r’, encoding=’utf-8′, errors=’ignore’) as f:
lines = f.readlines()

start = max(0, target_line – radius – 1)
end = min(len(lines), target_line + radius)

snippet_lines = []
for idx in range(start, end):
prefix = “==> ” if (idx + 1) == target_line else ” ”
snippet_lines.append(f”{prefix}{idx + 1}: {lines[idx]}”)

return “”.join(snippet_lines)
except Exception as e:
return f”Failed to read source: {str(e)}”

if __name__ == “__main__”:
if len(sys.argv) < 2: print("Usage: python compiler_medic.py “, file=sys.stderr)
sys.exit(1)

structured_issues = parse_diagnostics(sys.argv[1], os.getcwd())
# LLMへの入力として標準出力にJSONを吐き出す
print(json.dumps(structured_issues, indent=2, ensure_ascii=False))

—

3. LLMプロンプトエンジニアリング:C/C++コンパイラエラー特化型プロンプト

LLMにコードを修正させる際、単に「直して」と投げると、モダンなC++20の機能を勝手に使ったり、プロジェクトのコーディング規約を無視したりする。これを防ぐための厳格なSystem Promptとフォーマット指定を行う。

API連携スクリプト (`llm_patcher.py`)

OpenAI API(またはローカルで稼働するLlama 3等)を叩き、`git diff` 形式のパッチを出力させるモジュール。

!/usr/bin/env python3
— coding: utf-8 —

import os
import sys
import json
import urllib.request
import urllib.error

OPENAI_API_KEY = os.environ.get(“OPENAI_API_KEY”)
API_URL = “https://api.openai.com/v1/chat/completions”

SYSTEM_PROMPT = “””
あなたは世界最高峰のC/C++コンパイラデバッグおよび低レイヤ最適化エンジニアです。
提供されたコンパイラのエラー情報とソースコードスニペットを分析し、最小限かつ正確な修正を行ってください。

【絶対遵守ルール】
1. 出力は必ず標準の `git diff` (Unified Diff形式)のみを出力してください。
2. マークダウンのコードブロック( … )で囲むこと。
3. 余計な解説や挨拶は一切含めないこと。
4. 未定義の変数、型ミスマッチ、const修飾子の脱落など、エラーの原因を完全に根絶すること。
5. プロジェクト既存のコーディングスタイル(インデント、命名規則)を厳格に維持すること。
“””

def request_llm_patch(structured_issues_json):
if not OPENAI_API_KEY:
print(“Error: OPENAI_API_KEY environment variable is not set.”, file=sys.stderr)
sys.exit(1)

user_prompt = f”””
以下のコンパイラ診断結果に基づいて、コードを修正する Unified Diff パッチを作成してください。

診断結果データ:
{json.dumps(structured_issues_json, indent=2, ensure_ascii=False)}
“””

payload = {
“model”: “gpt-4o”,
“messages”: [
{“role”: “system”, “content”: SYSTEM_PROMPT.load() if hasattr(SYSTEM_PROMPT, ‘load’) else SYSTEM_PROMPT},
{“role”: “user”, “content”: user_prompt}
],
“temperature”: 0.1, # ハルシネーションを抑制するため決定論的に近い挙動にする
“max_tokens”: 2048
}

req = urllib.request.Request(
API_URL,
data=json.dumps(payload).encode(‘utf-8’),
headers={
“Content-Type”: “application/json”,
“Authorization”: f”Bearer {OPENAI_API_KEY}”
},
method=”POST”
)

try:
with urllib.request.urlopen(req) as response:
res_data = json.loads(response.read().decode(‘utf-8’))
return res_data[‘choices’][0][‘message’][‘content’]
except urllib.error.HTTPError as e:
print(f”API Error: {e.code} – {e.read().decode(‘utf-8’)}”, file=sys.stderr)
sys.exit(1)

if __name__ == “__main__”:
if len(sys.argv) < 2: sys.exit(1) with open(sys.argv[1], 'r', encoding='utf-8') as f: issues = json.load(f) if not issues: print("No compilation errors to fix.") sys.exit(0) patch_content = request_llm_patch(issues) print(patch_content) ---

4. ハルシネーション対策:テスト自動化ループ(Self-Correction Loop)

LLMが生成したパッチが常に正しいとは限らない。文法エラーの「穴埋め」のつもりが、別の未定義参照を引き起こしたり、メモリリークを誘発する可能性もある。
したがって、「パッチ適用 ➔ 再ビルド ➔ 自動テスト実行」の閉じたループ(Self-Correction Loop)を構築し、失敗した場合はエラーログを再びLLMにフィードバックする仕組みが不可欠である。

自動修復オーケストレータ (`auto_heal.sh`)

このシェルスクリプトは、ローカル開発環境およびCI/CDパイプラインの心臓部として機能する。最大3回までLLMへの修正リトライを行う。

!/usr/bin/env bash
set -euo pipefail

設定
MAX_RETRIES=3
BUILD_COMMAND=”cmake –build build –config Release”
TEST_COMMAND=”ctest –test-dir build –output-on-failure”
DIAGNOSTICS_FILE=”build/diagnostics.json”

echo “=== [Auto-Heal Pipeline] Starting compilation self-healing loop ===”

for ((i=1; i<=MAX_RETRIES; i++)); do echo "--- Attempt $i of $MAX_RETRIES ---" # 1. コンパイルを実行し、診断結果をJSONで出力させる # Clangの場合: -fdiagnostics-format=json -fno-caret-diagnostics # GCC 9以降の場合: -fdiagnostics-format=json if CXXFLAGS="-fdiagnostics-format=json" cmake --build build --clean-first > build/build.log 2>&1; then
echo “>>> Build succeeded without errors!”

# ビルド成功後、テストスイートを実行
if eval “$TEST_COMMAND”; then
echo “=== [Auto-Heal Pipeline] All tests passed successfully! ===”
exit 0
else
echo “>>> Build succeeded, but tests failed. Feeding test failures to LLM…”
# テスト失敗時のログを診断結果に見立てて処理することも可能(今回は省略)
exit 1
fi
fi

echo “>>> Build failed. Extracting diagnostics…”

# コンパイルエラーが発生した場合、JSONログが生成されているか確認
if [ ! -f “$DIAGNOSTICS_FILE” ]; then
# JSON出力に対応していない環境向けのフォールバック処理
echo “Warning: Diagnostics JSON not found. Trying text fallback.”
python3 scripts/compiler_medic_text.py build/build.log > build/structured_issues.json
else
# Pythonスクリプトでログを構造化
python3 scripts/compiler_medic.py “$DIAGNOSTICS_FILE” > build/structured_issues.json
fi

# 2. LLMにパッチを要求
echo “>>> Requesting patch from LLM (GPT-4o)…”
python3 scripts/llm_patcher.py build/structured_issues.json > build/suggested.patch

# 3. パッチの適用前後に安全性を担保するためGitの状態を確認
if [ ! -s “build/suggested.patch” ]; then
echo “Error: LLM returned an empty patch.”
exit 1
}

echo “>>> Applying LLM suggested patch…”
# Markdownのコードブロック記法が混入している場合を除去してapplyする
sed -i ‘/^/d’ build/suggested.patch

if git apply build/suggested.patch; then
echo “>>> Patch applied successfully. Retrying build…”
else
echo “Error: Failed to apply git diff patch. Reverting changes…”
git checkout — .
exit 1
fi
done

echo “=== [Auto-Heal Pipeline] Error: Maximum retries reached. Could not automatically fix code. ===”
exit 1

—

5. Dockerコンテナ環境による完全自動構成

ローカルマシンの環境依存性を排除し、どんなホストOS上でも同一のコンパイル・自動修復環境を担保するため、マルチステージビルドを採用したDocker環境を構築する。

`Dockerfile`

ベースイメージとして最新の LLVM/Clang 環境を使用
FROM debian:bookworm-slim AS builder

必要なビルドツール、Python、Git、CMakeのインストール
RUN apt-get update && apt-get install -y –no-install-recommends \
build-essential \
clang \
cmake \
git \
python3 \
python3-pip \
ca-certificates \
&& rm -rf /var/lib/apt/lists/

WORKDIR /workspace

依存関係スクリプトのコピー
COPY requirements.txt .
RUN pip3 install –no-cache-dir -r requirements.txt –break-system-packages

ソースコードの配置
COPY . .

CMakeの初期構成(Clangをデフォルトコンパイラに指定)
RUN cmake -B build -G “Unix Makefiles” -DCMAKE_CXX_COMPILER=clang++

自動修復スクリプトを実行可能にする
RUN chmod +x scripts/auto_heal.sh

CMD [“./scripts/auto_heal.sh”]

—

6. CI/CDパイプラインとの高度な統合(GitHub Actions)

この自動修復システムをGitHub Actionsに組み込むことで、PR(プルリクエスト)作成時に発生したコンパイルエラーをLLMが自律的に修正し、修正コミットを自動プッシュする「セルフヒーリングCI」が完成する。

`.github/workflows/auto_heal_ci.yml`

name: AI Compiler Auto-Heal CI

on:
pull_request:
branches: [ main, develop ]

jobs:
auto-heal:
runs-on: ubuntu-latest

# 権限設定:自動コミットとプッシュを行うため書き込み権限を付与
permissions:
contents: write

steps:

  • name: Checkout Repository

uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0

  • name: Set up Python Environment

uses: actions/setup-python@v5
with:
python-version: ‘3.11’

  • name: Install System Dependencies

run: |
sudo apt-get update
sudo apt-get install -y clang cmake build-essential

  • name: Install Python Dependencies

run: |
pip install –no-cache-dir requests

  • name: Configure CMake

run: |
cmake -B build -G “Unix Makefiles” -DCMAKE_CXX_COMPILER=clang++

  • name: Run Auto-Heal Pipeline

env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
./scripts/auto_heal.sh

  • name: Check for Changes and Push Fixes

run: |
git config –global user.name “AI Auto-Heal Bot”
git config –global user.email “ai-bot@users.noreply.github.com”

if [[ -n $(git status –porcelain) ]]; then
git add .
git commit -m “chore(ai): automatically fix compilation errors via LLM pipeline”
git push
echo “Successfully pushed automated fixes to the pull request.”
else
echo “No changes were made by the auto-heal pipeline.”
fi

—

7. 現場のアーキテクトが直伝する「運用上の極意とセキュリティ」

このパイプラインをプロダクション環境に導入するにあたり、現場で必ず直面する課題に対する解決策を提示する。

1. 機密情報の漏洩防止(Data Privacy):
商用API(OpenAI等)を使用する場合、社内のプロプライエタリなソースコードスニペットが外部に送信される。企業のセキュリティポリシーがこれを許さない場合、オンプレミス環境に Llama 3 (70B) や CodeLlama、DeepSeek-Coder などのオープンウェイトモデルを Ollama や vLLM でホスティングし、APIのエンドポイントをローカルに向ければ完全なエアギャップ環境で同様のパイプラインが稼働する。
2. 無限ループの防止:
LLMが間違った修正を延々と繰り返し、CIのランタイム制限やAPIクォータを食い潰すリスクがある。前述のシェルスクリプトにある通り、`MAX_RETRIES=3` のハードリミットを必ず設け、失敗した場合は人間(レビュアー)に通知するエスカレーションパスを維持すること。
3. インクリメンタルビルドの罠:
自動修復時に `cmake –build` のキャッシュが残っていると、誤った依存関係のまま処理が進むことがある。リトライループの最初は必ず `–clean-first` または `cmake –build build –target clean` を挟むのが、低レイヤビルドの鉄則である。

コンパイラの診断結果をAIの燃料へと昇華させよ。人間が赤字のコンパイルエラーと格闘する時間は、もはやエンジニアリングの無駄遣いである。このパイプラインを導入した瞬間から、あなたのチームは「コードを書くこと」から「アーキテクチャを定義すること」へ、本当の意味で集中できるようになる。

タイトルとURLをコピーしました