Hatena::ブログ(Diary)

Flast?なにそれ、おいしいの? このページをアンテナに追加 RSSフィード Twitter

2011-12-29

libstdc++.7のversioned namespaceに起因する問題

GCC4.6.3/4.7.0のlibstdc++から修正されたversioned namespaceを有効にするとboost/detail/container_fwd.hppのforward declとぶつかって曖昧性が生じるためコンパイルエラーになる部分が発生しています。
Interprocessでも同様の問題があったようですが、こちらは修正済みです。

既に報告しましたがこれがどのサブライブラリ下にあるのか私はちょっとよくわかってないので(Boost.Containersかと思ったけど違うよう)対応までには時間がかかるかもしれないです。

#6323 (Conflicting forward declarations with versioned namespace in libstdc++.) ? Boost C++ Libraries
#6287 (hardcoded fwd declarations don’t work for gcc configured withor --enable-symvers=gnu-versioned-namespace) ? Boost C++ Libraries

あと私は4.6.3でしか試してないので4.7.0の方はもう少し違うかもしれないです。

現在のwaはboost/detail/container_fwd.hppで以下のようにします。

Index: container_fwd.hpp
===================================================================
--- container_fwd.hpp	(revision 76217)
+++ container_fwd.hpp	(working copy)
@@ -110,6 +110,8 @@
 
 namespace std
 {
+namespace __7
+{
     template <class T> class allocator;
     template <class charT, class traits, class Allocator> class basic_string;
 
@@ -124,6 +126,7 @@
 #else
     template <class T> class complex;
 #endif
+}
 
 #if !defined(BOOST_CONTAINER_FWD_BAD_DEQUE)
     template <class T, class Allocator> class deque;
@@ -140,8 +143,11 @@
 #if !defined(BOOST_CONTAINER_FWD_BAD_BITSET)
     template <size_t N> class bitset;
 #endif
+namespace __7
+{
     template <class T1, class T2> struct pair;
 }
+}
 
 #if defined(BOOST_MSVC)
 #pragma warning(pop)

いくつかの定義がstd::__7に移っているのでnamespaceを修正すれば問題ないです。
次のBoost 1.49.0で直ってるといいのですが。

2011-12-25

bjam AdC jp 2011 25日目

らすとです。

jamではなくPython

Python知らんし別にJamで困ってないので問題ないのではということに気づきました。

まとめ

結局私を助けてくれる人は一人もいませんでした。

bjam AdC jp 2011 23日目

はー

Boost.Build PythonPortとは

ラスト二回はPythonPortについてにします。と言っても使ったこと無いのでドキュメント見ながら書いてます。
Boost.Build PythonPortに関するドキュメントはこちら

※注 Boost.Build PythonPortは現状でexperimental扱いです。www.boost.orgではアンドキュメント扱いなので場合によっては正しくビルドされないかも知れません。

とりあえずこのPythonPortとはなんぞやということから。本家ではBoost.Build(bjam)には以下問題があると言っています。

  • どマイナーすぎてだれも使ってないし開発者がいないのはやばい
  • bjamいみわからん
  • 標準ライブラリないし使えない
  • データ構造が文字列のリストしか無くて使えないし遅い

肯定しかできないのが素晴らしいですね。
ということで、メジャーだしBoost.PythonあるしPython使おうということになったのです。

現状ですべて実装されているようです。また、1:1で対応がとれているらしいので使ってみるといいかも知れないです。
と書かれてますが一部エラーになったりしました...

Boost.Build PythonPortのビルド

実はtools/build/v2/bootstrap.shを実行するだけではPythonPortは有効になりません。
1.48.0現在は以下のようにする必要があります。

cd $BOOST_ROOT/tools/build/v2
./bootstrap.sh
cd engine
./build.sh --with-python=/usr
TOOLSET=`./build.sh --guess-toolset`
ARCH=`./bootstrap/jam0 -d0 -f build.jam --toolset=$TOOLSET --toolset-root= --show-locate-target`
cp -f engine/$ARCH/b2 engine/$ARCH/bjam ..
cd ..
sudo ./b2 install --prefix=$PREFIX
sudo cp -f *.py *.pyc $PREFIX/share/boost_build/


