C言語専用 x86 → RISC-V 移植統合テスティングフレームワーク「Akari」
🚀 C言語プロジェクトに組み込みやすい! x86 → RISC-V の移植を自動検証! 🚀
1. 目的と概要
✅ x86 の SSE / AVX / MMX / FMA / VNNI のイントリンシックを RISC-V に移植する際の品質を保証
✅ 1対1で対応できる命令は最適化し、対応できない命令はエミュレーションコードで処理
✅ C言語プロジェクトに簡単に統合できるように API 提供 (akari.h
)
✅ アセンブリ解析・数学的なベンチマーク比較・Intel公式リファレンス + ChatGPT API によるイントリンシック変換・CI/CD 自動テストを統合
なぜ x86 → RISC-V 移植が難しいのか?
💡 移植で起こる主な問題点
- x86 の AVX / SSE / MMX などのベクトル命令は RISC-V にそのまま移植できない
RISC-V には RISC-V Vector Extension (RVV) があり、AVX に似た機能を持つが、命令セットが異なるため単純な変換は不可。
- x86 の一部命令(_mm_movemask_epi8 など)は RISC-V に直接対応する命令がない
これらは エミュレーションコード を使用して RISC-V 上で動作させる必要がある。
- 最適化の方法が異なるため、x86 の移植後に性能が大きく異なる可能性がある
移植が正しく行われたかどうかの確認が難しい。
- 移植後のコードが「正しく動作しているか」「最適化されているか」を検証する必要がある
そのために「アセンブリ解析」「ベンチマーク比較」「エミュレーションコード」「CI/CD の自動チェック」が必要。
2. イントリンシックの変換パターン
💡 x86 → RISC-V のイントリンシック変換には以下の3つのパターンがある
変換タイプ | 対応状況 | 例 | 処理方法 |
---|---|---|---|
1対1対応 | ✅ 完全対応 | _mm_add_ps → vadd.vv |
そのまま変換 |
非対応 | ❌ 変換不可 | _mm_movemask_epi8 |
エミュレーション |
部分対応 | ⚠️ 一部変換可 | _mm_shuffle_ps |
複数命令に展開 |
✅ RISC-V に 1対1 で対応する命令があれば、それを使用
✅ 対応できない場合はエミュレーションコードを適用
✅ 部分対応のケースでは、複数の RISC-V 命令で再構築
3. イントリンシックのエミュレーションコード集
1. _mm_shuffle_ps
(SSE)
説明: 4要素の128-bit ベクトルの順序を変更
static inline void simd_shuffle(float* dst, float* src, int imm) {
dst[0] = src[(imm >> 0) & 0x3];
dst[1] = src[(imm >> 2) & 0x3];
dst[2] = src[(imm >> 4) & 0x3];
dst[3] = src[(imm >> 6) & 0x3];
}
RISC-V の RVV には _mm_shuffle_ps
に相当する命令がないため手動でエミュレーション
2. _mm_hadd_ps
(SSE3)
説明: 4要素の128-bit ベクトルの隣接要素を加算
static inline void simd_hadd(float* dst, float* src) {
dst[0] = src[0] + src[1];
dst[1] = src[2] + src[3];
dst[2] = src[4] + src[5];
dst[3] = src[6] + src[7];
}
RVV には隣接要素を加算する命令がないため手動で対応
3. _mm_movemask_epi8
(SSE2)
説明: 128-bit の 16要素の符号ビットを抽出し整数にまとめる
static inline uint16_t simd_movemask_epi8(const int8_t* src) {
uint16_t mask = 0;
for (int i = 0; i < 16; i++) {
if (src[i] & 0x80) {
mask |= (1 << i);
}
}
return mask;
}
RISC-V には PMOVMSKB
に相当する命令がないため手動でビットマスクを生成
4. _mm256_blendv_ps
(AVX)
説明: 256-bit ベクトルの要素ごとにマスクを適用
static inline void simd_blendv(float* dst, float* a, float* b, float* mask) {
for (int i = 0; i < 8; i++) {
dst[i] = mask[i] < 0 ? b[i] : a[i];
}
}
RVV には _mm256_blendv_ps
に相当する命令がないため負の値かどうかで手動分岐
5. _mm_min_epu8
(SSE2)
説明: 8ビット整数の最小値を計算
static inline void simd_min_epu8(uint8_t* dst, uint8_t* a, uint8_t* b) {
for (int i = 0; i < 16; i++) {
dst[i] = (a[i] < b[i]) ? a[i] : b[i];
}
}
RVV には _mm_min_epu8
に相当する命令がないためループで処理
6. _mm_cvtsi128_si32
(SSE2)
説明: 128-bit ベクトルの最初の整数要素をスカラーに変換
static inline int simd_cvtsi128_si32(const int* src) {
return src[0];
}
RISC-V には _mm_cvtsi128_si32
に相当する命令がないため手動で取得
7. _mm256_permutevar_ps
(AVX2)
説明: 256-bit ベクトルの要素の並び替え
static inline void simd_permutevar(float* dst, float* src, int* indices) {
for (int i = 0; i < 8; i++) {
dst[i] = src[indices[i] & 0x7];
}
}
RVV には _mm256_permutevar_ps
に相当する命令がないため手動でインデックス操作
8. _mm_abs_epi16
(SSSE3)
説明: 16ビット整数の絶対値を計算
static inline void simd_abs_epi16(int16_t* dst, int16_t* src) {
for (int i = 0; i < 8; i++) {
dst[i] = (src[i] < 0) ? -src[i] : src[i];
}
}
RVV には _mm_abs_epi16
に相当する命令がないため手動で処理
9. _mm256_max_epu32
(AVX2)
説明: 32ビット整数の最大値を計算
static inline void simd_max_epu32(uint32_t* dst, uint32_t* a, uint32_t* b) {
for (int i = 0; i < 8; i++) {
dst[i] = (a[i] > b[i]) ? a[i] : b[i];
}
}
RVV には _mm256_max_epu32
に相当する命令がないためループで処理
4. アセンブリ解析(最適化チェック)
💡 objdump
の出力を JSON ベースで管理し、最適化が適用されているかをチェックする。
import json
import re
import sys
def load_patterns(json_file):
with open(json_file, 'r') as f:
return json.load(f)
def check_assembly(file_path, patterns, arch):
with open(file_path, 'r') as f:
asm = f.read()
matched = False
for label, pattern in patterns[arch].items():
matches = re.findall(r"\b" + pattern + r"\b", asm)
if matches:
print(f"✅ {label}: {pattern} found {len(matches)} times in {file_path} ({arch})")
matched = True
else:
print(f"❌ {label}: {pattern} NOT found in {file_path} ({arch})")
return matched
if __name__ == "__main__":
patterns = load_patterns("akari_asm_patterns.json")
arch = sys.argv[2]
matched = check_assembly(sys.argv[1], patterns, arch)
if not matched:
sys.exit(1)
✅ JSON に定義された最適化パターンが適用されていなければ NG 判定
5. 数学的に公平なベンチマーク解析
💡 移植が成功したかどうかを「理論値 vs. 実測値」で判定
import json
import numpy as np
import sys
def analyze_benchmarks(x86_json, riscv_json, epsilon=0.1):
x86_results = load_json(x86_json)
riscv_results = load_json(riscv_json)
x86_times = np.array([run["real_time"] for run in x86_results["benchmarks"]])
riscv_times = np.array([run["real_time"] for run in riscv_results["benchmarks"]])
R_real = np.mean(riscv_times) / np.mean(x86_times)
R_theory = expected_ratio_based_on_instruction_count(x86_results, riscv_results)
if abs(R_real - R_theory) < epsilon:
print(f"✅ OK")
else:
print(f"❌ NG")
sys.exit(1)
✅ 理論値 (R_theory
) と実測値 (R_real
) の誤差が小さければ OK!
✅ NG なら最適化ミス or 移植ミスの可能性があるので CI/CD でアラート!
6. Intel公式リファレンス + ChatGPT API によるイントリンシック変換
💡 Intel の公式リファレンスをスクレイピングし、ChatGPT API を使って RISC-V への変換 or エミュレーションコードを自動生成
from selenium import webdriver
from selenium.webdriver.common.by import By
import json
import time
URL = "https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html"
driver = webdriver.Chrome()
driver.get(URL)
time.sleep(5)
intrinsics = []
elements = driver.find_elements(By.CSS_SELECTOR, ".intrinsic-name")
for elem in elements:
name = elem.text.strip()
if name:
intrinsics.append(name)
driver.quit()
with open("intrinsics_list.json", "w") as f:
json.dump(intrinsics, f, indent=2)
✅ Intel の公式リファレンスを Web スクレイピングし、すべてのイントリンシック命令をリスト化
7. CI/CD での自動チェック
- name: Analyze Benchmarks
run: python3 akari_benchmark.py benchmark_x86.json benchmark_riscv.json
✅ アセンブリの最適化 & ベンチマークの白黒評価を CI/CD で自動化!
8. まとめ
✅ x86 → RISC-V の移植を自動でチェックする Akari を作成!
✅ C言語プロジェクト専用! #include "akari.h"
を追加するだけで組み込み可能!
✅ 1対1変換可能なイントリンシックは最適化、対応不可のものはエミュレーション適用!
✅ アセンブリ解析・数学的なベンチマーク比較を統合!
✅ Intel 公式リファレンスをスクレイピングし、ChatGPT でイントリンシック変換!
✅ CI/CD で統合し、自動テスト可能!
🚀 「Akari」は C 言語プロジェクトの x86 → RISC-V 移植を品質保証し、自動検証を可能にするフレームワーク! 🚀