Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JL_STD* vs ios_std* cleanup #9450

Merged
merged 2 commits into from
Feb 16, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would worry about the performance of allocating an array here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be handled by jl_fs_write, passing &c as the data.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue that this is not the right layer to deal with performance of single-byte writes.
The OS implementation of the single-byte write involves a sys-call, maybe a network stack, etc... it is expensive.
If the caller needs to efficiently write lots of single bytes, they should write to a buffer interface that wraps the raw io interface.
Is there a particular use-pattern in current Julia code where single character writes to File are performance critical?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JeffBezanson you have to be careful there, since we could execute a task switch and invalid the stack slot before it gets written out

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @vtjnash,
Sorry, I'm afraid I don't quite understand.
Are you saying that the user of a buffered interface might write a character that does not get sent because it is left in the buffer? ... in that case they should call buffer.flush(). No?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that was addressed to Jeff's comment. just bad timing as we both typed replies at the same time


function write{T}(f::File, a::Array{T})
if isbits(T)
Expand Down
98 changes: 28 additions & 70 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,52 @@ end
# finish_read(state...)
#end

macro uv_write(n,call)
esc(quote
check_open(s)
uvw = c_malloc(_sizeof_uv_write+$(n))
err = $call
function uv_write(s::AsyncStream, p, n::Integer)
check_open(s)
uvw = c_malloc(_sizeof_uv_write)
try
uv_req_set_data(uvw,C_NULL)
err = ccall(:jl_uv_write,
Int32,
(Ptr{Void}, Ptr{Void}, UInt, Ptr{Void}, Ptr{Void}),
handle(s), p, n, uvw,
uv_jl_writecb_task::Ptr{Void})
if err < 0
c_free(uvw)
uv_error("write", err)
end
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)))
ct = current_task()
uv_req_set_data(uvw,ct)
ct.state = :waiting
stream_wait(ct)
finally
c_free(uvw)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs a try/finally since you moved the c_free here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

although this seems to be less InterruptException-safe than it was before. perhaps also need to move the JL_SIGATOMIC into this function, since the code it was protecting also moved here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is still JL_SIGATOMIC in C code (in jl_uv_write()) around the call to uv_write().
Which code do you think could be unsafe? uv_req_set_data(uvw, C_NULL)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mostly the possible c_free interactions here

end
return n
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
## low-level calls ##

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))
return uv_write(s, a, 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)
end

write(s::AsyncStream, p::Ptr, n::Integer) = uv_write(s, p, n)

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