bootstrap.shに特設渡すことができないので面倒です。Windows環境では--with-pythonPythonがインストールされてるパスを渡した上で、Boost.Buildがインストールされたディレクトリにも必要なファイルをコピーする必要があります。

Boost.Build PythonPortでビルドする

以上のことをしてPythonPortが有効なbjamを用意したらあとは手元のプロジェクトをビルドするだけです。
いつもの通りbjamを起動するだけではPythonPortは使用されません。bjamに--pythonを渡します。

tools/build/v2/example/helloをPythonPortでビルドしてみると以下のようになります。

$ bjam --python
Boost.Build V2 Python port (experimental)
...found 10 targets...
...updating 4 targets...
common.MkDir1-quick-fix-for-unix ./bin/gcc-4.6.3
common.MkDir1-quick-fix-for-unix ./bin/gcc-4.6.3/debug
gcc.compile.c++ ./bin/gcc-4.6.3/debug/hello.o
gcc.link ./bin/gcc-4.6.3/debug/hello
...updated 4 targets...


ちなみにJamでビルドするとこんな感じ。微妙に違います。
$ bjam
...found 9 targets...
...updating 5 targets...
common.mkdir bin
common.mkdir bin/gcc-4.6.3
common.mkdir bin/gcc-4.6.3/debug
gcc.compile.c++ bin/gcc-4.6.3/debug/hello.o
gcc.link bin/gcc-4.6.3/debug/hello
...updated 5 targets...

つまり

次回はPythonで書きましょう。

2011-12-21

bjam AdC jp 2011 21日目

つらいれす...

フラグとか共通化

feature.extend toolset : nvcc ;
toolset.inherit-generators nvcc : unix : unix.link unix.link.dll ;
toolset.inherit nvcc : unix ;

generators.override nvcc.prebuilt : builtin.lib-generator ;
generators.override nvcc.prebuilt : builtin.prebuilt ;
generators.override nvcc.searched-lib-generator : searched-lib-generator ;

ここらへんは既存のものを使い回すための設定です。最初の1行だけはnvccというtoolsetがあるという登録をしてる感じです。

rule init ( version ? : command * : options * )
{
	local condition = [ common.check-init-parameters nvcc : version $(version) ] ;
	local command = [ common.get-invocation-command nvcc : nvcc : $(command) ] ;
	handle-options nvcc : $(condition) : $(command) : $(options) ;
}

local rule handle-options ( toolset : condition * : command * : options * )
{
	common.handle-options $(toolset) : $(condition) : $(command) : $(options) ;

	flags nvcc.compile.cu OPTIONS $(condition)
	  : [ feature.get-values <cuflags> : $(options) ]
	  : unchecked ;
}

using nvcc ; で呼ばれるinit ruleです。これはtoolsetの初期化を行ないます。バージョン番号を取得したり色々します。
今回は特に何もしてないです。大体こんなことかいてあればいい感じらしいので適当です。

で、generatorがくるのですが、これは前々回やってるので飛ばします。

コンパイラオプション周りの定義です。

flags nvcc.compile OPTIONS <address-model>32 : -m32 ;
flags nvcc.compile OPTIONS <address-model>64 : -m64 ;

flags nvcc.compile OPTIONS <debug-symbols>on : -g ;

feature device-debug-symbols : off on : propagated ;
flags nvcc.compile OPTIONS <device-debug-symbols>on : -G ;

flags nvcc.compile DEFINES <define> ;
flags nvcc.compile INCLUDES <include> ;

feature cuflags : : free optional ;
flags nvcc.compile.cu OPTIONS <cuflags> ;

flags nvcc.link LINKPATH <library-path> ;
flags nvcc.link LIBRARIES <library-file> ;
flags nvcc.link OPTIONS <linkflags> ;

