2008-10-27

透過的なLLVM (10)

前回のハックは美しくなかった。Lame 3.98.2ではハックが働いたけれど、x264ではうまく働かなかった。問題は、畢竟、アーカイブファイルにオブジェクトファイルの完全パスが含まれていることにほかならないのだけれど、手を抜いたのは私の責任だ。カレントディレクトリへのシンボリックリンクと言わず、ディレクトリを作成するほうが美しく、汎用性があった。

#! /bin/sh

set -e

case "$1" in
    *x*)
        for i in `llvm-ar tv "$2" | awk '{ printf " %s", $9 }'`; do
            d=`dirname "$i"`
            mkdir -p "$d"
        done
        ;;
esac
llvm-ar "$@"

当初はカレントディレクトリにオブジェクトファイルを集めることに固執していたんだけど、どうやら、その必要性はないらしい。

2008-10-24

透過的なLLVM (9)

SUSv3はarのx命令を規定しており、llvm-arもx命令を提供する。アーカイブファイルからオブジェクトファイルを抽出するという機能は共通だが、共通の動作は期待できないかもしれない。llvm-arはfモディファイヤを与えない限り、オブジェクトファイルの完全パスをアーカイブファイルに保存する。x命令は、保存された完全パスにオブジェクトファイルを書き出そうとする。ディレクトリが存在していない場合、抽出は失敗する。

Lame 3.98.2はビルドにx命令を使用する。libmpgdecoder.aとliblamevectorroutines.aからオブジェクトファイルを抽出して、libmp3lame/.libs/libmp3lame.0.0.0.{a,dylib}にまとめる(実際の仕事はlibtoolシェルスクリプトが行う)。

$ llvm-ar tv mpglib/.libs/libmpgdecoder.a
brw-r--r--  501/  20     6508 Oct 23 09:23:37 2008 .libs/common.o
brw-r--r--  501/  20     8116 Oct 23 09:23:38 2008 .libs/dct64_i386.o
brw-r--r--  501/  20    15224 Oct 23 09:23:39 2008 .libs/decode_i386.o
brw-r--r--  501/  20    18900 Oct 23 09:23:40 2008 .libs/interface.o
brw-r--r--  501/  20     6344 Oct 23 09:23:40 2008 .libs/layer1.o
brw-r--r--  501/  20    12296 Oct 23 09:23:41 2008 .libs/layer2.o
brw-r--r--  501/  20    74804 Oct 23 09:23:43 2008 .libs/layer3.o
brw-r--r--  501/  20     5284 Oct 23 09:23:44 2008 .libs/tabinit.o
$ llvm-ar tv libmp3lame/vector/.libs/liblamevectorroutines.a
brw-r--r--  501/  20     1932 Oct 23 09:23:46 2008 .libs/xmm_quantize_sub.o

xmm_quantize_sub.oは18文字あるので、アーカイブファイルの作成時にfモディファイヤをつけて対処することはできない。簡単な解決策は美しくないハックだ。x命令の実行前にカレントディレクトリへのシンボリックリンクを作成する。

#! /bin/sh

set -e

case "$1" in
    *x*)
        for i in `llvm-ar tv "$2" | awk '{ printf " %s", $9 }'`; do
            ifs="$IFS"
            IFS="/"
            for d in `dirname "$i"`; do
                if [ -f "$d" ] || [ -d "$d" ] || [ -h "$d" ]; then
                    :
                else
                    ln "-s" "." "$d"
                fi
            done
            IFS="$ifs"
        done
        ;;
esac
llvm-ar "$@"

第一引数が命令とモディファイヤで、第二引数がアーカイブファイルであるならば、シェルスクリプトはほとんどの場合うまく働き、期待した動作を行う。嫌がらせみたいなオブジェクトファイルのパスを含む場合は、もちろん無理だけど。

2008-10-16

変奏と反復 (3a)

変奏なのだ、これは。

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

いまもって。いまなお。

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

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

2008-10-14

変奏と反復 (2)

ヴァリエーション、いくたびの。図書館の庭、通りからは目立たない広場。カップ酒をあおり、おじさんは故郷の話を繰り返した。「兄ちゃんがよう、偉くなってよう、おれらみたいなのをよう」イテレーション、いくたびも。なしくずしの二十一世紀、マンガ喫茶と個室ビデオ屋は相似形の双生児、忘れないことだけを綱領に、時間と空間が混ぜこぜになった都市を、速度だけを武器として。

