🛠️

LLVMを改造した話し

7 min read

LLVMをSPARCでビルドして使いたい

最近は、SPARCマシンにお目にかかることはなくなりました。
そんな中、「アルファベットJの次」のマシンがSPARCでして、LLVMをビルドしてインストールしたいという話しが舞い込んできました(2017年12月)。

LLVMはSPARCに対応しているか?

以下の2017年にメーリングリストでオラクルコンパイラチームのFedor Sergeev氏は、バージョン5までにはSPARCに対応したいと意気込みが見えます。

https://lists.llvm.org/pipermail/llvm-dev/2017-June/114437.html

私が、実際に修正したのはLLVMのバージョン3.9.1でした。
このバージョンもSPARCには対応できていません。

対応していないならコードを書くしかない

じゃあ サポートするためにはコードを書いてビルドするしかない。
要件が月面着陸ミッションくらいの困難さですね。

調査開始

まず、なにが不足しているのか?
LLVMのソースをダウンロードして調査(はい地味です、そこから始めるかぁ、やっぱりそこからだよね)。
LLVMの最新バージョン 12.0.1(2021.09.23時点)

https://github.com/llvm/llvm-project
SPARCなどのキーワードでGrepして関係しそうなところを探す。
観点を変えてIntelとかはサポートされているのでIntelの処理はあるけどSPARCの処理はないところを探す。

ここにSPARCの定義はある
※バージョン3.9.1 と バージョン12.0.1 はディレクトリ構成が変わってました バージョン12.0.1 で書いています
llvm-project-main/llvm/include/llvm/BinaryFormat/ELFRelocs/Sparc.def

#ifndef ELF_RELOC
#error "ELF_RELOC must be defined"
#endif

ELF_RELOC(R_SPARC_NONE,         0)
ELF_RELOC(R_SPARC_8,            1)
ELF_RELOC(R_SPARC_16,           2)
ELF_RELOC(R_SPARC_32,           3)
ELF_RELOC(R_SPARC_DISP8,        4)
ELF_RELOC(R_SPARC_DISP16,       5)
ELF_RELOC(R_SPARC_DISP32,       6)
ELF_RELOC(R_SPARC_WDISP30,      7)
ELF_RELOC(R_SPARC_WDISP22,      8)
ELF_RELOC(R_SPARC_HI22,         9)
ELF_RELOC(R_SPARC_22,           10)
ELF_RELOC(R_SPARC_13,           11)
ELF_RELOC(R_SPARC_LO10,         12)
ELF_RELOC(R_SPARC_GOT10,        13)
ELF_RELOC(R_SPARC_GOT13,        14)
ELF_RELOC(R_SPARC_GOT22,        15)
ELF_RELOC(R_SPARC_PC10,         16)
ELF_RELOC(R_SPARC_PC22,         17)
ELF_RELOC(R_SPARC_WPLT30,       18)
ELF_RELOC(R_SPARC_COPY,         19)
ELF_RELOC(R_SPARC_GLOB_DAT,     20)
:(途中省略)
ELF_RELOC(R_SPARC_TLS_DTPOFF32,   76)
ELF_RELOC(R_SPARC_TLS_DTPOFF64,   77)
ELF_RELOC(R_SPARC_TLS_TPOFF32,    78)
ELF_RELOC(R_SPARC_TLS_TPOFF64,    79)
ELF_RELOC(R_SPARC_GOTDATA_HIX22,  80)
ELF_RELOC(R_SPARC_GOTDATA_LOX10,  81)
ELF_RELOC(R_SPARC_GOTDATA_OP_HIX22,  82)
ELF_RELOC(R_SPARC_GOTDATA_OP_LOX10,  83)
ELF_RELOC(R_SPARC_GOTDATA_OP,     84)

ここにIntel(x86_64)はあるけどSPARCがない
llvm-project-main/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.cpp

// The target location for the relocation is described by RE.SectionID and
// RE.Offset.  RE.SectionID can be used to find the SectionEntry.  Each
// SectionEntry has three members describing its location.
// SectionEntry::Address is the address at which the section has been loaded
// into memory in the current (host) process.  SectionEntry::LoadAddress is the
// address that the section will have in the target process.
// SectionEntry::ObjAddress is the address of the bits for this section in the
// original emitted object image (also in the current address space).
//
// Relocations will be applied as if the section were loaded at
// SectionEntry::LoadAddress, but they will be applied at an address based
// on SectionEntry::Address.  SectionEntry::ObjAddress will be used to refer to
// Target memory contents if they are required for value calculations.
//
// The Value parameter here is the load address of the symbol for the
// relocation to be applied.  For relocations which refer to symbols in the
// current object Value will be the LoadAddress of the section in which
// the symbol resides (RE.Addend provides additional information about the
// symbol location).  For external symbols, Value will be the address of the
// symbol in the target address space.
void RuntimeDyldELF::resolveRelocation(const RelocationEntry &RE,
                                       uint64_t Value) {
  const SectionEntry &Section = Sections[RE.SectionID];
  return resolveRelocation(Section, RE.Offset, Value, RE.RelType, RE.Addend,
                           RE.SymOffset, RE.SectionID);
}