feature ruleはrequirementsなどに書くfeatureを定義します。いくつかはbuiltinで定義されています。ここではnvcc特有のデバイスコードのデバッグシンボルを含めるかのフラグと<cflags>/<cxxflags>とは違うCUDA専用のフラグとして<cuflags>を定義します。
featureにも種類があって、何でも記述できるfreeや無くても問題ないoptionalなどがいくつかあります。公式のドキュメントに書いてあるので探すといいです。

次にflags ruleです。これは定義したfeatureをどうやってコマンドに投げつけるかという簡単な対応を記述できます。

flags nvcc.compile OPTIONS <device-debug-symbols>on : -G ;

という書き方のだと、

nvcc toolsetでコンパイルする場合に<device-debug-symbols>onというrequirementsが記述されていればOPTIONSというシェル変数に-Gを追加する

となります。OPTIONSはjamで使われる変数ではなく、actionsなどで使われるシェル変数であることに注意してください。
同じ要領でインクルードパスやリンカフラグも設定します。

コンパイラに渡す部分

rule compile.cu ( target * : sources * : properties * )
{
	local target = [ feature.get-values target : $(properties) ] ;
	switch $(target)
	{
		case "" : OPTIONS on $(targets) += -c ;
		case * : EXIT "unknown target" ;
	}
}
actions compile.cu
{
	$(CONFIG_COMMAND) $(OPTIONS) -D$(DEFINES) -I"$(INCLUDES)" -o "$(<)" "$(>)"
}

actions link bind LIBRARIES
{
	$(CONFIG_COMMAND) $(OPTIONS) -L"$(LINKPATH)" -o "$(<)" "$(>)" $(LIBRARIES)
}

rule名と同名のactionsがある場合、ruleを処理してからactionsが呼ばれます。また、actions内ではruleの第1引数は$(>)第2引数は$(<)という変数に格納されます。
$(CONFIG_COMMAND)はinit rule内で呼び出したcommon.handle-options内で定義されます。とりあえず適当にやった甲斐があったものです。

また、rule側でactionsで使用するシェル変数を定義したり変更したりしたいことがあります。この時、シェル変数はターゲット毎に空間が用意されているので、どのターゲットに対して操作するかを指定した上で操作する必要があります。
それが OPTIONS on $(targets) という書き方で、この場合 <target> が無いのでデフォルトでオブジェクトを生成するように-cオプションを追加しています。

また、bjamのsequenceについて面白い特性を見ることができます。通常、シェルスクリプトなどで

var = hoge fuga piyo
echo x$(var)

とすると、出力結果は

xhoge fuga piyo

となります。

しかし、bjamではsequenceにスペースで区切られていない隣合ったものがある場合、すべての順列に展開します。つまり

var = hoge fuga piyo ;
ECHO x$(var) ;

の出力結果は

xhoge xfuga xpiyo

となります。すべての順列なので、複数のsequenceが隣り合っている場合、

var1 = hoge fuga piyo ;
var2 = foo bar baz ;
ECHO $(var1)$(var2) ;

の出力結果は

hogefoo hogebar hogebaz fugafoo fugabar fugabaz piyofoo piyobar piyobaz

となります。

これを利用すると、すべてのインクルードパスに-Iを付けることも、マクロに-Dを付けることも容易です。
これでコンパイルに必要な引数がうまく生成されるわけです。

まとめ

あと2日分のネタがありません。

2011-12-19

bjam AdC jp 2011 19日目

やっべーはてな時間で日付計算してたわー

前回大まかな流れを説明したので今回はtype/cuda.jamです。が、これは単純で説明も何も無いのでちょっと掘り進めます。

scanner

前回ものすごくシンプルなcuda-scannerを示しました。というより単純に派生して何もしてないだけですが。
で、このscannerが何をしているかってのを見ていきます。
どうでもいいですが、ちょっと調べてみたらscannerのctorは1 sequence受け取ればいいようなので、cuda-scanner.__init__ ruleみたいに9まで並べる必要は無いです。

cuda-scannerの継承関係は

cuda-scanner -> c-scanner(tools/types/cpp.jam) -> scanner(build/scanner.jam)