透過的なLLVM (8)

前回は完膚なきまでに事実を誤認していた。llvm-arはbitcodeとnative codeを同時に含む。llvm-arのマニュアルに曰く、

t[v]
Print the table of contents. Without any modifiers, this operation just prints the names of the members to the standard output. With the v modifier, llvm-ar also prints out the file type (B=bitcode, Z=compressed, S=symbol table, blank=regular file), the permission mode, the owner and group, the size, and the date. If any files are specified, the listing is only for those files. If no files are specified, the table of contents for the whole archive is printed.

t命令にvモディファイヤを付けると、たとえばこんな出力が得られる。

 rw-r--r--  501/  20      636 Oct 14 10:46:35 2008 bar0.o
 rw-r--r--  501/  20      544 Oct 14 10:46:35 2008 bar1.o
brw-r--r--  501/  20      544 Oct 14 10:46:35 2008 foo0.o
brw-r--r--  501/  20      540 Oct 14 10:46:35 2008 foo1.o

foo0.oとfoo1.oがbitcodeのオブジェクトファイルで、bar0.oとbar1.oがnative codeのオブジェクトファイルだ。x命令を使うと、アーカイブファイルからオブジェクトファイルを抽出できる。

x[oP]
Extract archive members back to files. The o modifier applies to this operation. This operation retrieves the indicated files from the archive and writes them back to the operating system's file system. If no files are specified, the entire archive is extract.

だけど、llvm-ldは、bitcodeとnative codeの両方を含むアーカイブファイルを良きにはからってくれない(tools/llvm-ld/llvm-ld.cppを読むとItemsNativeLinkItemsに二分しちゃってる)。だったら、x命令を使用してオブジェクトファイルを抽出して、bitcodeだけからなるアーカイブファイルとnative codeだけからなるアーカイブファイルを作成しよう。下記はコンセプトを実証するためのもので、オブジェクトファイルがディレクトリ階層を持っている場合に破綻する(かもしれない)。

#! /bin/sh

set -e

file="$1"
shift

b_objs=`llvm-ar tv "$file" | awk '/^b/ { printf " %s", $9}'`
n_objs=`llvm-ar tv "$file" | awk '/^ / { printf " %s", $9}'`

d=`dirname "$file"`
s=`printf '%s' "$file" | sed -e 's/.*\(\..*\)$/\1/'`
n=`basename "$file" "$s"`

cd "$d"
mkdir "-p" "$n-o"
cd "$n-o"
llvm-ar "xo" "../$n$s"
llvm-ar "rc" "$n-b$s" $b_objs
ar "rc" "$n-n$s" $n_objs
mv "$n-b$s" "$n-n$s" ".."
cd ..
rm "-fr" "$n-o"

話はほとんど、これでおしまい。ただし、native codeがbitcodeのシンボルを参照する場合(多くの場合参照する、そもそも、参照しなかったらそのコードに意味がない)、できあがったアーカイブファイルをリンクする際に-disble-internalizeの付与を検討するべきだ。すべてのbitcodeが先にリンクされ(願わくば最適化され)、その後native codeがリンクされる。最適化はnative codeが参照しているシンボルをどっかにやっちゃうかもしれない(native codeからしか参照されていなかったら、たぶんどっかにやっちゃう)。

2008-10-10

変奏と反復 (1c)

ビデオライブラリーのブースで『カサブランカ』が再生しながら、彼はロディアのレポートパッドに論文を書きあぐねている。主題はあった。かかる主題のために『摩利と新吾』を読んだのだ。けっして髪を切った少女のためではなく。隣のブースで港湾労働者のおじさんがクロサワを見ている。自治体が運営する図書館は、当該自治体に居住するか、当該自治体に学校や職場がある者にサービスを提供する。上階の自習室は受験勉強に勤しむ若者たちに占拠された。本棚と本棚の間の机は良き市民たちが頑としてゆずらない。だけど、おじさんたちが鍵括弧なしの横浜で働いていることは間違いない。闘争は図書館内に酒を持ちこまないことで決着し、地階のビデオライブラリーが勝ちとられた。

