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.IOStream
,
Base.TTY
or Base.PipeEndpoint
object to represent each
stream, e.g.:
$ 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}
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::IO,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_uv_puts()
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/*.c
julia/src/dump.c
– for serialisation file IO and for memory buffers.base/iostream.jl
– for file IO (seebase/fs.jl
forlibuv
equivalent).
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_uv_puts()
function has special handling for this:
if(stream->type>UV_HANDLE_TYPE_MAX){returnios_write((ios_t*)stream,str,n);}