printf() and stdio in the Julia runtime

Libuv wrappers for stdio

julia.hstdio.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/ および cli/ ディレクトリ内の .c ファイルで必要に応じて使用されます。

In special cases, like signal handlers, where the full libuv infrastructure is too heavy, jl_safe_printf() can be used to write(2) directly to STDERR_FILENO:

void jl_safe_printf(const char *str, ...);

Interface between JL_STD* and Julia code

Base.stdinBase.stdout および Base.stderr は、ランタイムで定義された JL_STD* libuv ストリームにバインドされています。

ジュリアの __init__() 関数(base/sysimg.jl 内)は、reinit_stdio()base/stream.jl 内)を呼び出して、Base.stdinBase.stdout および Base.stderr のためのジュリアオブジェクトを作成します。

reinit_stdio()ccall を使用して JL_STD* へのポインタを取得し、各ストリームのタイプを調べるために jl_uv_handle_type() を呼び出します。その後、各ストリームを表すために Julia の Base.IOStreamBase.TTY または Base.PipeEndpoint オブジェクトを作成します。例えば:

$ 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 を使用して src/jl_uv.c 内の libuv ラッパーを呼び出します。例えば:

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() during initialization

jl_printf() などで使用される libuv ストリームは、ランタイムの初期化の途中まで利用できません(init.cinit_stdio() を参照)。この前に印刷する必要があるエラーメッセージや警告は、次のメカニズムによって標準 C ライブラリの fwrite() 関数にルーティングされます:

sys.cでは、JL_STD*ストリームポインタが整数定数STD*_FILENO (0, 1, 2)に静的に初期化されています。jl_uv.cでは、jl_uv_puts()関数がそのuv_stream_t* stream引数をチェックし、ストリームがSTDOUT_FILENOまたはSTDERR_FILENOに設定されている場合にfwrite()を呼び出します。

これにより、初期化が完了する前に特定のコードが到達可能かどうかに関係なく、ランタイム全体で jl_printf() を一貫して使用できるようになります。

Legacy ios.c library

src/support/ios.cライブラリはfemtolispから継承されています。これは、クロスプラットフォームのバッファ付きファイルIOとメモリ内一時バッファを提供します。

ios.c はまだ以下で使用されています:

  • src/flisp/*.c
  • src/dump.c – シリアル化ファイルIOおよびメモリバッファ用。
  • src/staticdata.c – シリアル化ファイルIOおよびメモリバッファ用。
  • base/iostream.jl – ファイル入出力用 (libuvの同等物についてはbase/fs.jlを参照)。

ios.cのこれらのモジュールでの使用は、主に自己完結型であり、libuv I/Oシステムから分離されています。しかし、one placeでは、femtolispがレガシーなios_tストリームを使用してjl_printf()を呼び出します。

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);
}