「兄ちゃん、」と、おじさんは言った。飛行機が飛びたつのをおじさんは辛抱強く待っていた。クロード・レインズがヴィシー水をゴミ箱に放る。「酒をさ」
「そんなにお金ないから、みんなのぶんはないよ」
「ああ、うん、うん、いや、いいんだ」
彼はポケットに移しておいた千円札をおじさんに渡す。そのころの彼に、千円は大金だった。だけど財布にマンガを買うためのお金は残っていたし、少女と元町のマクドナルドでハンバーガーを食べるためのお金は鞄の底に隠してあった。
「いや、どうも、ありがとう」
その顔に浮かんだ表情から眼をそらさないように、彼は歯をくいしばる。おじさんはデッキからビデオを取り出し、返却カウンターに向かう。鉛筆を取りあげ、レポートパッドに「ゆるやかな」と書く。二重線でそれを消し、「無限にひきのばされた」と書く。

2008-10-08

変奏と反復 (1b)

柳瀬尚紀版の『フィネガンズ・ウェイク』を箱に入れ、いちばん上の棚に戻す。店頭のワゴンからムックを一冊択んでカウンターに持っていく。
「立ち読みするなら、図書館で借りりゃいいでないの」
と、左翼運動家くずれの店主がためいきをつく。
「たとえ飾られてあることがその本の目的だったとしても、」
ほとんどささやきに近い彼の言葉に、店主は眉を持ちあげる。
「最後の人民戦線たるぼくはそんなプチブル迎合主義を許さない」
「プチブルはおまえだよ」
「たこにも。だから、こうして、売り上げに貢献しようとしている」
「煙草銭にもなりゃしねえ」
一九八九年以前、一九八九年以前の状況を大正時代末期になぞらえたアンソロジーが幾冊も出版された。右も左も上も下も、長すぎた昭和に決着をつけあぐね、状況は整理されすぎていった。Xデイを待ち続け、Xデイを待ち望み、Xデイを待つことに飽き、ようやく訪れたXデイは、もちろん左翼運動家たちが望んだようなXデイではなかったのだけれど、Xに代入するべきなにものかこそが失われたものだったと気づいたときには九〇年代が始まっていた。
「小さいとはいえ立派な資本家がそんなこと言っちゃだめじゃない。金に色はないって言うよ」
「うるせえ。そんな本ばっかり読んでたら莫迦になるぞ。セットで『球根栽培法』はいかがですか」
「二重に権力の謀略じゃない、それって」
「小さいとはいえ立派な資本家だからな。バブルのころだって土地売らずにがんばったんだぜ」
「裏本売って?」
肩をすくめて、わざとらしく壁の時計に視線をやり、
「良い子のおぼっちゃんは図書館行って勉強でもする時間だ」
彼が取り出した百円玉がカウンターにぶつかってくぐもった音を鳴らした。ボストンバッグに本をしまい、彼は古本屋を出た。ウインズから桜木町に向かう人の流れにさからい、仏壇屋の並ぶ坂をのぼっていった。

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の返り値を信用しないようにコードを書き換えれば死人は甦るかもしれない。

2008-09-24

柔らかい現実時間 (2)

多数の非同期入力を読んで、データをこねこねして、多数の非同期出力に書きだす。データをこねこねする処理を定期的に行う。伝統的で先進的なサーバアプリケーションが直面している問題は、だいたいこのようなものだと断定して間違いなかろう(たとえば、AJAXだったり、メディア合成だったり、ネットワークゲームだったり)。

そもそも、負荷が高かったら現実時間なんて望むらくもない。それでもしばしば望むのだ。負荷が高い場合は特定の処理を優先してほしいと。望んだことを叶えるべく、ウンコな並列処理機構のウンコなオプションを端から試す。よくよく考え直そう。データセンタに走っていって、新しいマシンをぶちこむほうが先だ。

負荷がほどほどだったら、システムが望んだ時間間隔で処理の機会を与えてくれることを期待して良い、わけはない。総体としてのサーバアプリケーションはクライアントアプリケーションより資源を管理しやすいけれど、リアルタイムOSを使わないのならば(使うの?)、完全に資源を管理できはしない(言うまでもない。Linuxカーネル 2.6はリアルタイム性を大幅に改善したけれど)。

望んだ定期的な時間間隔の二分の一の時間間隔で機会を与えてくれるようにとシステムに頼むことにしよう(二分の一はサンプリング定理を盲目的に適用しただけで、どんな時間間隔も気の向くままに)。機会を与えられるたび、現在時刻を調べて定期的に処理するべき処理を行う。

