2008-10-06

変奏と反復 (1a)

変奏なのだ、これは。

彼はといえば、重荷を背負い、坂をのぼったりくだったりしていた。坂が多い街なのだ、横浜は。名高い女子校は丘のうえにあった。坂と運河が元町と寿町を分けた。丘のうえには名高い教会があり、そのすじでは名高いプールつきの邸宅があり、丘のむこうの基地はすでになかった。仕立ての良い制服と透徹を身にまとい、彼は歩きつづけた。そのころの彼は、なにしろ、モールトンを手に入れてなかった。

いまもって。いまなお。

長い髪を切った少女が「ぼくたちの失敗」を唄うのを眺める彼は、唇をしめらすアルコールと同じくらい引きのばされたあらゆる二度めのものどものことを思った。育ちのよい少女がカラオケボックスを去り、身持ちのよろしい少女が石川町の改札に消えてもなお、彼はそのことを思いつづけた。

「ぼくは上京するぜ」と、彼は言った。少女は首をかしげると、切りそろえられた前髪が微笑した。「莫迦じゃない」と、少女は身をひるがえした。彼が神田川沿いの風呂なしのアパートに住むのは、それからもうすこし先の話だ。

2008-10-03

透過的なLLVM (7)

ほとんどもう、透過的にLLVMを使うことができるようになった。

Mac OS X 10.5 (darwin9) では、メディアを扱うライブラリやツール、つまり圧縮や暗号化は確かに高速になる。32bitで試した範囲ではGNU/Linuxシステム、畢竟ELF形式もビルドに問題はなさそうだ。ビルドに問題はないが、速度が向上するとは限らない。メディアを扱うライブラリやツールは、しばしばアセンブリ言語で高速なコードを記述する。Native codeがまざるとLLVMの魔法が効きにくくなる。

OpenSSLが良い例だ。OpenSSL 0.9.8iのcpuid命令の実装は、アーキテクチャごとに4個のファイルを用意している。

  • crypto/ia64cpuid.S
  • crypto/sparccpuid.S
  • crypto/x86_64cpuid.pl
  • crypto/x86cpuid.pl

さまざまのアセンブラに対応するべく、x86-64とx86はアセンブリコードを生成するPerlスクリプトとして記述されている。対応するアセンブラにELF形式は含まれるが、Mach-O形式は含まれない。つまり、Darwinで普通にビルドしたOpenSSLはもろもろのハッシュ、AESやらDESやらをCで書かれたコードで計算している。LLVMの魔法は最大の効果を発揮するだろう。

GNU/Linuxシステムで透過的にLLVMを使用するためには、no-asm付きで./configを実行する羽目になる。静的リンクライブラリはbitcodeとnative codeを同時に含むことができない。静的リンクライブラリはbitcodeとnative codeを同時に含むことはできる(というか含んでしまう)けれど、単純には正しくリンクしてくれない。アセンブリ言語でかりかりに高速化された暗号化にたちうちすることは、さしものLLVMにも難しいようだ。openssl speedで調べた限りでは。

いくつかの解決策があるだろう。

  • native codeからbitcodeへの変換
    参考: x86toLLVM?
  • インラインアセンブラへの変換
    参考: Module-Level Inline Assembly, Inline Assembler Expressions
  • LLVMの静的リンクライブラリの形式を変更する
  • bitcodeとnative codeで別々に静的リンクライブラリを分割生成
  • 正しくリンクしてくれるようにハックする。

前者ほど最適化の可能性が高く、透過的であり、しかし、実装が困難である。

2008-09-30

透過的なLLVM (6)

CentOS 4でLLVMを使いはじめた。__USE_EXTERN_INLINESマクロが定義されていると、GCCはインライン展開を押し進めるべく最適化ヘッダを利用する。/usr/include/stdio.hをひもとこう。

/* If we are compiling with optimizing read this file.  It contains
   several optimizing inline functions and macros.  */
#ifdef __USE_EXTERN_INLINES
# include <bits/stdio.h>
#endif
#if __USE_FORTIFY_LEVEL > 0 && !defined __cplusplus
# include <bits/stdio2.h>
#endif

/usr/include/bits/stdio.hはインライン展開を期待して、関数をいくつか定義する。たとえば、vprintfはvfprintfのラッパーだ。__STDIO_INLINEマクロはextern __inlineに展開されるだろう。

/* Write formatted output to stdout from argument list ARG.  */
__STDIO_INLINE int
vprintf (__const char *__restrict __fmt, _G_va_list __arg)
{
  return vfprintf (stdout, __fmt, __arg);
}

残念なお知らせがある。最適化オプションとともに-std=c99を与えると、LLVM-GCCはvprintfにインライン展開を期待してくれない。結果、/usr/include/stdio.hを利用する全てのコードにvprintfが偏在する。

LLVM-GCCはAppleのGCCを基に開発された。尋ねよ、さらば見出さん。-fno-gnu89-inlineがあるとLLVM-GCCのマニュアルは教えている。

This option is accepted by GCC versions 4.1.3 and up. In GCC versions /* APPLE LOCAL extern inline */ prior to 4.3 (4.2 for Apple's gcc), C99 inline semantics are not supported, and thus this option is effectively assumed to be present regardless of whether or not it is specified; the only effect of specifying it explicitly is to disable warnings about using inline functions in C99 mode. Likewise, the option -fno-gnu89-inline is not supported in versions of /* APPLE LOCAL extern inline */ GCC before 4.3 (4.2 for Apple's gcc). It is supported only in C99 or gnu99 mode, not in C89 or gnu89 mode.

