Clang
概要
Clang は、LLVM ネイティブな C/C++/Objective-C コンパイラ。
The Clang project provides a language front-end and tooling infrastructure for languages in the C language family (C, C++, Objective C/C++, OpenCL, CUDA, and RenderScript) for the LLVM project.
http://clang.llvm.org
How to Use
インストール
Command Line Tools に付属しており、そちらをインストールした後に xcrun 経由で実行できる。
code:sh
$ xcrun clang -v
Apple clang version 11.0.0 (clang-1100.0.33.8)
Target: x86_64-apple-darwin18.7.0
Thread model: posix
InstalledDir: /Users/tasuwo/Downloads/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
コンパイル
code:.c
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("Hello World!\n");
return 0;
}
code:sh
$ xcrun clang helloworld.c
$ ./a.out
Hello World!
Clang Modules
概要
Swift や Objective-C のフレームワークは、Clang Modules をベースとしたモジュールシステムを共有している。モジュールは .modulemap ファイルで定義され、基本的には Objective-C プロジェクトを Xcode でビルドした時に Xcode によって自動生成される (Swift の場合は生成されず、また別の .swiftmodule などが生成される)。
この .modulemap によるモジュールの定義があることで、Swift, Objective-C のプロジェクトからモジュールのインポートが行えるようになる。
従来の include の問題点
ソフトウェア開発では大抵いくつかのライブラリを利用する。それらライブラリの API にアクセスしたい場合、C ファミリーの言語だとヘッダーファイルを利用し、参照は #include ディレクティブを利用して行ってきた。
code:my_header.h
#define AUTHORS_NAME "tasuwo"
extern void printMyName(char *);
code:main.c
#include "my_header.h"
int main(int argc, char **argv) {
printMyName(AUTHORS_NAME);
}
ただし、#include は C プリプロセッサによって処理される、その箇所を単に文字列で置き換える指示 に過ぎない。そのため、実際にコンパイルされるのは下記のようなコードになる。
code:main.c
extern void printMyName(char *);
int main(int argc, char **argv) {
printMyName("tasuwo");
}
この方法にはいくつかの問題がある。
コンパイル時間の拡大
コンパイラーはヘッダーがincludeされているたびに、そのヘッダーのテキストのパースを前処理を行う必要がある
これは複数のファイルから共通の1つのヘッダーを参照している場合でも、複数ファイル分処理が行われるため、非常に冗長
例えば、N個のファイルが各々M個のヘッダーを include していた場合、処理回数は M*N 回になってしまう
脆さ
ヘッダファイルにマクロが含まれていた場合、その呼び出し順によってコンパイラに渡されるコードが変わってしまう
あるいは、ライブラリの API を破壊してしまう可能性がある
定義がヘッダ間でコンフリクトする可能性もある
これらを解決するために、include の順番を変える、include guard (ifdef~endif) を利用する 等の対策を講じる必要があった
命名におけるworkarounds
C のプロプロセッサの脆さのためにいくつかの命名規則が強要される
例えばマクロ名はコンフリクト回避のために大文字のスネークケースでなくてはならない
ツールの難しさ
C ベースの言語では、ビルドツールでライブラリを扱うのは難しい
ライブラリと自分のコードの境界が曖昧だし、C, C++, Objective-C どの言語のヘッダーかわからない
モジュールによる解決
#include の問題を解決するために、Module Map が導入された。Module Map の考え方の背景は、外部のライブラリのインタフェースを、単なるテキストのコピー&ペーストではなく、コンパイラがどのような内容をインポートするのか?をセマンティックに解釈して扱う、というもの。
モジュールによって、インポートする対象は一度のみパース, コンパイルされる。そのため、処理回数は M * N ではなく M + N になる。また、各々が独立してパースされるためモジュール同士が影響を及ぼしあうこともなく、API やどの言語で動作するか?なども定義できるので、ツールからも扱いやすくなる。
また、モジュールは precompile header (.pch ファイル) を置き換える。
http://clang.llvm.org/docs/Modules.html
定義
Clang の Module Map は、.modulemap ファイルに定義する。.modulemap ファイルは、どのヘッダーがこのモジュールの一部なのか?モジュールがどのような構造か (サブモジュールの定義等)?モジュールが何に依存しているか?ヘッダーに何か必要な処理などがないか?等の情報をコンパイラに教える役割を持つ。
Module Map が含まれれていた場合、コード上の #import や #include は、モジュールのインポートに置き換えられる。
例えば、.framework ファイルをビルドすると、以下のような Module Map が Xcode によって生成される。
Module Map 使ってみよう
適当な C プロジェクトをモジュールとしてインポートしてみよう!
参考
Mach-O Executables · objc.io
The Compiler · objc.io
Exploring Clang Modules
Exploring Clang Modules - Speaker Deck
https://www.youtube.com/watch?v=o3HG0Z3yc5c