システムにお願いする方法はどうしよう。現実的に言えば、非同期入出力のイベントキュー(つまり、selectとかpollとか、/dev/pollとかkqueueとかepollとか)のタイムアウトの精度で充分だ。非同期入出力が本当に多数だったら、イベントキューはタイムアウトしている余裕はなく、機会はもっとたくさん与えられるだろう。シグナルベースのタイマを使用する場合でも、再入可能性の厄介を避けるためにセルフパイプを使うならば、結局、イベントキューに帰着する。

2008-09-22

柔らかい現実時間 (1)

Google ChromeがtimeBeginPeriod(1)を呼び出していたって知ったことじゃない。そもそも、それを捨てたところから二十一世紀は始まったのだ。もちろん、ぼくにとっての。

Cの並列処理機構はウンコで、ウンコを継承したさまざまなプログラミング言語の並列処理機構はウンコだった。結局、最適な時分割と最適な優先度付与はしばしばプログラマ自身の仕事だった。そのプログラマがぼくでなかったらよいのにと思わなかったといえば嘘になる。

だれもが最初に望み、最初に試すだろう。目覚まし時計が正確に悪魔を召還することを。悪魔が忙しくなければ、窓から少女が(言うまでもない、おさななじみの少女が)転がりこんでくるかもしれない。悪魔は、しかし、たいていとても忙しい。畢竟、昔ながらの方法、忙しい繰り返しが使われる。目覚まし時計を見て、少し眠って、また目覚まし時計を見て、ついに慌てて飛び起きるのにそれは似ている。目覚まし時計は鳴らないのだ。

直感的ではないひとつの原因は、言うまでもない、時計の精度とタイマの精度にある。

2008-09-16

本物の配列

シェルスクリプトにおいてしばしば、$IFSevalを利用して疑似配列を扱う。疑似配列は疑似精液と同じくらい疑似なので、つまり疑似本番と同じくらい疑似である。だけど、本物の配列はかつてあり、ありつづけている。

#! /bin/sh

set -e

f() {
    if [ "$1" -eq "$2" ]; then
        return
    fi

    i="$1"
    s="$2"
    v="$3"
    shift 3

    printf '[%d:%s]\n' "$i" "$v"
    f `expr "$i" + 1` "$s" "$@" "$v"
}

f "0" "$#" "$@"

配列でなくリストというべきかもしれない(それとこれとどれほど違うだろう)。望むなら、execで末尾再帰、つまり、ほぼ無限が手に入る。そもそも、なんの役にたつのかと問う向きには応えよう。"$@"は本物の局所変数でもあるのだ。

2008-09-10

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

すべてのシステムがSUSv3に準拠しているわけはなく、すべてのシェルがSUSv3に準拠しているわけはなく、シェルスクリプトはシェルとユーティリティの混淆で、どちらが玉でどちらが石か判別できるわけはない。

Linux GNU/SystemsはBourne Again Shellを/bin/shに使うかもしれない(Ubutuはそうじゃない)。BSD系列は(Almquist Shellを含む)パブリックドメインのKorn Shellを使うかもしれない。Mac OS XはバージョンによってZ Shellを使ったりBoune Again Shellを使ったりする(ウンコ!)。SolarisはThe Heirloom Bounre Shellなる黙示的な名前をひりだした(ぶりぶりっ!)。

GNU Autoconfのマニュアルに曰く、

So while most modern systems do have a shell somewhere that meets the POSIX standard, the challenge is to find it.

The Heirloom Bourne Shellで動作すれば、現代的なシステムではだいたい動作するだろうと期待してもそれほど間違いではない。シェルの問題はユーティリティに押しつけられ、問題は面倒になるばかりだけれど。たとえば算術演算は$((expression))でなく`expr expression || true`と書かなければならない。

2008-09-09

透過的なLLVM (4)

静的リンクライブラリはLLVMの世界のなかにある。llvm-arとllvm-ranlibがそれを扱う。llvm-ldは動的リンクライブラリをリンクする(bitcodeで表現された静的リンクライブラリが見つからなかったら)。現代的なオペレーティングシステムにおいて、動的リンクライブラリが実行可能バイナリとほとんど同義であるとしたら、動的リンクライブラリはnative codeで表現されるだろう。

llvm-gcc -emit-llvm -Wall -O2 -g -c foo.c
llvm-gcc -emit-llvm -Wall -O2 -g -c bar.c
llvm-ld -link-as-library foo.o bar.o -o libfoobar.bc
llc libfoobar.bc -f -o libfoobar.s
gcc -Wall -O2 -g -c libfoobar.s
libtool -dynamic libfoobar.o -o libfoobar.dylib