もっとも、システムヘッダとコンパイラの歩調が合ってないことが問題なのだけれど。

2008-09-29

可搬なシェルスクリプト (2)

SUSv3は計算結果がnullか0でなければexprが0で終了すると規定している。

#! /bin/sh

set -e

output="-"

while getopts "ho:" opt; do
    case "$opt" in
        h)
            printf 'Usage: %s [ -h ] [ -o FILE ] [file...]\n' "$0"
            exit 1
            ;;
        o)
            output="$OPTARG"
            ;;
        ?)
            exit 1
            ;;
    esac
done

shift=`expr "$OPTIND" - "1"`
shift "$shift"

printf '[output=%s]\n' "$output"
printf '[%s]\n' "$@"

なにもオプションを指定しないと、シェルスクリプトは期待したように動作しない。shift=`expr "$OPTIND" - "1"`$OPTINDが1のときに0で終了する。set -eはコマンドが正常に終了しなかった場合にシェルスクリプトをただちに終了する。

shift=`expr "$OPTIND" - "1" || true`
shift "$shift"

塵理論

LLVM-GCCの出力結果を眺めていると、当然のようにアウト・オブ・オーダー最適化がなされていて、コンパイラ技術ってやっぱり非モテ技術でありつづけているんだ、と、その瞬間、フラッシュバック、X端末がずらりと並んだ窓のない部屋が襲いかかる。たとえ、iPhoneのクゥゥゥルを実現するためであったとしても、それはイーガンと同じくらいにしかクゥゥゥルでない(コンパイラ技術の因襲だもの、アップルのプレゼンテーションソフトを使ったところで非モテは解消されやしない)。まさに、というよりも、初手から塵理論はそういう話だった。アウト・オブ・オーダー実行がPentiumのハッタリだったように。

2008-09-27

透過的なLLVM (5)

そして、Apple (Computer,) Inc.はlibtoolを発明した。Apple libtoolのマニュアルに曰く、

Libtool with -static is intended to replace ar(5) and ranlib.

動的リンクライブラリはさておくとして、問題はMach-Oユニバーサルバイナリだ。もちろん、-Oで終わる単語(うん、たとえば、シロガネ-OとかPARANOIA-Oとか)はXで始まる単語と同じくらいろくでもないに決まっている。

The libtool command takes the specified input object files and creates a library for use with the link editor, ld(1). The library's name is specified by output (the argument to the -o flag). The input object files may be in any correct format that contains object files ("universal" files, archives, archives, object files). Libtool will not put any non-object input file into the output library (unlike ranlib, which allows this in the archives it operates on).

Mach-OユニバーサルバイナリはNeXTに由来する。CPUタイプ(とサブタイプ)ごとのオブジェクトファイルを合体してユニバーサルバイナリにする(うん、厳密にはオブジェクトファイルがそれぞれのアーキテクチャの機械語コードを内包しているかも)。合体は(釣りバカ日誌の)浜チャンにまかせておきたい。同居人はPowerPCのPowerBookを使いつづけているけれど、私はMacBook Proに移行して久しい(初期型なのでx86-64ではないCoreDuoだけど)。

ユニバーサルバイナリを捨てるとしたら、捨てたのだけれど、利点は、静的ライブラリの作成に際して、ranlibを忘れないことくらいだ(うん、それは実際、悪くはない)。動的ライブラリの作成に際しては、特にC++を使用する場合、使用するのだけれど、素直にg++ -dynamiclibを使用するほうがましだ(悪いのはlibstdc++だ)。

畢竟、Apple libtoolがなくても生きていけるし、生きていく資格なら初手から要らない。じゃあ、なぜ、と言えば、Boostが静的リンクライブラリをビルドするためにApple libtoolを使おうとするからだ。

2008-09-26

strcat

CURL 7.19.0をLLVM-GCC 4.2でコンパイルすると、test539が失敗する。LLVM-GCC固有の問題なのか、GCC 4.2の問題なのかは知らない。

標準は、char *strcat(char *restrict s1, const char *restrict s2);が第一引数s1の末尾に第二引数s2の内容をコピーしてからs1を返すと定めている。充分な最適化オプションを指定し、-fno-builtinを指定しなければ、GCCはstrcatをインライン展開するかもしれない。以下のコードはtests/libtest/lib539.cから抜粋した。

newURL = strcat (strcpy ((char*)malloc (strlen (URL) + 3),
                         URL),
                 "./");

新しく確保した領域にURL"./"を連結した文字列を保存する。newURLmallocで確保した領域の先頭のアドレスを指すだろう。懐かしさこそ感じれど、このコード自体にどうというところはない。

p = malloc(strlen(URL) + 3);
p = strcpy(p, URL);
newURL = strcat(p, "./");
assert(p == newURL);

標準はassertが成功することを保証する。LLVM-GCC 4.2はstrcatstrlenmemcpyに展開する。なにかがおかしくなる。展開の結果を擬似的にCで表現すると、

p = malloc(strlen (URL) + 3);
p = strcpy(p, URL);
newURL = p + strlen(p);
memcpy(newURL, "./", 3);
assert(p == newURL);

assertは成功しない。犯人は誰だろう。これはミステリではないので、strcatの返り値を信用しないようにコードを書き換えれば死人は甦るかもしれない。