Skip to content

Commit

Permalink
Refactor to reduce dependancy on ios.c
Browse files Browse the repository at this point in the history
Rationale:

    Unravel ios.c’s legacy role as debug io library for the runtime;
    vs current role as backend for iostream.jl.

Clean up historical debug printf functions:

    Replace calls to ios_printf(), ios_puts(), JL_PRINTF, JL_PUTS etc
    with jl_printf(), jl_puts()...

    Replace exit() with jl_exit() in repl.c, dump.c and init.c.

Make jl_printf() and jl_exit() safe in early initialisation.

Remove ios.c dependance from ast.c and builtins.c:

    src/flisp/flisp.c:

        New fl_load_system_image_str(char*, size_t)
        Hides detail of ios_static_buffer from ast.c
        ios_* no longer used in ast.c.

    src/builtins.c

        In jl_errorf(), replace ios_vprintf() with vasprintf()
        (following the precedent set by src/jl_uv.c:663).

Use free() not LLT_FREE() to clean up after vasprintf() in jl_uv.c:

    man vasprintf says “asprintf() and vasprintf() dynamically
                        allocate a new string with malloc(3)”

Refactor pairs of calls to jl_printf() & jl_exit(1) into calls to
jl_error[f](), which does the same thing during early init.

Dead code removal:

    jl_uv.c: jl_bufptr(), jl_ios_buf_base(), jl_putc(), jl_pututf8()
    sys.c: jl_ios_size()

New jl_safe_printf().

jl_safe_printf() uses a statically allocated 1000 byte buffer to
vnsprintf() a message then calls write(2) to send it to stderr.

jl_safe_printf() is intended to be used in places where there is the
potential for out of memory, corrupted stack, re-entrancy etc.

The current jl_safe_printf() implementation is not 100% safe in all
situations, but it should be pretty good most of the time, especially
if the formatting is limited to %d.

The safety of jl_safe_printf() could be improved later, but at least
for now the places were safety might matter are identified by its use.

jl_safe_printf() is used in:

 - sigdie_handler()
 - sigint_handler()
 - _exception_handler()
 - catch_exception_raise()
 - gdblookup()

Thanks to @vtjnash for assistance and feedback with these changes.

Dead code cleanup & jl_write() simplificaiton.

Clean up dead code:
    stream.jl : write!() — My apologies if this is used somewhere, I
could’t find any doc or callers.
    builtins.c : jl_print_symbol(), jl_print_int64()
    ccall.cpp : if(0 …) jl_puts()
    jl_uv.c : jl_puts

Replace 2 remaining calls to jl_puts() with calls to jl_printf() for
consistency.
    builtins.c : jl_error()
    repl.c : true_main()

Move jl_uv_writecb() implementation from stream.jl to jl_uv.c.
This callback is only used by jl_write(),
which is only called from jl_vprintf() in runtime C code.
This helps to de-tangle fs.jl and stream.jl from the runtime's printf().

Use jl_safe_printf() to attempt error message output in jl_uv_writecb().
If this callback is printing an error, there was a problem writing to
JL_STDOUT or JL_STDERR, so its best to try the simpelest means possible
to get the error out.

update doc/devdocs/stdio.rst to remove reference to:
    jl_write(), jl_putc(), jl_puts().

Simplify handling of STDOUT and STDERR early in initialisation:
    Instead of calling fwrite(), just call uv_default_loop() to get the
    jl_io_loop and then call jl_fs_write as usual.
    This means that all jl_printf()s now go through uvlib.
    There is a chance that this won’t work in win32, AppVeyor will tell...

Remove C interface for single character writes:

 - jl_fs_write_byte()
 - jl_putc_copy()
 - jl_pututf8_copy()

Hadnle single character writes in Julia:

 - write(f::File,        c::UInt8) = write(f, [c])
 - write(s::AsyncStream, b::UInt8) = write(s, [b])
 - write(s::AsyncStream, c::Char)  = write(s, string(c))

Rationale:

 - Writing to a libuv stream involves: "Julia does C malloc, Julia calls C,
   C calls libuv, libuv calls C callback, C callback calls julia hook,
   C callcback does c_free". Having less special cases of this makes
   it easier to reason about correctness.

 - Whatever overhead there might be in creating the temporary 1
   element array (or string); it is small compared to all the
   machinery in libuv, and the OS that actually makes the single
   character write happen.

 - If a user is doing single character writes to a libuv stream,
   they either don’t care about performance, or they should be
   buffering in Julia before calling write().