となっています。

scannerクラスを見ると

class scanner
{
	rule __init__ ( )
	{
	}

	rule pattern ( )
	{
		error "method must be overriden" ;
	}

	rule process ( target : matches * )
	{
		error "method must be overriden" ;
	}
}

となっていることから、pattern ruleとprocess ruleがあればとりあえず問題ないということがわかります。abstractキーワード的なのはbjamには無いのでとりあえずerrorにしている感じです。

問題のc-scannerは主要な部分だけ抜き出すと以下のようになってます。

    rule pattern ( )
    {
        return "#[ \t]*include[ ]*(<(.*)>|\"(.*)\")" ;
    }
 
    rule process ( target : matches * : binding )
    {
        local angle  = [ regex.transform $(matches) : "<(.*)>"   ] ;
        angle = [ sequence.transform path.native : $(angle) ] ;
        local quoted = [ regex.transform $(matches) : "\"(.*)\"" ] ;
        quoted = [ sequence.transform path.native : $(quoted) ] ;
 
        local g = [ on $(target) return $(HDRGRIST) ] ;
        local b = [ NORMALIZE_PATH $(binding:D) ] ;
 
        local g2 = $(g)"#"$(b) ;
 
        angle = $(angle:G=$(g)) ;
        quoted = $(quoted:G=$(g2)) ;
 
        local all = $(angle) $(quoted) ;
 
        INCLUDES $(target) : $(all) ;
        NOCARE $(all) ;
        SEARCH on $(angle) = $(self.includes:G=) ;
        SEARCH on $(quoted) = $(b) $(self.includes:G=) ;
 
        scanner.propagate $(__name__) : $(angle) $(quoted) : $(target) ;
 
        ISFILE $(angle) $(quoted) ;
    }

pattern ruleでソースを走査する際に使う正規表現を定義します。この正規表現をずっと使うことになるので、意味不明な構文を持つ言語だとやたら変な正規表現を書くことになると思います。includeの次の空白部分に\tが含まれていないのが若干気になりますが後でバグレポ投げときます。多分。
この正規表現でマッチした場合、\1がprocess ruleのmatchesに投げられることになるので、依存しているファイル名やモジュール名を()で囲う必要があります。C系言語の場合<>と""でincludeの挙動が異なるのでこれも含めた部分を囲っています。

process ruleでpatternでマッチした部分から依存しているファイルのリストを作ります。c-scannerの場合ここで<>(angle)と""(quoted)を振り分けています。
で、本来なら再帰的にこいつらを走査すると思うのですが、どうもそうではないのかprocessが呼ばれてる様子が無いのにもかかわらず変更の検出などを行っています。まったく謎です...

register/set-scanner

registerとset-scannerは計3箇所あります。これらは前回軽く説明したので特に要らないかと思いますが、一応。

scanner.register cuda-scanner : include ;
type.register CUDA : cu ;
type.set-scanner CUDA : cuda-scanner ;

scanner.registerはscannerがどのプロパティを要求しているかという情報と共に登録しています。走査する際にはここで指定したプロパティがctorに渡されてscannerが構築されます。
type.registerはターゲットタイプを登録するものです。今回はCUDA Cの拡張子の.cuだけですが、C++の場合は.cpp .cxx .ccなどもすべてC++のソースなので

type.register CPP : cpp cxx cc ;

となっています。
type.set-scannerで、あるターゲットタイプはどのscannerを使うかを設定しています。

一連の流れ

たとえば

exe hoge : hoge.cu ;

というビルドターゲットがあった場合、hoge.cuというソースはtype.registerで設定したとおりCUDAという種類のソースであることがわかります。
またexe ruleの出力は実行形式のファイルなので、EXEという種類の出力を出すことがわかります。
これからCUDA -> EXEという関係が作られます。

そうするとソースであるCUDAを解析しようとします。CUDAというターゲットはtype.set-scannerで設定したとおりcuda-scannerを使うことがわかります。
scanner.registerでcuda-scannerはincludeプロパティが必要であることを設定したので、現在のプロジェクトおよびexe ruleのrequirementsから<include>を引っ張ってきてcuda-scannerに投げてscannerを構築します。

