Julia ランタイムの printf() と stdio
stdio に対する libuv のラッパー
julia.h は stdio.h のストリームに対する libuv ラッパーを定義します:
uv_stream_t *JL_STDIN;
uv_stream_t *JL_STDOUT;
uv_stream_t *JL_STDERR;
このラッパーに対応する出力関数も定義されます:
int jl_printf(uv_stream_t *s, const char *format, ...);
int jl_vprintf(uv_stream_t *s, const char *format, va_list args);
これらの printf 関数は src/ と ui/ にある .c ファイルで、出力のバッファリングが統一した方法で行われることを保証するために stdio が必要になったときに使われます。
完全な libuv インフラを使ったのでは時間がかかり過ぎる特殊な状況 (シグナルハンドラなど) では、STDERR_FILENO に直接 write(2) する jl_safe_printf() を使うことができます:
void jl_safe_printf(const char *str, ...);
JL_STD* と Julia コードのインターフェース
Base.stdin, Base.stdout, Base.stderr はランタイムで定義される上述の libuv ストリーム JL_STD* に束縛されます。
base/sysimg.jl に含まれる Julia 関数 __init__() は Base.stdin, Base.stdout, Base.stderr に対応する Julia オブジェクトを作成します。
reinit_stdio() が ccall を使って JL_STD* へのポインタを取得し、jl_uv_handle_type() を呼んで各ストリームの型を調べます。JL_STD* が変化する例を示します:
$ julia -e 'println(typeof((stdin, stdout, stderr)))'
Tuple{Base.TTY,Base.TTY,Base.TTY}
$ julia -e 'println(typeof((stdin, stdout, stderr)))' < /dev/null 2>/dev/null
Tuple{IOStream,Base.TTY,IOStream}
$ echo hello | julia -e 'println(typeof((stdin, stdout, stderr)))' | cat
Tuple{Base.PipeEndpoint,Base.PipeEndpoint,Base.TTY}
これらのストリームに対する Base.read と Base.write は ccall で libuv のラッパーを呼び出し、そのラッパーは src/jl_uv.c で定義されます:
stream.jl: function write(s::IO, p::Ptr, nb::Integer)
-> ccall(:jl_uv_write, ...)
jl_uv.c: -> int jl_uv_write(uv_stream_t *stream, ...)
-> uv_write(uvw, stream, buf, ...)
初期化中の printf()
jl_printf() などが依存する libuv ストリームはランタイムの初期化中には利用できません (参照: src/init.c, init_stdio())。init_stdio() より前に発生するエラーメッセージや警告は次のように 標準 C ライブラリの fwrite() 関数へ迂回されます:
-
src/sys.cでJL_STD*ストリームのポインタがSTD*_FILENOという静的定数へと静的に初期化される。 -
src/jl_uv.cのjl_uv_puts()関数はuv_stream_t* streamを確認し、値がSTDOUT_FILENOまたはSTDERR_FILENOのときはfwrite()を呼び出す。
この仕組みによって、初期化が終わっているかどうかを気にすることなくランタイム全体で統一して jl_printf() 関数を使えるようになります。
レガシーの ios.c ライブラリ
src/support/ios.c ライブラリは femtolisp から受け継いだものです。クロスプラットフォームなバッファ付きファイル IO とインメモリの一次バッファを提供します。
ios.c は次のファイルで現在でも使われています:
src/flisp/*.c-
src/dump.c: ファイル IO のシリアライズとメモリバッファ -
src/staticdata.c: ファイル IO のシリアライズとメモリバッファ -
base/iostream.jl: ファイル IO (libuv を使った同じ関数はbase/fs.jlにある)
これらのモジュールにおける ios.c の仕様は大部分が自己充足的であり、libuv を使った IO システムとは切り離されています。ただし femtolisp が jl_printf() を使ってレガシーな ios_t ストリームに書き込む場面が一か所だけ存在します。
src/support/ios.h には ios_t.bm フィールドの位置を uv_stream_t.type フィールドと一致させ、ios_t.bm に使われる値が正当な UV_HANDLE_TYPE の値と被らないようにする処理が存在します。これにより uv_stream_t ポインタが ios_t ストリームを指せるようになります。
これが必要なのは、jl_printf() を呼び出す jl_static_show() に femtolisp の fl_print() 関数から ios_t ストリームが渡されるためです。Julia の jl_uv_puts() 関数にはこのための特別な処理を持ちます:
if (stream->type > UV_HANDLE_TYPE_MAX) {
return ios_write((ios_t*)stream, str, n);
}