Inline jl_write_copy() into only remaining called: jl_write():

 - This is now the only place that needs to know about the
   trick used to store a copy of the output string after the
   request struct (i.e. malloc(sizeof(uv_write_t) + n));

Removed dead code:
 - stream.jl : make_stdout_stream()
 - stream.jl : jl_write_copy()

Rename:
 - jl_write_no_copy() -> jl_uv_write()

Move common code from stream.jl write() functions into uv_write macro:
 - ccall to jl_uv_write() (was jl_write_no_copy).
 - wait for current task

Simplify uv_write memory management:

Try to keep all the state management in one place (in macto uv_write()).

Move free() of uv_write_t request object
from C callback jl_uv_writecb_task() to macro uv_write().
This puts the malloc() and the free() in the same place.

Move uv_req_set_data(uvw,C_NULL) from C jl_uv_write() to macro
uv_write().
In _uv_hook_writecb_task() assert that uv_req_data(req) is not NULL.
libuv should not call the callback from within the call to uv_write(),
libuv has an internal “write_completed_queue” that is pumped by the main
loop.
  • Loading branch information
samoconnor committed Feb 2, 2015
1 parent 56e69e2 commit fabd42c
Show file tree
Hide file tree
Showing 27 changed files with 519 additions and 588 deletions.
9 changes: 1 addition & 8 deletions base/fs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,7 @@ function write(f::File, buf::Ptr{UInt8}, len::Integer, offset::Integer=-1)
len
end

function write(f::File, c::UInt8)
if !f.open
throw(ArgumentError("file \"$(f.path)\" is not open"))
end
err = ccall(:jl_fs_write_byte, Int32, (Int32, Cchar), f.handle, c)
uv_error("write",err)
1
end
write(f::File, c::UInt8) = write(f,[c])

function write{T}(f::File, a::Array{T})
if isbits(T)
Expand Down
90 changes: 25 additions & 65 deletions base/stream.jl
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,6 @@ convert(T::Type{Ptr{Void}}, s::AsyncStream) = convert(T, s.handle)
handle(s::AsyncStream) = s.handle
handle(s::Ptr{Void}) = s

make_stdout_stream() = _uv_tty2tty(ccall(:jl_stdout_stream, Ptr{Void}, ()))

associate_julia_struct(handle::Ptr{Void},jlobj::ANY) =
ccall(:jl_uv_associate_julia_struct,Void,(Ptr{Void},Any),handle,jlobj)
disassociate_julia_struct(uv) = disassociate_julia_struct(uv.handle)
Expand All @@ -239,6 +237,8 @@ function init_stdio(handle)
t = ccall(:jl_uv_handle_type,Int32,(Ptr{Void},),handle)
if t == UV_FILE
return fdio(ccall(:jl_uv_file_handle,Int32,(Ptr{Void},),handle))
# Replace ios.c filw with libuv file?
# return File(RawFD(ccall(:jl_uv_file_handle,Int32,(Ptr{Void},),handle)))
else
if t == UV_TTY
ret = TTY(handle)
Expand Down Expand Up @@ -272,7 +272,6 @@ function reinit_stdio()
global uv_jl_readcb = cglobal(:jl_uv_readcb)
global uv_jl_connectioncb = cglobal(:jl_uv_connectioncb)
global uv_jl_connectcb = cglobal(:jl_uv_connectcb)
global uv_jl_writecb = cglobal(:jl_uv_writecb)
global uv_jl_writecb_task = cglobal(:jl_uv_writecb_task)
global uv_eventloop = ccall(:jl_global_event_loop, Ptr{Void}, ())
global STDIN = init_stdio(ccall(:jl_stdin_stream ,Ptr{Void},()))
Expand Down Expand Up @@ -726,93 +725,54 @@ end
# finish_read(state...)
#end

macro uv_write(n,call)
macro uv_write(stream, data, len)
esc(quote
check_open(s)
uvw = c_malloc(_sizeof_uv_write+$(n))
err = $call
uvw = c_malloc(_sizeof_uv_write)
uv_req_set_data(uvw,C_NULL)
err = ccall(:jl_uv_write,
Int32,
(Ptr{Void}, Ptr{Void}, UInt, Ptr{Void}, Ptr{Void}),
handle($(stream)), $(data), $(len), uvw,
uv_jl_writecb_task::Ptr{Void})
if err < 0
c_free(uvw)
uv_error("write", err)
end
ct = current_task()
uv_req_set_data(uvw,ct)
ct.state = :waiting
stream_wait(ct)
c_free(uvw)
end)
end

## low-level calls ##

