printf() and stdio in the Julia runtime

Libuv wrappers for stdio

julia.h definiert libuv Wrapper für die stdio.h Streams:

uv_stream_t *JL_STDIN;
uv_stream_t *JL_STDOUT;
uv_stream_t *JL_STDERR;

... und entsprechende Ausgabefunktionen:

int jl_printf(uv_stream_t *s, const char *format, ...);
int jl_vprintf(uv_stream_t *s, const char *format, va_list args);

Diese printf-Funktionen werden von den .c-Dateien in den src/- und cli/-Verzeichnissen verwendet, wo immer stdio benötigt wird, um sicherzustellen, dass die Ausgabepufferung einheitlich behandelt wird.

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 und Base.stderr sind an die JL_STD* libuv-Streams gebunden, die zur Laufzeit definiert sind.

Julias __init__()-Funktion (in base/sysimg.jl) ruft reinit_stdio() (in base/stream.jl) auf, um Julia-Objekte für Base.stdin, Base.stdout und Base.stderr zu erstellen.

reinit_stdio() verwendet ccall, um Zeiger auf JL_STD* abzurufen, und ruft jl_uv_handle_type() auf, um den Typ jedes Streams zu überprüfen. Anschließend erstellt es ein Julia Base.IOStream, Base.TTY oder Base.PipeEndpoint Objekt, um jeden Stream darzustellen, z. B.:

$ 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}

Die Base.read und Base.write Methoden für diese Streams verwenden ccall, um libuv-Wrapper in src/jl_uv.c aufzurufen, z.B.:

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

Die von jl_printf() usw. abhängigen libuv-Streams sind erst zur Mitte der Initialisierung der Laufzeit verfügbar (siehe init.c, init_stdio()). Fehlermeldungen oder Warnungen, die vorher ausgegeben werden müssen, werden durch den folgenden Mechanismus an die Standard-C-Bibliotheksfunktion fwrite() weitergeleitet:

In sys.c sind die JL_STD* Stream-Pointer statisch auf Ganzzahlkonstanten initialisiert: STD*_FILENO (0, 1 und 2). In jl_uv.c überprüft die Funktion jl_uv_puts() ihr Argument uv_stream_t* stream und ruft fwrite() auf, wenn der Stream auf STDOUT_FILENO oder STDERR_FILENO gesetzt ist.

Dies ermöglicht eine einheitliche Verwendung von jl_printf() während der Laufzeit, unabhängig davon, ob ein bestimmter Codeabschnitt vor Abschluss der Initialisierung erreichbar ist oder nicht.

Legacy ios.c library

Die src/support/ios.c Bibliothek wird von femtolisp abgeleitet. Sie bietet plattformübergreifende gepufferte Datei-I/O und temporäre Puffer im Arbeitsspeicher.

ios.c wird immer noch verwendet von:

  • src/flisp/*.c
  • src/dump.c – für die Serialisierung von Datei-I/O und für Speicherpuffer.
  • src/staticdata.c – für die Serialisierungsdatei IO und für Speicherpuffer.
  • base/iostream.jl – für Datei-I/O (siehe base/fs.jl für das libuv-Äquivalent).

Die Verwendung von ios.c in diesen Modulen ist größtenteils eigenständig und vom libuv I/O-System getrennt. Es gibt jedoch one place, wo femtolisp über einen Legacy ios_t-Stream auf jl_printf() zugreift.

Es gibt einen Hack in ios.h, der das Feld ios_t.bm mit dem uv_stream_t.type ausrichtet und sicherstellt, dass die Werte, die für ios_t.bm verwendet werden, sich nicht mit gültigen UV_HANDLE_TYPE-Werten überschneiden. Dies ermöglicht es uv_stream_t-Zeigern, auf ios_t-Streams zu zeigen.

Dies ist erforderlich, da der Aufrufer von jl_printf(), jl_static_show(), einen ios_t-Stream von femtolisp's fl_print()-Funktion übergeben bekommt. Julias Funktion jl_uv_puts() hat dafür eine spezielle Behandlung:

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