libtoolはldを呼び出す(もちろんlibtoolはGNU libtoolではない)。自明なオプションとアーキテクチャ関連のオプションは省略しよう。

ld -dylib -ldylib1.o libfoobar.dylib.o -o libfoobar.dylib

ldには既にフックがかけてある。解は単純だ。ldに-dylibオプションが与えられていたら、入力をnative codeに変換してからシステムのldを呼び出せばいい。

透過的なLLVM (3)

GNU Octaveのマニュアルに曰く、

On NeXT systems, if you get errors like this:

/usr/tmp/cc007458.s:unknown:Undefined local symbol LBB7656
/usr/tmp/cc007458.s:unknown:Undefined local symbol LBE7656

when compiling Array.cc and Matrix.cc, try recompiling these files without -g.

いやはや、Mac OS X 10.5 (darwin9) においておや、おやおや。フリーなソフトウェアやオープンなソフトウェアをかたっぱしからLLVM-GCCでコンパイルしていくと、アセンブラがときどき「unknown:Undefined local symbol L...」と言い出す(うん、厳密に言うとLLVM-GCCは関係なくて、llvm-ldが呼び出すMax OS Xのアセンブラが文句を言うんだ)。GNU Octaveのマニュアルは-gを付けずにコンパイルしなおせと教えているが、llvm-ldに-strip-debugを与えても同じことだ(すすめはしないけれど-disable-inliningでもいい)。

デバッグ情報の、これは夜の物語である。デバッグ情報の霧についてはさておいて、動的リンクの闇の物語を語ることにしよう。

2008-09-08

サラリーマンは気楽な稼業ときたもんだ

植木等が死んだのはぼくの誕生日の前日だった。植木等が死んでもう一年になる。二十一世紀、サラリーマンが気楽な稼業なわけはない。背負わなくてもよいものまで背負ってしまうぼくは、背負うべきもののことを忘れて生きている。

誰かが倒れるたびに、理由がメンタル不健全であるたびに、ぼくの煙草は二本ずつ増えていく。救うなんておこがましい行為、神様にしか許されていないけれど、神様はもう長いこと留守にしていて、忘れないことだけを綱領として、しかし二十一世紀は一貫しつづけるには長すぎる。

情報処理技術のみならず、経営の技術が、金融の技術が、ありとあらゆる技術が、完膚なく不可逆に人類を変容させつつある。変容は非人間的だから保守と反動は叛旗を翻す。変容は、しかし、まったく不可逆なので、二十一世紀、サラリーマンは気楽な稼業に返り咲きえない。

それは、グローバリズムではない。

これは、弔辞だ。しかし、いったい誰への?

SQLITE_INT_TO_PTR(X)

SQLite 3.6.2をLLVM-GCC 4.2でコンパイルすると、整数からポインタへのキャストが定数値でないというエラーが発生する。LLVM-GCC固有の問題なのか、GCC 4.2の問題なのかは知らない。正確に言うと問題ですらない。

sqlite3.c: In function 'sqlite3RegisterDateTimeFunctions':
sqlite3.c:11837: error: initializer element is not constant
sqlite3.c:11837: error: (near initialization for 'aDateTimeFuncs[0].pUserData')

コンパイル時定数でない値を配列の初期化に使用していると不平を述べている。

  • sqlite/src/sqliteInt.h
    • FuncDef構造体の配列を用いてSQL関数を表現する。
    • FuncDef構造体を作成するためにFUNCTIONマクロを定義する。
    • FUNCTIONマクロはSQLITE_INT_TO_PTRマクロを利用して整数をvoid*に変換する。

かつて整数とポインタは同義だったかもしれない。そんな昔のことは憶えていないし、そんな先のことは判らない。

#define SQLITE_INT_TO_PTR(X)   ((void*)&((char*)0)[X])
#define SQLITE_PTR_TO_INT(X)   ((int)(((char*)X)-(char*)0))

コンパイル時定数にするかどうかはコンパイラの自由なので、SQLITE_INT_TO_PTR(X)はコンパイル時定数にならないかもしれない。SQLiteの名誉のために付け加えると、配列の初期化に使用している箇所ではX0から3までのリテラルだ(SQLITE_OMIT_DATETIME_FUNCSマクロが定義されていない限り)。宗教論争に加わるには人生は短すぎる。整数が何ビットだろうと、ポインタが何ビットだろうと、はたまた関数ポインタが何ビットだろうと知ったこっちゃない。