function write!{T}(s::AsyncStream, a::Array{T})
if isbits(T)
n = uint(length(a)*sizeof(T))
@uv_write n ccall(:jl_write_no_copy, Int32, (Ptr{Void}, Ptr{Void}, UInt, Ptr{Void}, Ptr{Void}), handle(s), a, n, uvw, uv_jl_writecb::Ptr{Void})
return int(length(a)*sizeof(T))
else
throw(MethodError(write,(s,a)))
end
end
function write!(s::AsyncStream, p::Ptr, nb::Integer)
@uv_write nb ccall(:jl_write_no_copy, Int32, (Ptr{Void}, Ptr{Void}, UInt, Ptr{Void}, Ptr{Void}), handle(s), p, nb, uvw, uv_jl_writecb::Ptr{Void})
return nb
end
write!(s::AsyncStream, string::ByteString) = write!(s,string.data)

function _uv_hook_writecb(s::AsyncStream, req::Ptr{Void}, status::Int32)
if status < 0
err = UVError("write",status)
showerror(STDERR, err, backtrace())
end
nothing
end

function write(s::AsyncStream, b::UInt8)
@uv_write 1 ccall(:jl_putc_copy, Int32, (UInt8, Ptr{Void}, Ptr{Void}, Ptr{Void}), b, handle(s), uvw, uv_jl_writecb_task::Ptr{Void})
ct = current_task()
uv_req_set_data(uvw,ct)
ct.state = :waiting
stream_wait(ct)
return 1
end
function write(s::AsyncStream, c::Char)
nb = utf8sizeof(c)
@uv_write nb ccall(:jl_pututf8_copy, Int32, (Ptr{Void}, UInt32, Ptr{Void}, Ptr{Void}), handle(s), c, uvw, uv_jl_writecb_task::Ptr{Void})
ct = current_task()
uv_req_set_data(uvw,ct)
ct.state = :waiting
stream_wait(ct)
return nb
end
write(s::AsyncStream, b::UInt8) = write(s, [b])
write(s::AsyncStream, c::Char) = write(s, string(c))
function write{T}(s::AsyncStream, a::Array{T})
if isbits(T)
n = uint(length(a)*sizeof(T))
@uv_write n ccall(:jl_write_no_copy, Int32, (Ptr{Void}, Ptr{Void}, UInt, Ptr{Void}, Ptr{Void}), handle(s), a, n, uvw, uv_jl_writecb_task::Ptr{Void})
ct = current_task()
uv_req_set_data(uvw,ct)
ct.state = :waiting
stream_wait(ct)
return int(length(a)*sizeof(T))
@uv_write s a n
return int(n)
else
check_open(s)
invoke(write,(IO,Array),s,a)
end
end
function write(s::AsyncStream, p::Ptr, nb::Integer)
@uv_write nb ccall(:jl_write_no_copy, Int32, (Ptr{Void}, Ptr{Void}, UInt, Ptr{Void}, Ptr{Void}), handle(s), p, nb, uvw, uv_jl_writecb_task::Ptr{Void})
ct = current_task()
uv_req_set_data(uvw,ct)
ct.state = :waiting
stream_wait(ct)
return int(nb)
function write(s::AsyncStream, p::Ptr, n::Integer)
@uv_write s p n
return int(n)
end

function _uv_hook_writecb_task(s::AsyncStream,req::Ptr{Void},status::Int32)
d = uv_req_data(req)
@assert d != C_NULL
if status < 0
err = UVError("write",status)
if d != C_NULL
schedule(unsafe_pointer_to_objref(d)::Task,err,error=true)
else
showerror(STDERR, err, backtrace())
end
elseif d != C_NULL
schedule(unsafe_pointer_to_objref(d)::Task,err,error=true)
else
schedule(unsafe_pointer_to_objref(d)::Task)
end
end
Expand Down
1 change: 1 addition & 0 deletions doc/devdocs/julia.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
subarrays
sysimg
llvm
stdio
116 changes: 116 additions & 0 deletions doc/devdocs/stdio.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
***************************************
printf() and stdio in the Julia runtime
***************************************

Libuv wrappers for stdio
------------------------

julia.h defines `libuv <http://docs.libuv.org>`_ 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::

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

These printf functions are used by :code:`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, :func:`jl_safe_printf` can be used to
:code:`write(2)` directly to :data:`STDERR_FILENO`::

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



Interface between JL_STD* and Julia code
----------------------------------------

:data:`Base.STDIN`, :data:`Base.STDOUT` and :data:`Base.STDERR` are
bound to the :code:`JL_STD*` `libuv <http://docs.libuv.org>`_ streams
defined in the runtime.