scannerが構築できると実際にソースを走査します。ここでcuda-scanner.patternにマッチしたものをcuda-scanner.processに投げます。
こうすることで依存関係を解決します。

まとめ

よくわからん。

2011-12-18

bjam AdC jp 2011 17日目

遅れてしまった...というか忘れてた...

今回からは何回かに分けてtoolsetについてやります。といっても私自身あんまりわかってないので昔書いたnvcc.jamを元に適当に説明します。

nvcc.jamは http://www.flast.jp/browser/btc/tools にあります。

toolsetとはなにか

toolsetはコンパイラなどのツールについてbjamから共通のインターフェースで操作するためにいろいろがんばってるやつです。

gccとmsvcではコンパイラオプションなどが違うことは周知の事実だと思います。しかし、bjamでビルドする際にはその差異についてはほとんど考慮する必要が無いように作られています。たとえばhoge.cppからhogeというバイナリを吐く場合、

project hoge ;

exe hoge : hoge.cpp ;

と書いてuser-config.jamやコマンドライン引数でどのコンパイラを使うか指定すると思います。

make単体ではこういったことはできないですし、autotoolsに任せるのも限界があります。configureでCC=clと指定してもconfigureが生成するMakefileはcl用にはなってないはずです。
まだgcc/msvc程度ならそれぞれ用意するとか.slnを用意するとかになると思いますが、他のコンパイラを使う必要があるとかどのコンパイラを使われるかわからないとかだともうお手上げなはずです。

つまりbjamの本領発揮となるわけです。

概要

今日は大まかな流れのみです。次回以降細かくやります。

toolsetはBoost.Buildのインストールディレクトリ(*nixではデフォルトで/usr/local/share/boost-build)以下にtoolsというディレクトリがありますが、このディレクトリ以下に用意されてるjamファイルがtoolsetを記述しているものです。
また、tools/typesというディレクトリもありますが、こちらはmakefileにおけるsuffixルールのようなものが記述されています。

つまり新たなtoolsetを作るには1つまたは2つの手順を踏む必要があります。

  • tools以下に追加するtoolsetを記述する
  • もしtoolsetが処理する拡張しがtools/types以下に登録されていないのであれば用意する

今回nvcc.jamを作るにあたって、CUDAは.cuファイルをソースとして用いるので

  • tools/nvcc.jamをつくる
  • tools/types/cuda.jamをつくる

という手順を踏みました。

tools/types/cuda.jam

cuda.jamは簡単なので先に説明します。

以下にソースの主要部分を載せます

import type ;
import scanner ;

class cuda-scanner : c-scanner
{
	rule __init__ ( * )
	{
		c-scanner.__init__ $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ;
	}
}
scanner.register cuda-scanner : include ;

type.register CUDA : cu ;
type.set-scanner CUDA : cuda-scanner ;

それほど多くのことはやってないです。このscannerというのはソースの依存関係を走査するときに使われるもので、CUDAはC系言語と同じくincludeで他のファイルへの依存関係を構築するので既にBoost.Buildで用意されてるc-scannerを使います。
また、scannerモジュールのregister ruleの第2引数に渡してるのは、走査する際にどのプロパティに依存するかというsequenceです。C系言語なのでincludeプロパティだけあれば問題ないです。

ここでこう疑問に思うはずです、本来includeだけではなくdefineでインクルードすべきファイルを取捨選択してるはずだと。つまりdefineプロパティでもincludeするファイルは変わるし、そもそもコンパイラのbuilt-inマクロでも変化するはずだと。
実際そのとおりで、これはプリプロセスするまで真の依存関係はわかりません。で、Boost.Buildはどうしてるかというと、include <...>かinclude "..."となっている部分を正規表現で抜き出し、その部分のファイルを依存関係として扱い、再帰的にすべての依存関係を走査していきます。

そのため、Boost.Buildの現在のtoolsetでは