#define SQLITE_INT_TO_PTR(X)   ((void*)(X))

2008-09-05

透過的なLLVM (2)

四行詩を諳んじるにはまだ遠い。

./configure
make
make check
make install

前回、ラッパーとなるシェルスクリプトを介してllvm-ldを呼び出すことで、llvm-gccをほとんどgccとして扱えることを示した。こうして、いつでもllvm-gccに-emit-llvmを与えられるようになった。もちろん、gccをllvm-gccへのシンボリックリンクに設定するべきではない。コンパイラドライバとしてのllvm-gccは、アセンブラとしてのgccを必要とする。llvm-gccを呼び出す前に、自分自身が存在するディレクトリを$PATHから削除すれば問題はほとんど解決する。

そもそもの目的に照らせば、いつでもllvm-gccに-emit-llvmを与えることは必然だ。llvm-ldを呼び出す際にどのようなオプションを与えるかは考慮に値する。考慮するべきオプションは、-disable-optと-nativeだ。なにも指定しなければ、llvm-ldは可能な限りの最適化を試みる。ひとつの問題がある。

char function();
char (*f)() = function;

int main() {
    return f != function;
}

関数の存在を調べるためにconfigureが(つまりAutoconfのAC_CHECK_FUNCSマクロが)生成したコードだ。リンクに成功すればfunctionが外部に存在していると認識される。なにも指定しなければ、llvm-ldは可能な限りの最適化を試みる。ffunctionが等しいことを見抜き、f != functionは偽であることを見抜く。

int main() {
    return 0;
}

リンクは常に成功する。畢竟、functionは存在する。ひとつの教訓は、configureの実行中、llvm-ldに-disable-optを与えることだ。

-nativeまたは-native-cbeを指定しなければ、llvm-ldはbitcodeとそれを実行するためのシェルスクリプトを生成する。ひとつの問題は動的リンクライブラリのリンクが動的に行われることで、最適化と同様の問題が発生する。さらに、make installがぼくたちの望んだように働かない(シェルスクリプトだけをインストールしてくれちゃう)。

目的に立ち返ろう。ぼくたちのC++のコードがリンクする全ての静的ライブラリをbitcodeで表現すること、最後の最後に最適化をかけることが目的だ。最後の最後が永遠に続くとしても、それはぼくたちのC++のコードの話だ。欲しいのはbitcodeで表現された図書館であって、bitcodeで表現された実行可能バイナリではない。

ふたつめの教訓は、四行詩を諳んじている間、常にllvm-ldに-nativeを与えることだ(ただし副作用はある。-g付きでコンパイルした場合、dsymutilがオブジェクトファイルからDWARFデバッグ情報を抽出しようとするかもしれない)。

2008-09-04

透過的なLLVM (1)

自由なUNIXに似たシステムのパッケージシステムは、Autotoolsの分厚い皮がすこし日焼けしたようなものだ。魔法の四行詩(たいていの場合、make checkは抜かすだろうけれど)を諳んじるのだ。

./configure
make
make check
make install

この四行はUNIXの範疇にある。shが実行され、makeが実行される。それらが呼び出すコマンドがUNIXの範疇にあるかどうかは保証されない。SUSv3が定義するCコンパイラはc99だけど、それが存在するかどうかは判らない(自由なUNIXに似たシステムはUNIXじゃないからね)。

目標は、畢竟、ぼくたちのC++のコードがリンクする全ての静的ライブラリをbitcodeで表現すること、最後の最後に最適化をかけることだ。実際問題、ユーザランドで動作する使い勝手の良いライブラリ、特に様々なメディアを扱うライブラリはLLVMの恩恵を受けやすい。C++のライブラリがヘッダー主体になって久しいが、それだって内部では昔ながらのCのライブラリを呼び出している。

makeと呪いを唱える。現実に即し、乱暴に述べるなら、ほとんどの場合、GNU Compiler Collectionが呼び出される。GNU Compiler Collectionは魔術的にリンカを呼び出す。

gcc -Wall -O2 -g -c foo.c
gcc -Wall -O2 -g -c bar.c
ar rc libfoobar.a foo.o bar.o
ranlib libfoobar.a
gcc -Wall -O2 -g baz.c -L. -lfoobar -lm -o baz

