Julia 运行时的初始化
How does the Julia runtime execute julia -e 'println("Hello World!")'
?
main()
Execution starts at main()
in cli/loader_exe.c
,
which calls jl_load_repl()
in cli/loader_lib.c
which loads a few libraries, eventually calling repl_entrypoint()
in src/jlapi.c
.
repl_entrypoint()
calls libsupport_init()
to set the C library locale and to initialize the “ios” library (see ios_init_stdstreams()
and Legacy ios.c
library).
Next jl_parse_opts()
is called to process
command line options. Note that jl_parse_opts()
only deals with options that affect code generation
or early initialization. Other options are handled later by process_options()
in base/client.jl
.
jl_parse_opts()
stores command line options in the global jl_options
struct.
julia_init()
julia_init()
in task.c
is called
by main()
and calls _julia_init()
in init.c
.
_julia_init()
begins by calling libsupport_init()
again (it does nothing the second time).
restore_signals()
is called
to zero the signal handler mask.
jl_resolve_sysimg_location()
searches
configured paths for the base system image. See Building the Julia system image.
jl_gc_init()
sets up allocation pools
and lists for weak refs, preserved values and finalization.
jl_init_frontend()
loads and initializes
a pre-compiled femtolisp image containing the scanner/parser.
jl_init_types()
creates jl_datatype_t
type description objects for the built-in types defined in julia.h
.
e.g.
jl_any_type = jl_new_abstracttype(jl_symbol("Any"), core, NULL, jl_emptysvec);
jl_any_type->super = jl_any_type;
jl_type_type = jl_new_abstracttype(jl_symbol("Type"), core, jl_any_type, jl_emptysvec);
jl_int32_type = jl_new_primitivetype(jl_symbol("Int32"), core,
jl_any_type, jl_emptysvec, 32);
jl_init_tasks()
creates the jl_datatype_t* jl_task_type
object; initializes the global jl_root_task
struct; and sets jl_current_task
to the root task.
jl_init_codegen()
initializes
the LLVM library.
jl_init_serializer()
initializes
8-bit serialization tags for builtin jl_value_t
values.
If there is no sysimg file (!jl_options.image_file
) then the Core
and Main
modules are
created and boot.jl
is evaluated:
jl_core_module = jl_new_module(jl_symbol("Core"))
creates the Julia Core
module.
jl_init_intrinsic_functions()
creates a new Julia module Intrinsics
containing constant jl_intrinsic_type
symbols. These define
an integer code for each intrinsic function.
emit_intrinsic()
translates
these symbols into LLVM instructions during code generation.
jl_init_primitives()
hooks C
functions up to Julia function symbols. e.g. the symbol Core.:(===)()
is bound to C function pointer
jl_f_is()
by calling add_builtin_func("===", jl_f_is)
.
jl_new_main_module()
creates
the global “Main” module and sets jl_current_task->current_module = jl_main_module
.
Note: _julia_init()
then sets jl_root_task->current_module = jl_core_module
.
jl_root_task
is an alias of jl_current_task
at this point, so the current_module
set by jl_new_main_module()
above is overwritten.
jl_load("boot.jl", sizeof("boot.jl"))
calls jl_parse_eval_all
which repeatedly
calls jl_toplevel_eval_flex()
to execute boot.jl
.
jl_get_builtin_hooks()
initializes
global C pointers to Julia globals defined in boot.jl
.
jl_init_box_caches()
pre-allocates
global boxed integer value objects for values up to 1024. This speeds up allocation of boxed ints
later on. e.g.:
jl_value_t *jl_box_uint8(uint32_t x)
{
return boxed_uint8_cache[(uint8_t)x];
}
_julia_init()
iterates over the
jl_core_module->bindings.table
looking for jl_datatype_t
values and sets the type name’s module
prefix to jl_core_module
.
jl_add_standard_imports(jl_main_module)
does “using Base” in the “Main” module.
Note: _julia_init()
now reverts to jl_root_task->current_module = jl_main_module
as it was
before being set to jl_core_module
above.
Platform specific signal handlers are initialized for SIGSEGV
(OSX, Linux), and SIGFPE
(Windows).
Other signals (SIGINFO, SIGBUS, SIGILL, SIGTERM, SIGABRT, SIGQUIT, SIGSYS
and SIGPIPE
) are
hooked up to sigdie_handler()
which prints a backtrace.
jl_init_restored_modules()
calls
jl_module_run_initializer()
for
each deserialized module to run the __init__()
function.
Finally sigint_handler()
is hooked up to SIGINT
and calls jl_throw(jl_interrupt_exception)
.
_julia_init()
then returns back to main()
in cli/loader_exe.c
and main()
calls repl_entrypoint(argc, (char**)argv)
.
!!! sidebar “sysimg”
If there is a sysimg file, it contains a pre-cooked image of the Core
and Main
modules (and
whatever else is created by boot.jl
). See Building the Julia system image.
[`jl_restore_system_image()`](https://github.com/JuliaLang/julia/blob/master/src/staticdata.c) deserializes
the saved sysimg into the current Julia runtime environment and initialization continues after
`jl_init_box_caches()` below...
Note: [`jl_restore_system_image()` (and `staticdata.c` in general)](https://github.com/JuliaLang/julia/blob/master/src/staticdata.c)
uses the [Legacy `ios.c` library]($zh_CN-doc-src-devdocs-@ref).
repl_entrypoint()
repl_entrypoint()
loads the contents of
argv[]
into Base.ARGS
.
If a .jl
“program” file was supplied on the command line, then exec_program()
calls jl_load(program,len)
which
calls jl_parse_eval_all
which repeatedly
calls jl_toplevel_eval_flex()
to execute the program.
However, in our example (julia -e 'println("Hello World!")'
), jl_get_global(jl_base_module, jl_symbol("_start"))
looks up Base._start
and jl_apply()
executes it.
Base._start
Base._start
calls Base.process_options
which calls jl_parse_input_line("println("Hello World!")")
to create an expression object and Base.eval()
to execute it.
Base.eval
Base.eval()
was mapped to jl_f_top_eval
by jl_init_primitives()
.
jl_f_top_eval()
calls jl_toplevel_eval_in(jl_main_module, ex)
,
where ex
is the parsed expression println("Hello World!")
.
jl_toplevel_eval_in()
calls
jl_toplevel_eval_flex()
which
calls eval()
in interpreter.c
.
The stack dump below shows how the interpreter works its way through various methods of Base.println()
and Base.print()
before arriving at write(s::IO, a::Array{T}) where T
which does ccall(jl_uv_write())
.
jl_uv_write()
calls uv_write()
to write “Hello World!” to JL_STDOUT
. See Libuv wrappers for stdio.:
Hello World!
Stack frame | Source code | Notes |
---|---|---|
jl_uv_write() |
jl_uv.c |
called though ccall |
julia_write_282942 |
stream.jl |
function write!(s::IO, a::Array{T}) where T |
julia_print_284639 |
ascii.jl |
print(io::IO, s::String) = (write(io, s); nothing) |
jlcall_print_284639 |
||
jl_apply() |
julia.h |
|
jl_trampoline() |
builtins.c |
|
jl_apply() |
julia.h |
|
jl_apply_generic() |
gf.c |
Base.print(Base.TTY, String) |
jl_apply() |
julia.h |
|
jl_trampoline() |
builtins.c |
|
jl_apply() |
julia.h |
|
jl_apply_generic() |
gf.c |
Base.print(Base.TTY, String, Char, Char...) |
jl_apply() |
julia.h |
|
jl_f_apply() |
builtins.c |
|
jl_apply() |
julia.h |
|
jl_trampoline() |
builtins.c |
|
jl_apply() |
julia.h |
|
jl_apply_generic() |
gf.c |
Base.println(Base.TTY, String, String...) |
jl_apply() |
julia.h |
|
jl_trampoline() |
builtins.c |
|
jl_apply() |
julia.h |
|
jl_apply_generic() |
gf.c |
Base.println(String,) |
jl_apply() |
julia.h |
|
do_call() |
interpreter.c |
|
eval() |
interpreter.c |
|
jl_interpret_toplevel_expr() |
interpreter.c |
|
jl_toplevel_eval_flex() |
toplevel.c |
|
jl_toplevel_eval() |
toplevel.c |
|
jl_toplevel_eval_in() |
builtins.c |
|
jl_f_top_eval() |
builtins.c |
Since our example has just one function call, which has done its job of printing “Hello World!”,
the stack now rapidly unwinds back to main()
.
jl_atexit_hook()
main()
calls jl_atexit_hook()
.
This calls Base._atexit
, then calls jl_gc_run_all_finalizers()
and cleans up libuv handles.
julia_save()
Finally, main()
calls julia_save()
,
which if requested on the command line, saves the runtime state to a new system image. See jl_compile_all()
and jl_save_system_image()
.