🦁

Cコンパイラ作成入門をcmakeでやる。

2023/09/16に公開

はじめに

「低レイヤを知りたい人のための C コンパイラ作成入門」を読んでいます。

https://www.sigbus.info/compilerbook

IDE を使ってコードを書いてると、Makefile を使うより Cmake を使った方がデバッグ機能を使えて便利なので、Makefile を Cmake に書き換えてみました。

この記事では私が書いた CMakeLists.txt を紹介します。なお私は cmake を書くのはこれが初めてです。

シェルスクリプトによるテスト

chibicc の最初期はシェルスクリプトでテストを記述し、それを Makefile から呼び出しています。chibicc が単一の main.c を tokeninze.c と parse.c と codegen.c に分離したコミットの Makefile が以下です。

https://github.com/rui314/chibicc/blame/725badfb494544b7c7f1d4c4690b9bc033c6d051/Makefile

これを次のように書き換えました。

CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(chibicc C)
set(CMAKE_C_STANDARD 17)

# build chibicc
file(GLOB SRCS *.c)
add_executable(chibicc ${SRCS})

# tests
enable_testing()
add_test(NAME test COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh)

1 行目で cmake のバージョンを指定します。

cmake_minimum_required(VERSION 3.14)

手元の Mac には3.27.4がインストールされていました。ただ、3.0 以降のバージョンだとどれを指定しても結果は変わらなかったのでキリよく3.14を指定しました。

2,3 行目でプロジェクト名と C 言語の仕様のバージョンを指定します。

project(chibicc C)
set(CMAKE_C_STANDARD 17)

C 言語の仕様のバージョンは 11 を指定しても 17 を指定しても結果は変わらなかったので新しい方の 17 を指定しました。

以下ではchibiccのコンパイルに必要なファイルを指定します。

# build chibicc
file(GLOB SRCS *.c)
add_executable(chibicc ${SRCS})

*.cで指定すると、一時的に作成したtmp.cなどにもマッチしてしまうのに注意が必要です。高々数個のファイルしかないのでベタ書きした方が便利かもしれません。

テストのシェルスクリプトは以下のように呼び出します。

# tests
enable_testing()
add_test(NAME test COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh)

COMMAND でシェルスクリプトを呼び出せます。

C によるテスト

chibicc の開発が進んでくると、テストの量が多くなってきてシェルスクリプトでは開発しづらくなってきます。chibicc ではある程度言語が育ってきた段階で C 言語によるテストに変更し高速化を行います。そのコミットが以下です。

https://github.com/rui314/chibicc/commit/cd832a311e56bda981c9c957ba45f1bc1f6cc737

この段階の Makefile を次のように書き換えました。

CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(chibicc C)
set(CMAKE_C_STANDARD 17)

# build chibicc
file(GLOB SRCS *.c)
add_executable(chibicc ${SRCS})

# build tests
enable_testing()
add_test(NAME driver COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test/driver.sh)
file(GLOB TEST_SRCS test/*.c)
foreach (TEST_SRC ${TEST_SRCS})
    get_filename_component(TEST_NAME ${TEST_SRC} NAME_WE)
    add_custom_target(
            ${TEST_NAME}.exe ALL
            COMMAND clang -o- -P -E -C ${TEST_SRC} | ./chibicc -o ${TEST_NAME}.s -
            COMMAND clang -o ${TEST_NAME}.exe ${TEST_NAME}.s -x c ${CMAKE_CURRENT_SOURCE_DIR}/test/common
            DEPENDS chibicc
    )
    add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}.exe)
endforeach ()

テストファイルの拡張子を除いたファイル名を取得する関数です。

get_filename_component(TEST_NAME ${TEST_SRC} NAME_WE)

add_custom_target でテストの実行ファイルを生成します。

add_custom_target(
    ${TEST_NAME}.exe ALL
    COMMAND clang -o- -P -E -C ${TEST_SRC} | ./chibicc -o ${TEST_NAME}.s -
    COMMAND clang -o ${TEST_NAME}.exe ${TEST_NAME}.s -x c ${CMAKE_CURRENT_SOURCE_DIR}/test/common
    DEPENDS chibicc
)

テストの実行ファイルを、テストに追加します。

add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}.exe)

おわりに

chibicc を cmake でビルドとテストをできるようにしました。私が使ってる CLion は cmake と相性が良いので、これで快適にデバッグできるようになりました。セルフホストができるようになったらその時の cmake は追記します。今後も chibicc の写経を続けたい。

参考

https://qiita.com/shohirose/items/45fb49c6b429e8b204ac

https://kamino.hatenablog.com/entry/cmake_tutorial1

Discussion