Julia's :func:`__init__` function (inbase/sysimg.jl) calls
:func:`reinit_stdio` (in base/stream.jl) to create Julia objects
for :data:`Base.STDIN`, :data:`Base.STDOUT` and :data:`Base.STDERR`.

:func:`reinit_stdio` uses :func:`ccall` to retrieve pointers to
:code:`JL_STD*` and calls :func:`jl_uv_handle_type()` to inspect
the type of each stream. It then creates a Julia :code:`Base.File`,
:code:`Base.TTY` or :code:`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 :func:`Base.read()` and :func:`Base.write()` methods for these
streams use :func:`ccall` to call libuv wrappers in :code:`src/jl_uv.c`. e.g::

stream.jl: function write(s::AsyncStream, p::Ptr, nb::Integer)
-> ccall(:jl_write_no_copy, ...)
jl_uv.c: -> int jl_write_no_copy(uv_stream_t *stream, ...)
-> uv_write(uvw, stream, buf, ...)

printf() during initialisation
------------------------------

The libuv streams relied apon by :func:`jl_printf` etc are not
available until mid-way through initialisation of the runtime (see
init.c, :func:`init_stdio`). Error messages or warnings that need
to be printed before this are routed to the standard C library
:func:`fwrite` function by the following mechanism:

In sys.c the :code:`JL_STD*` stream pointers are statically initialised
to integer constants: STD*_FILENO (0, 1 and 2). In jl_uv.c the
:func:`jl_write` function checks its :code:`uv_stream_t* stream`
argument and calls :func:`fwrite` if stream is set to STDOUT_FILENO
or STDERR_FILENO.

This allows for uniform use of :func:`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 :code:`julia/src/support/ios.c` library is inherited from `femptolisp <http://github.com/JeffBezanson/femtolisp>`_.
It provides cross-platform buffered file IO and in-memory temporary buffers.

:code:`ios.c` is still used by:

- :code:`julia/src/flisp/*.c`
- :code:`julia/src/dump.c` -- for serialisation file IO and for memory buffers.
- :code:`base/iostream.jl` -- for file IO (see :code:`base/fs.jl` for libuv equivalent).

Use of :code:`ios.c` in these modules is mostly self contained and
seperated from the libuv io system. However, there is `one place
<http://github.com/JuliaLang/julia/blob/master/src/flisp/print.c#L654>`_
where femptolisp calls though to :func:`jl_printf` with a legacy :code:`ios_t` stream.

There is a hack in :code:`ios.h` that makes the :code:`ios_t.bm`
field line up with the :code:`uv_stream_t.type` and ensures that
the values used for :code:`ios_t.bm` to not overlap with valid
UV_HANDLE_TYPE values. This allows :code:`uv_stream_t` pointers
to point to :code:`ios_t` streams.

This is needed because :func`jl_printf` caller :func`jl_static_show`
is passed an :code:`ios_t` stream by femptolisp's :func:`fl_print` function.
Julia's :func:`jl_write` function has special handling for this::

if (stream->type > UV_HANDLE_TYPE_MAX) {
return ios_write((ios_t*)stream, str, n);
}
2 changes: 1 addition & 1 deletion examples/embedding.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ int main()

if (jl_exception_occurred()) {
jl_show(jl_stderr_obj(), jl_exception_occurred());
JL_PRINTF(jl_stderr_stream(), "\n");
jl_printf(jl_stderr_stream(), "\n");
}
}

Expand Down
10 changes: 3 additions & 7 deletions src/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ static uint8_t flisp_system_image[] = {
#include "julia_flisp.boot.inc"
};

extern fltype_t *iostreamtype;
static fltype_t *jvtype=NULL;

static value_t true_sym;
Expand Down Expand Up @@ -120,13 +119,10 @@ extern int jl_parse_depwarn(int warn);
void jl_init_frontend(void)
{
fl_init(4*1024*1024);
value_t img = cvalue(iostreamtype, sizeof(ios_t));
ios_t *pi = value2c(ios_t*, img);
ios_static_buffer(pi, (char*)flisp_system_image, sizeof(flisp_system_image));

if (fl_load_system_image(img)) {
JL_PRINTF(JL_STDERR, "fatal error loading system image\n");
jl_exit(1);
if (fl_load_system_image_str((char*)flisp_system_image,
sizeof(flisp_system_image))) {
jl_error("fatal error loading system image\n");
}

fl_applyn(0, symbol_value(symbol("__init_globals")));
Expand Down
Loading

0 comments on commit fabd42c

Please sign in to comment.