printf() and stdio in the Julia runtime

Libuv wrappers for stdio

julia.h определяет libuv обертки для потоков stdio.h:

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 используются файлами .c в директориях src/ и cli/ везде, где требуется stdio, чтобы гарантировать, что буферизация вывода обрабатывается единообразно.

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.stdin, Base.stdout и Base.stderr связаны с библиотекой JL_STD* потоков libuv, определенных в среде выполнения.

Функция __init__() Julia (в base/sysimg.jl) вызывает reinit_stdio()base/stream.jl), чтобы создать объекты Julia для Base.stdin, Base.stdout и Base.stderr.

reinit_stdio() использует ccall для получения указателей на JL_STD* и вызывает jl_uv_handle_type(), чтобы проверить тип каждого потока. Затем он создает объект Julia Base.IOStream, Base.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 для вызова оберток 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() during initialization

Потоки libuv, на которые полагается jl_printf() и т.д., недоступны до середины инициализации времени выполнения (см. init.c, init_stdio()). Сообщения об ошибках или предупреждения, которые необходимо напечатать до этого, перенаправляются в стандартную библиотеку C fwrite() с помощью следующего механизма:

В sys.c указатели потока JL_STD* статически инициализируются целочисленными константами: STD*_FILENO (0, 1 и 2). В jl_uv.c функция jl_uv_puts() проверяет свой аргумент uv_stream_t* stream и вызывает fwrite(), если поток установлен на STDOUT_FILENO или STDERR_FILENO.

Это позволяет единообразно использовать jl_printf() на протяжении всего времени выполнения, независимо от того, доступен ли какой-либо конкретный фрагмент кода до завершения инициализации.

Legacy ios.c library

Библиотека src/support/ios.c унаследована от femtolisp. Она предоставляет кроссплатформенный буферизованный ввод-вывод файлов и временные буферы в памяти.

ios.c все еще используется:

  • src/flisp/*.c
  • src/dump.c – для сериализации файлового ввода-вывода и для буферов памяти.
  • src/staticdata.c – для сериализации файлового ввода-вывода и для буферов памяти.
  • base/iostream.jl – для ввода-вывода файлов (см. base/fs.jl для эквивалента libuv).

Использование ios.c в этих модулях в основном является самодостаточным и отделено от системы ввода-вывода libuv. Однако есть one place, где femtolisp вызывает jl_printf() с помощью устаревшего потока ios_t.

В 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() передается поток ios_t функцией fl_print() из femtolisp. Функция Julia jl_uv_puts() имеет специальную обработку для этого:

if (stream->type > UV_HANDLE_TYPE_MAX) {
    return ios_write((ios_t*)stream, str, n);
}