printf() and stdio in the Julia runtime¶
Libuv wrappers for stdio¶
julia.h defines libuv wrappers for the
stdio.h streams:
uv_stream_t*JL_STDIN;uv_stream_t*JL_STDOUT;uv_stream_t*JL_STDERR;... and corresponding output functions:
intjl_printf(uv_stream_t*s,constchar*format,...);intjl_vprintf(uv_stream_t*s,constchar*format,va_listargs);These printf functions are used by julia/{src,ui}/*.c wherever stdio
is needed to ensure that output buffering is handled in a unified
way.
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:
voidjl_safe_printf(constchar*str,...);Interface between JL_STD* and Julia code¶
Base.STDIN, Base.STDOUT and Base.STDERR are
bound to the JL_STD*libuv streams
defined in the runtime.
Julia’s __init__() function (in base/sysimg.jl) calls
reinit_stdio() (in base/stream.jl) to create Julia objects
for Base.STDIN, Base.STDOUT and Base.STDERR.
reinit_stdio() uses ccall() to retrieve pointers to
JL_STD* and calls jl_uv_handle_type() to inspect
the type of each stream. It then creates a Julia Base.File,
Base.TTY or Base.Pipe object to represent each
stream, e.g.:
$ julia -e 'typeof((STDIN, STDOUT, STDERR))'(TTY,TTY,TTY)
$ julia -e 'println(typeof((STDIN, STDOUT, STDERR)))' < /dev/null 2>/dev/null
(Base.FS.File,TTY,Base.FS.File)
$ echo hello | julia -e 'println(typeof((STDIN, STDOUT, STDERR)))'| cat
(Pipe,Pipe,TTY)The Base.read() and Base.write() methods for these
streams use ccall() to call libuv wrappers in src/jl_uv.c, e.g.:
stream.jl:functionwrite(s::AsyncStream,p::Ptr,nb::Integer)->ccall(:jl_uv_write,...)jl_uv.c:->intjl_uv_write(uv_stream_t*stream,...)->uv_write(uvw,stream,buf,...)printf() during initialisation¶
The libuv streams relied upon by jl_printf() etc., are not
available until midway through initialisation of the runtime (see
init.c, init_stdio()). Error messages or warnings that need
to be printed before this are routed to the standard C library
fwrite() function by the following mechanism:
In sys.c, the JL_STD* stream pointers are statically initialised
to integer constants: STD*_FILENO(0,1and2). In jl_uv.c the
jl_write() function checks its uv_stream_t*stream
argument and calls fwrite() if stream is set to STDOUT_FILENO
or STDERR_FILENO.
This allows for uniform use of jl_printf() throughout the
runtime regardless of whether or not any particular piece of code
is reachable before initialisation is complete.
Legacy ios.c library¶
The julia/src/support/ios.c library is inherited from femtolisp.
It provides cross-platform buffered file IO and in-memory temporary buffers.
ios.c is still used by:
julia/src/flisp/*.cjulia/src/dump.c– for serialisation file IO and for memory buffers.base/iostream.jl– for file IO (seebase/fs.jlforlibuvequivalent).
Use of ios.c in these modules is mostly self-contained and
separated from the libuv I/O system. However, there is one place
where femtolisp calls through to jl_printf() with a legacy ios_t stream.
There is a hack in ios.h that makes the ios_t.bm
field line up with the uv_stream_t.type and ensures that
the values used for ios_t.bm to not overlap with valid
UV_HANDLE_TYPE values. This allows uv_stream_t pointers
to point to ios_t streams.
This is needed because jl_printf() caller jl_static_show()
is passed an ios_t stream by femtolisp’s fl_print() function.
Julia’s jl_write() function has special handling for this:
if(stream->type>UV_HANDLE_TYPE_MAX){returnios_write((ios_t*)stream,str,n);}