printf() и stdio в среде выполнения Julia

Оболочки libuv для 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, чтобы обеспечить единую обработку буферизации вывода.

В особых случаях, когда, например, применяются обработчики сигналов и когда полная инфраструктура libuv слишком объемна, функцию jl_safe_printf() можно использовать для записи (write(2)) непосредственно в STDERR_FILENO.

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

Интерфейс между JL_STD* и кодом Julia

Константы Base.stdin, Base.stdout и Base.stderr привязаны к потокам JL_STD* libuv, определенным в среде выполнения.

Функция Julia __init__()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() во время инициализации

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

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

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

Устаревшая библиотека ios.c

Библиотека 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. Однако есть одно место, где 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.

Это необходимо, поскольку функция fl_print() femtolisp передает поток ios_t функции jl_printf(), вызывающей jl_static_show(). Функция jl_uv_puts() в Julia обрабатывает этот момент особым образом.

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