RustでCコンパイラ その01 環境構築

2025-11-13 18:10:44

RustでCコンパイラを作り始めることにした。

その界隈では有名な下記の記事があって、一度挑戦したことがあるのだが、挫折したので、
再度、しかもRustでやってみる。(だからセルフコンパイルはできない)

低レイヤを知りたい人のためのCコンパイラ作成入門

なお、この記事はDockerで実装することを推奨しているが、
上記の通りにやってもMacのSiliconチップ上では動かないため、工夫が必要になる。
今回は、その内容。

なお、実装は、下記のGithubにまとめている。
Dockerfileとか、compose.ymlとかも一緒。
コンパイラの名前は、tvcc (tavi c compiler)

ryotakato/tvcc

環境情報

まずは環境情報。
MacBookAir の、2025 M4

$ sw_vers
ProductName:		macOS
ProductVersion:		15.7.1
BuildVersion:		24G231

Rustはasdfでインストール済み

$ rustc --version
rustc 1.89.0 (29483883e 2025-08-04)
$ cargo version
cargo 1.89.0 (c24e10642 2025-06-23)

Dockerもdocker composeも、brewで入れている

$ docker version
Client:
 Version:           28.5.1
 API version:       1.51
 Go version:        go1.24.8
 Git commit:        e180ab8
 Built:             Wed Oct  8 12:16:17 2025
 OS/Arch:           darwin/arm64
 Context:           desktop-linux

Server: Docker Desktop 4.50.0 (209931)
 Engine:
  Version:          28.5.1
  API version:      1.51 (minimum version 1.24)
  Go version:       go1.24.8
  Git commit:       f8215cc
  Built:            Wed Oct  8 12:18:25 2025
  OS/Arch:          linux/arm64
  Experimental:     false
 containerd:
  Version:          1.7.27
  GitCommit:        05044ec0a9a75232cad458027ca83437aae3f4da
 runc:
  Version:          1.2.5
  GitCommit:        v1.2.5-0-g59923ef
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

構成の説明

元の記事のDockerの構成は、
Linux環境下で、さらにLinuxのDockerを立てて実行するたびにコンテナを作ってクリーンな状況で実行するというもの。
この構成は同じにしたいが、
Cソースではなく、Rustソースをコンパイルして、実装していくので、
Makefileで書いてあるビルドやテストの実行はcargoなどで代用していくことになる。

さらに、MacのSiliconチップで開発するので、
普通に作っていくと、x86_64向けのアセンブラが出力されず、全く動かない。
これをどうにかしないといけない。

一応、上記のDockerにRustもインストールさせて、
ソースコードを書くことだけホストでやって、ビルド以降はすべてコンテナの中でやるという手法もあるのだが、
Rustはクロスコンパイルできるはずだと思って、その方法は取らないことにした。
進んでいてどうしても困ったら方針変えるかも。

Rosettaインストール

まず、Apple Siliconの環境でx64_86向けのアプリケーション(ここではDockerでつくるLinuxコンテナ)を動かすため
Rosettaをいれる。
いれるのだが、僕の環境ではGoogle日本語入力いれたときに入っていた。
まあ下記でインストールできる

# インストール
$ softwareupdate --install-rosetta

# 確認
$ arch -x86_64 uname -m
x86_64

Rosettaの詳しい説明は下記。

Rosetta 2 ってなに?|M1 Mac ってなに? ぼくにも使える?

Rustでクロスコンパイル

次にRust

Rustのcargo buildで生成されるバイナリは、
そのままだと、Apple Siliconチップのものであり、
x86_64のLinuxコンテナ上では動かないので、
これをx86_64にコンパイルできるようにする必要がある。

Rustのクロスコンパイルは自前でもできるらしいのだが、色々調べて結構面倒なことが分かったので、
crossというクレートに頼ることにした。

cross - crates.io: Rust Package Registry

バイナリだけほしいので、cargo installでグローバルにいれてしまう。

$ cargo install cross

$ cross --version
cross 0.2.5
[cross] warning: `cross` does not provide a Docker image for target aarch64-apple-darwin, specify a custom image in `Cross.toml`.
[cross] note: Falling back to `cargo` on the host.
cargo 1.89.0 (c24e10642 2025-06-23)