void RuntimeDyldELF::resolveRelocation(const SectionEntry &Section,
                                       uint64_t Offset, uint64_t Value,
                                       uint32_t Type, int64_t Addend,
                                       uint64_t SymOffset, SID SectionID) {
  switch (Arch) {
  case Triple::x86_64:
    resolveX86_64Relocation(Section, Offset, Value, Type, Addend, SymOffset);
    break;
  case Triple::x86:
    resolveX86Relocation(Section, Offset, (uint32_t)(Value & 0xffffffffL), Type,
                         (uint32_t)(Addend & 0xffffffffL));
    break;
  case Triple::aarch64:
  case Triple::aarch64_be:
    resolveAArch64Relocation(Section, Offset, Value, Type, Addend);
    break;
  case Triple::arm: // Fall through.
  case Triple::armeb:
  case Triple::thumb:
  case Triple::thumbeb:
    resolveARMRelocation(Section, Offset, (uint32_t)(Value & 0xffffffffL), Type,
                         (uint32_t)(Addend & 0xffffffffL));
    break;
  case Triple::ppc: // Fall through.
  case Triple::ppcle:
    resolvePPC32Relocation(Section, Offset, Value, Type, Addend);
    break;
  case Triple::ppc64: // Fall through.
  case Triple::ppc64le:
    resolvePPC64Relocation(Section, Offset, Value, Type, Addend);
    break;
  case Triple::systemz:
    resolveSystemZRelocation(Section, Offset, Value, Type, Addend);
    break;
  case Triple::bpfel:
  case Triple::bpfeb:
    resolveBPFRelocation(Section, Offset, Value, Type, Addend);
    break;
  default:
    llvm_unreachable("Unsupported CPU type!");
  }
}

※こんなに簡単には見つかってません もっと泥臭いです

ELFとは

LinuxなどUNIX系OSでオブジェクトファイルや実行形式のファイルのフォーマットは、このELF(Executable and Linking Format)が使われています。
前述のIntelにはあるけどSPARCにはない処理はプロセッサ毎のELFに再配置している部分にSPARCの再配置処理がない、つまりSPARCで実行形式ファイルが作れないということです。

どうしたらSPARCのELFに再配置できるのか

そもそもSPARCは誰もの?
はじまりは、SUNマイクロシステムズでした。それをOracleが引き取っています。
なので資料はOracleサイトに載っているはず
なので
Oracle sparc elf
というキーワードを入れてググリます。
すぐに以下のサイトがヒットします。

https://docs.oracle.com/cd/E37932_01/html/E36753/chapter6-43405.html
リンカーとかそれっぽいですね。
ELFについて日本語で書かれています。
サイトの左側のメニューに 64 ビット SPARC: 再配置型 が目に留まると思います。
ここがビンゴです。
表 12-17 x64: ELF 再配置型 の表のいずれかの再配置方式が今回の要件の「アルファベットJの次」のマシンに対応します。
なので表のいずれかを見つけて再配置処理をプログラミングすれば完成です。
(おいおい、気安く言うなよ とっても面倒だったろ)
実際のプログラミングではビットのシフトやら置き換えやら地味な処理を記述します。
一部を載せておきます。テイストだけ味わってください。
  • 再配置型名(値):R_SPARC_DISP32(6)
case ELF::R_SPARC_DISP32:
	// S + A を計算
	Value += Addend;
	// セクションのアドレスの値をポインタ値に設定
	TargetPtr32 = reinterpret_cast<uint32_t*>(TargetAddress);
	// S + A の値に - P を計算
	*TargetPtr32 = (Value - reinterpret_cast<uintptr_t>(TargetPtr32));
	break;

月面着陸できた感じ

LLVMといったコンパイラ基盤でも公開されている資料を使って改造できないことはないのだなと2017年の年末~2018年の年始を振り返って思いにふける。

Discussion

ログインするとコメントできます