きりかノート 3冊め

おあそびプログラミング

発表: ClangのModules (Xcode)(10/19)

昨年のLLVM DevMtgの提案時と変わっていて情報が錯綜していたので、調べたり試したりしたのでまとめついでに発表した。公式(LLVM Clang)のドキュメントを読んだらだいたい終わりな話。

Modulesってなに?

  • #importの変わりに@importと書く
  • コンパイルが速くなる(らしい)
  • ライブラリ、フレームワークはリンカオプションを指定しなくても自動的にリンクする

従来の#includeや#importでは

  • ヘッダファイルの数が多くなると指数的に遅くなる
  • #include前の#defineにより、#includeしたものが予期しない影響を受ける

とかの問題があって、それらを解決する新方式として登場したのがModulesなのです。

使うには以下の環境が必要です。

基本的にiOSMacのアプリの開発者は、Xcodeがめんどうを見てくれるのであんまり気にする必要はありません。新規プロジェクトつくったら自然に対応済みになってる。

module.map

Modulesのカナメとなるのがmoudle.mapファイル。これでモジュールを定義します。

実際にiOS7のSDKを見てみると、

   % cd `xcrun --sdk iphoneos --show-sdk-path`
   % find . -name module.map
   ./Developer/Library/Frameworks/XCTest.framework/module.map
   ./System/Library/Frameworks/AudioToolbox.framework/module.map
   ./System/Library/Frameworks/AudioUnit.framework/module.map
   ./System/Library/Frameworks/CoreAudio.framework/module.map
     :
   ./usr/include/dispatch/module.map
   ./usr/include/mach-o/module.map
   ./usr/include/module.map
   ./usr/include/objc/module.map

というように、フレームワークやヘッダファイルのある場所にmodule.mapファイルがあることがわかります。ファイルは

   framework module Foundation [system] {
     umbrella header "Foundation.h"

     export *
     module * {
       export *
     }

のようなテキストファイルで、この内容についてはドキュメントの"Module Map Language"を参照してください。今回は説明しません。

Modulesを使ってみる

実際に使ってみます。Mountain Lionだとまだ対応していませんが、Foudation.frameworkの下にmodule.mapファイルをつくることで利用できるようになります。iOS7 SDKを参考に次のようなファイルを用意します。

   framework module Foundation [system] {
     umbrella header "Foundation.h"

     export *
     module * {
       export *
     }
   }

まずは従来型のコードです。#importでFoundationのヘッダファイルを指定したものです。

   // mod1
   #import <Foundation/Foundation.h>
   int main (int argc, const char * argv[]) {
     :

フレームワークを使うときは、コンパイル時に-frameworkオプションでフレームワーク名を指定する必要があります。

   % clang mod1.m -o mod1 -framework Foundation
                    # framework name ^^^^^^^^^^

次にModulesを使った例です。@importでモジュール名(Foundation)を指定します。末尾にセミコロンが必要なので忘れずに。

   @import Foundation;
   /* ^^^^ using modules */
   int main (int argc, const char * argv[]) {
     :

モジュールを使うときは-fmodulesオプションを使います。フレームワークの指定は必要ありません。

   % clang mod2.m -o mod2 -fmodules
        # no -framework   ^^^^^^^^^
   % otool -L mod2
   mod2:
     :
     /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (...)

ちなみに、-fmodulesオプションを使うと#importも@importとして機能します。

   % clang mod1.m -o mod1 -fmodules
   % ./mod1
   2013-10-18 ...[] Hello, modules!

Xcodeの説明のところで「コード修正しなくていいよ」とあったのでXcode側でなんか対応してるのかと思っていたのですが、単にコンパイラレベルでそういう動作をするようです。

ちなみに自動リンクを無効にするオプションがあるのですが、それを使うともちろんシンボルのエラーになります。

   % clang mod1.m -o mod1 -fmodules -fno-autolink
               #   disable autolink ^^^^^^^^^^^^^
   Undefined symbols for architecture x86_64:
     "_NSLog", referenced from:
         _main in mod1-dTLx89.o
     :

これらのコンパイラオプションに対応する、Xcodeの設定項目は次の2つです。

  • CLANG_ENABLE_MODULES = YES → -fmodules
  • CLANG_MODULES_AUTOLINK = NO → -fno-autolink

ということで簡単にModulesを試してみました。

  • module.mapがあれば利用できる。(ライブラリの再コンパイル等は不要)
  • #importもModulesを有効にすれば@importとして解釈される。

ということがわかりました。

Clang 3.4

現在リリースされているClangのバージョンは3.3(Xcodeも同じ)ですが、現在開発中の3.4はどうなるでしょうか。Clangはドキュメントも開発に合わせて更新されています。

これらをw3m -dumpでテキストに落としてdiffで比較してみました:)

  • C++ support (experimental)
  • compiler options
    • -fmodule-maps, -fmodule-map-file, ...
  • module map lang
    • private, extern, use

C++のサポートが導入され、コンパイラオプションやmodule.mapの予約語が増えるようです。まだまだいろいろ変わりそうですね。

またちょっと視点を変えてツールを見てみると、clang-tools-extraのmodularizeがおもしろそうです。

ヘッダファイルからmoudle.mapを生成することができるようです。ライブラリ・フレームワークの開発者にはよいニュースかもですね。module.mapはよっぽどでなければ簡単に書けるので、ツールがなくてもさほど困るとは思えませんが「moudle.mapの構文を覚えろ」と言われてもやる気でませんし。。

まとめとか感想とか

ということで、簡単にClangのModulesについて説明しました。

ソースコード修正が不要、Xcodeのオプションいっぱつでmodule対応完了など、開発者が移行しやすいよううまく用意されてるなあという印象を受けました。また、3.4の新機能がたくさんあるなど、現状で安定ではなくまだまだ変わっていきそうな雰囲気を感じます。

従来のコンパイル済みヘッダ(PCI)と比べて速くなる理由がいまいちわからなかったのですが、バイナリをモジュール単位(module.mapに記載された、Foundation.frameworkならそのぜんぶ)で生成することでファイルごとのPCIよりパーズ回数とかで有利なのかなあ、と思いました。正直さっぱりです!