で、ソースを用意したら、cargo buildと同様にビルド
targetはx86_64で、ABIはmuslにしたが、それは下記を参考にしたから、gnuのほうがよいのかなどは正直よくわかっていない。

📕 RustでRui Ueyama先生の低レイヤを知りたい人のためのCコンパイラ作成入門をやってみる1(環境構築から四則演算まで) | Happy developing

$ cross build --target x86_64-unknown-linux-musl

しかし、ここでエラー。
crossはビルドにdockerを使うみたいなんだけど、そのDockerイメージが落ちてこない。
色々調べて、下記をみつけた。

“no matching manifest” on cross v0.2.5 · Issue #1214 · cross-rs/cross

ここで、環境変数を設定していたので、やってみる。

$ CROSS_CONTAINER_OPTS="--platform linux/amd64" cross build --target x86_64-unknown-linux-musl

すると、すんなりコンテナが作成されて、buildが通った。
コンテナが作られていれば、2回目以降はcross build –target x86_64-unknown-linux-musl
だけでよさそう。

ちなみに、上記の参考にした日本語情報では、makeクレートも使っていたみたい。
便利そうだけどどうしようかな。
まあコマンド打つのが面倒になったら使うかも。

Docker構成

低レイヤを知りたい人のためのCコンパイラ作成入門
では、Dockerコマンドだけなんだけど、
僕個人はたとえコンテナ1個でもdocker composeするのが好きなので、
指定されているDockerfileをローカルに落としてきて、
下記compose.ymlファイルを作っている。

services:
  tvcc:
    container_name: tvcc
    platform: linux/amd64
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ${PWD}:/tvcc
    working_dir: /tvcc
    command: ./test.sh

ポイントは、platform指定をしているところ。
これにより、x86_64に対応した実行環境がビルドされ、それがコンテナになる。
後で、Docker DesktopのDashboard画面開くと、AMD64みたいな表記になっているからわかりやすい。

なお、Dockerのplatformについては下記が参考になる。

Docker について|M1 Mac ってなに? ぼくにも使える?

test.shは、こんな感じ
さっきのようにcrossコマンドでビルドすると、./targetx86_64-unknown-linux-musl/debug配下に実行ファイルtvccができるので、
それをコンテナ内での実行するファイルとしてCMD変数にパスとして書いているということ。
gccコマンド時に実行してできたアセンブリを機械語に変換して、その後実行という感じ。

#!/bin/bash

CMD=./target/x86_64-unknown-linux-musl/debug/tvcc
TARGET=./target/tmp

mkdir -p "${TARGET}"

try() {
  expected="$1"
  input="$2"

  ${CMD} "$input" > "${TARGET}/tmp.s"
  gcc -o "${TARGET}/tmp" "${TARGET}/tmp.s"
  "${TARGET}/tmp"
  actual="$?"

  if [ "$actual" = "$expected" ]; then
    echo "$input => $actual"
  else
    echo "$input => $expected expected, but got $actual"
    exit 1
  fi
}

try 0 0
try 42 42

echo OK

とりあえず動くようになってよかった。
そして、クロスコンパイルとか、アーキテクチャについても結構学べた。
ちなみに、Mac siliconで動くアセンブラを出力すればもっと簡単になりそうだが、そこまで手を出すと長そうなのでやめておく。

ちなみにまだ下記のようなアセンブラを出力するだけのプログラムなので、あまり意味はない。
次回からはいよいよ進めていく。

.intel_syntax noprefix
.globl main
main:
  mov rax, 42
  ret

参考

📕 RustでRui Ueyama先生の低レイヤを知りたい人のためのCコンパイラ作成入門をやってみる1(環境構築から四則演算まで) | Happy developing

[Rust] 開発環境と実行環境が違う場合のビルド方法 | DevelopersIO

「低レイヤを知りたい人のためのCコンパイラ作成入門」をApple Silliconで動かす

hatappo/compilerbook: 『低レイヤを知りたい人のためのCコンパイラ作成入門』

M1 Macでchibiccを動かす - sbite’s blog




Comments

There are currently no comments on this article, be the first to add one below

Add a Comment

Note that I may remove comments for any reason, so try to be civil. If you are looking for a response to your comment, either leave your email address or check back on this page periodically.