人工的だけど典型的で例だ(実際、bzip2のMakefileはこんな感じだ)。-vオプションを与えると、gccは内部で呼び出すコマンドを表示する。本当に呼び出しているコマンドを知るためにはdtrace(あるいは他のなにか)を使用する。Mac OS X 10.5では/usr/bin/gccは/usr/bin/gcc-4.0のシンボリックリンクだ。最後の一行を実行すると、コンパイルのためにcc1とasが、リンクのためにcollect2そしてldが呼び出される。

  • /usr/bin/gcc-4.0
    • /usr/bin/i686-apple-darwin9-gcc-4.0.1
      • /usr/libexec/gcc/i686-apple-darwin9/4.0.1/cc1
      • /usr/libexec/gcc/i686-apple-darwin9/4.0.1/as
      • /usr/libexec/gcc/i686-apple-darwin9/4.0.1/collect2
        • /usr/libexec/gcc/i686-apple-darwin9/4.0.1/ld

LLVMが透過的だとしたら、接頭辞を加えてコマンドを実行すれば全てがうまくいくだろう(もちろん、そう、-emit-llvmを忘れなければ)。そしてbitcodeが生成されるだろう。

llvm-gcc -emit-llvm -Wall -O2 -g -c foo.c
llvm-gcc -emit-llvm -Wall -O2 -g -c bar.c
llvm-ar rc libfoobar.a foo.o bar.o
llvm-ranlib libfoobar.a
llvm-gcc -emit-llvm -Wall baz.c -L. -lfoobar -lm -o baz

はかない望みである。はかなさの理由は、コンパイルとリンクを合わせて行うことがllvmの流儀に反しているからだと説明できるかもしれない。llvm-gccとllvm-ld(またはllvm-link)を別々に実行すれば問題は解決する。bitcodeとそれを実行するためのシェルスクリプトが生成される。

llvm-gcc -Wall -emit-llvm -c baz.c
llvm-ld baz.o -L. -lfoobar -lm -o baz

流儀云々は窓から投げ捨てよう。collect2は$PATHからldを決定する(実際には、もっと面倒な決定方法を経る)。特に設定していなければシステムのldを見つける。システムのldが望みを叶えてくれるわけはない。

  • $LLVM_GCC_PREFIX/bin/llvm-gcc
    • $LLVM_GCC_PREFIX/libexec/gcc/i686-apple-darwin9/4.2.1/cc1
    • $LLVM_GCC_PREFIX/libexec/gcc/i686-apple-darwin9/4.2.1/collect2
      • /usr/bin/ld

ldをllvm-ldへのシンボリックリンクに設定すれば、llvm-ldが呼び出されるようになる。しかし、collect2がldに渡す引数のなかにはllvm-ldが解釈できないものがある。典型的には-emit-llvmを扱えないし、Mac OS Xの拡張オプションや癖のあるlibc周りはうまく扱えない(なんだか間尺に合わない話だ)。厳密さを追い求めなければ、対処は難しくない。扱えない引数を削除した後、llvm-ldを呼び出せばいい。

2008-09-02

PDFを画像に変換する

手元にPDF 1.7のファイルがある。各ページを連番画像として出力したい。2006年、PDF 1.7はAdobe Acrobat 8とともに世に出た。それから、2年が経った。

かつてポストスクリプトプリンターは高価だった。今もって高価でありつづけている。高価でないポストスクリプトプリンターはうんこでありつづけている。

手元の(正確に言えばデータセンターの)GNU Linux/Systemsをのぞいてみる。CentOS 4にはGNU Ghostscript 7.07が、CentOS 5にはESP Ghostscript 8.15.2がインストールされていた。GNU版かESP版か、議論を闘わせた夜もあった。どちらにしてもPDF 1.7を解釈するには古すぎる。GNU Ghostscript 8.63をビルドした。

Ghostscriptのビルドは、魔術的なフォントの設定とほとんど同義でありつづけている。かつてフォントは本当に高価で、貧乏人は黒魔術でそれと対峙した。それとはいったいなんだったのだろう?

いつしか高価でないOpenTypeフォントがOSに付属するようになり、高価なOpenTypeフォントを恋人にプレゼントできるくらいに歳をとった。TeXを使わなくなった。にもかかわらず。

GNU Ghostscript 8.63は、無事にPDF 1.7を解釈した。