システム · 0.1.4.6 から出荷
LibL
Loment の標準ライブラリのlibc の面である。libc と同じ名前、同じ 引数順、同じ戻り値の意味論 —— すでに C を知っている人のために。ひとつのライブラリの 第二の面であり、第二のライブラリではない。
0.1.4.6 から利用できる
0.1.4.6 から、リリースビルドは use libl を受け入れ、さらに
システムの本物の libc.so.6 をリンクして最後まで動かせる。0.1.4.5 でこの二つを
止めていた錠は開いており、リリースノートがそう述べている。
「利用できる」は「揃っている」ではない。ここにある名前はすべてシステムの glibc に対して判定され、欠けている組は読者がリンクの段で発見するのではなく、下に書かれて いる。このページは理由も残している。理由のない libc の面は、誰も監査できない約束だからだ。
なぜ第二の面か
標準ライブラリはすでに存在していたが、そこにはlibc の名前がひとつもなかった
—— strlen も、printf も、malloc も。それは省略では
なかった。ひとつの形であった。その関数は、終端文字を探すのではなく明示的な長さを取り、
バッファにはオフセットでアドレスし、可変個の引数を決して取らない。言語には NUL 終端文字列も
可変長引数関数もないので、ライブラリは言語のあるがままの形で書かれた。
その形は Loment を書く人には正しいが、C を知っている人には壁である。第二の面は別の
問いに答える: もしあなたが Loment を聞いたことがなかったら、これは何と
呼ばれるだろうか? C のものと同じものを返す strcmp。
qsort。isdigit。学ぶものは何もない。
二つの面は同じバイトを指す。isdigit はライブラリ自身の
文字判定を呼び、memcpy は自身のコピールーチンを呼ぶ。だから二つの面から
予想される破綻 —— 同じバイトが、どの名前で尋ねたかによって違う答えを返す —— は、
単に避けられているのではなく、構造的に不可能である。
二つの入り口
LibL はライブラリの名前であり、std は今もパッケージの名前である。
どちらの入り口も同じパッケージに届くが、どれだけを一緒に引き込むかが異なる。
use std
パッケージ全体。LibL より前に書かれたすべてがすでにそうしており、それはこれからも 動く。代償は、パッケージ全体の入り口がすべてのモジュールをひとつの単位に 引き込むことであり、この言語の出力シンボル空間は平坦である —— だからそれらすべての トップレベルの名前は互いに一意でなければならず、コンパイル時間はモジュール数より 速く増える。
use libl
四つの libc の面と、それらが依存するもの —— 六つのモジュール。小さな面である: 素早くコンパイルし、衝突するものがほとんどない。libc の名前だけが欲しかったときの 入り口はこれである。
何を覆うか
libc のヘッダひとつがモジュールひとつに対応し、その対応そのものが libc に従うことの 一部である。
<string.h>
strlen, strcpy, strncpy, strcat,
strcmp, strncmp, strchr, strrchr,
strstr, strspn, strcspn, strpbrk,
strtok_r, memchr, memcmp, memcpy,
memmove, memset.
<ctype.h>
isdigit, isalpha, isalnum,
isspace, isupper, islower,
isxdigit, ispunct, isprint, そしてその集合の
残りすべて、toupper と tolower を伴って。
<stdlib.h>
ヒープなしで作れる半分: atoi, atol,
strtol, strtoul, abs, labs,
llabs, qsort, bsearch.
<stdio.h>
書式の半分: snprintf、sprintf、そして残りの
printf 書式。ファイルの半分は 0.1.4.6 に別の host モジュール
として到来した —— fopen、fclose、fread、
fwrite、fseek、ftell、fflush、
feof、fgets、fputs、puts、
putchar、getchar —— これらはシステムコールを必要とし、
コアライブラリは一つも発しないからだ。
加えて、libc が持っていない関数がひとつ。そしてそれこそが面白い —— 下を見よ。
対照物は libc 自身である
標準ライブラリの他のあらゆる部分は、Loment が計算するものを Python が計算するものと
比べて判定される。それはこの問いにとって間違った対照物であり、証明可能にそうである:
「libc のように振る舞う」を定義するまさにその縁 —— strncpy がゼロ埋めするか、
strcmp が符号を返すか差を返すか、strchr が終端文字を
見つけるか、範囲が重なったとき memmove が安全か —— において、Python と libc は
同じ答えではない。Python と比べれば、二つの実装が一緒に間違っていても通ってしまう。
そこで判定基準は、システムの実物の glibc に対して C プログラムをコンパイルし、両側で同じ
呼び出しの組を走らせ、標準出力をバイト単位で比較し、最初に異なる行を報告する。
二つの正規化は意図的である。さもなければテストは意味論ではなく実装の細部を測ることになる
からだ: 標準は strcmp の符号しか約束しないので、符号と差の両方を検査し;
glibc の文字判定は 1 ではなくビットマスクを返すので、比較の前に両側を yes-or-no に
還元する。
言語が強いたもの
この作業の規則は「libc がどう実装していようと、LibL はその通りに実装する」であった。 言語は C ではないので、四か所は異ならざるを得ず、ひとつは加えざるを得なかった。それぞれは、 隠されるのではなく書き留められている。
文字列リテラルは終端文字を運ばない
だからリテラルに対する strlen は末尾を越えて読む。最初の探針では、空文字列に
対して17 を返した —— たまたまゼロバイトに出会うまで走り続けたのである。
C では "abc" がすでに自分の NUL を運んでいるので、このための関数は要らない。
これは省略ではない —— 二つの str 型は同じものではない:
この言語のものは長さを運び、C のものは運ばない。LibL は一方をもう一方へ変換するために
cstr_of を加える。ライブラリのすべての NUL 文字列関数はそれを必要とする。
可変長引数関数はない
だから snprintf は libc のものより引数をひとつ多く取る:
snprintf(buf, cap, fmt, args, nargs)、ここで args は
64 ビット値の配列を指す。変換指定子は libc とまったく同じく、それぞれをどう読むかを
決める。これはライブラリで唯一食い違うシグネチャである、そしてそれは
純粋な損失ではない: libc は自分の引数を数えられないので、少なすぎるとスタック上にある
ものを読んでしまう; ここでは、少なすぎれば変換が止まり、それまでに作られたものが
正しく仕上げられる。
関数ポインタはない
だから qsort の第四引数は、比較関数ではなく、何で比較するかを述べる列挙で
ある。それは実際の違いであり、一方方向への格下げではない: 「このソートは何で比較するか」
を実行時のアドレスから成果物の中の定数へと移すので、ソースを読まずに
答えられる。代償も一緒に述べられている: 組み込みの種類のどれでもない
順序は表現できない —— ループは自分で書く。
モジュールレベルの可変グローバルはない
だから strtok は strtok_r になり、カーソルは呼び出し側が
持つ —— これは POSIX がすでに提供しているので、格下げではない。隠れた種を保つ
rand も存在できない; この言語が使う形は呼び出し側から状態を取るのであり、
意図的に rand とは名付けられていない。そして errno も
ない: strtol は境界値へ飽和して正常に戻るので、呼び出し側は代わりに
境界と比較する。
何ではないか
libc の置き換えではない
LibL はソースライブラリであり、C のオブジェクトファイルがリンクできる
アーカイブではない。C のコードからそれを呼ぶことはできない; それには「Loment から
オブジェクトファイルへ」の道が要るが、それは存在しない。置き換えるものはもっと狭く、
正確に言う価値がある: Loment のプログラムが strcmp を手書き
しなければならないこと、である。
libc の全体ではない
realloc と free は意図的に欠けている: realloc
は古い長さを必要とし、その古い長さはコンパイラ私有のヒープヘッダにある。
free の名前はすでに組み込みが取っているので、ライブラリに同名の関数を
置いても呼び出し地点で解決されない。malloc と calloc は
0.1.4.6 からある —— 組み込みヒープへの綴りで、自前の長さヘッダを持たない。だから
組み込みの free が、それらが返したものを受け取れる。stdin、
stdout、stderr はモジュールレベルの可変グローバルになるが、
言語にそれはない。プロセスや環境の一族では、exit と abort
以外は一つもない。そして %f/%e/%g は「未実装」
ではなく「まだできない」である: 上流のフォーマッタはソフトウェア浮動小数点で、その逆方向は
この層が意図的に持たない広い除算を必要とする。
完成しておらず、そう装ってもいない
それを書くことで、周囲のシステムの欠陥が露わになった —— その中には、重なりの検査で libc がアドレスを比べるところをオフセットで比べてしまうヘルパー、システムのアセンブラが 拒む IR を出すフォーマッタ、そして間違ったモジュールを責める依存検査がある。それらは 通りがかりに直されるのではなく、イシューとして起票されている。そしてライブラリ自身の 判定基準は意図的に狭い: これらのモジュールが正しい単位であることは主張するが、それらの 依存がそうであることは主張しない。