#define DEPENDING_HEADER() "dep.h"
#include DEPENDING_HEADER()

という記述がなされているとこれを依存関係に入れることができず、dep.hおよびdep.hが依存しているファイルに変更があっても検出することができません。Boost.PPのPP_ITERATEとかは特にこれに該当します。

また、次のようにBoostの全hppをincludeしているソースがあっても、すべて依存扱いになってしまうので注意です。

#if 0
#include <boost/token_functions.hpp>
#include <boost/assign.hpp>
...
#include <boost/dynamic_bitset/config.hpp>
#include <boost/dynamic_bitset/dynamic_bitset.hpp>
#endif

これの実行結果は

$ bjam
...found 9021 targets...
...updating 1 target...
gcc.compile.c bin/gcc-4.6.3/debug/test.o
...updated 1 target...


もちろん既に依存扱いになってるファイルがincludeされても再度見に行ったり、無限ループに陥るといったことは無いです。
ということで悲しいけどincludeプロパティだけで何とかしてもらうことにします。

type.registerでCUDAという型を.cuという拡張子に関連付けています。また、type.set-scannerで、CUDAという型はcuda-scannerというscannerを使うように関連付けています。

tools/nvcc.jam

# import省略
feature.extend toolset : nvcc ;
# 省略

rule init ( version ? command * : options * )
{
# 省略
}

class CUDA-compiling-generator : C-compiling-generator
{
	rule __init__ ( id : source-types + : target-types +
		: requirements * : optional-properties * )
	{
		C-compiling-generator.__init__ $(id)
			: $(source-types) : $(target-types)
			: $(requirements) : $(optional-properties) ;
	}
}

rule register-cuda-compiler ( id
	: source-types + : target-types +
	: requirements * : optional-properties * )
{
	generators.register
		[ new CUDA-compiling-generator $(id)
			: $(source-types) : $(target-types)
			: $(requirements) : $(optional-properties) ] ;
}

IMPORT $(__name__) : register-cuda-compiler : : generators.register-cuda-compiler ;

generators.register-cuda.compiler nvcc.compile.cu : CUDA : OBJ : <toolset>nvcc ;

type.set-generated-target-suffix OBJ : <toolset>nvcc : o ;

# flags 省略

rule compile.cu ( targets * : sources * : properties * )
{
	local target = [ feature.get-values target : $(properties) ] ;
	switch $(target)
	{
		case "" :
			OPTIONS on $(targets) += -c ;
		case * :
			EXIT "error" ;
	}
}

actions compile.cu
{
	$(CONFIG_COMMAND) $(OPTIONS) -D$(DEFINES) -I"$(INCLUDES)" -o "$(<)" "$(>)"
}

actions link bind LIBRARIES
{
	$(CONFIG_COMMAND) $(OPTIONS) -L"$(LINKPATH)" -o "$(<)" "$(>)" $(LIBRARIES)
}

省略と書いてる以上に省略してますが、重要な部分のみ残しました。

init ruleはusing ruleを使ってtoolsetを初期化するときに呼ばれるruleです。いつもusing gcc ;とかusing gcc : gcc-4.6 ;とか書いてると思いますが、それら引数はgcc.jamのinitに渡されているのです。

CUDA-compiling-generatorクラスはtoolsetとソースを関連付けるために使われるクラスです。CUDAの場合C(C++)のgeneratorと同一で問題ないですが、今後のことも考えて派生してあります。
Boost.Buildはこのほかにもlinking-generatorやarchive-generatorといったgeneratorを用意しています。

register-cuda-compiler ruleで実際に関連付けています。ちょうどIMPORT文のがそれにあたり、OBJというファイルをCUDAというファイルからtoolset nvccを用いて生成するといった意味合いになります。

compile.cu rule/actionsやlink actionsで実際にコンパイル/リンクする方法を記述します。

まとめ

おおまかな流れは以上のとおりです。実際はさまざまなプロパティに対応させるためにプロパティの登録や関連付けを行いますが、とりあえず今回は無視です。

次回はtools/type/cuda.jamのほうを見ますかね...