.. highlight:: c *********************************** Initialization of the Julia runtime *********************************** How does the Julia runtime execute :code:`julia -e 'println("Hello World!")'` ? main() ------ Execution starts at `main() in julia/ui/repl.c `_. main() calls `libsupport_init() `_ to set the C library locale and to initialise the "ios" library (see `ios_init_stdstreams() `_ and :ref:`dev-ios`). Next `parse_opts() `_ is called to process command line options. Note that :c:func:`parse_opts` only deals with options that affect code generation or early initialisation. Other options are handled later by `process_options() in base/client.jl `_. :c:func:`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 `_. :c:func:`_julia_init` begins by calling :c:func:`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 :ref:`dev-sysimg`. `jl_gc_init() `_ sets up allocation pools and lists for: weak refs, preserved values and finalization. `jl_init_frontend() `_ loads and initialises a pre-compiled femtolisp image containing the scanner/parser; `jl_init_types() `_ creates :c:type:`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"), NULL, jl_null); jl_any_type->super = jl_any_type; jl_type_type = jl_new_abstracttype(jl_symbol("Type"), jl_any_type, jl_null); jl_int32_type = jl_new_bitstype(jl_symbol("Int32"), jl_any_type, jl_null, 32); `jl_init_tasks() `_ creates the ``jl_datatype_t* jl_task_type`` object; initialises the global ``jl_root_task`` struct; and sets ``jl_current_task`` to the root task. `jl_init_codegen() `_ initialises the `LLVM library `_. `jl_init_serializer() `_ initialises 8-bit serialisation tags for 256 frequently used ``jl_value_t`` values. The serialisation mechanism uses these tags as shorthand (in lieu of storing whole objects) to save storage space. If there is no sysimg file (:code:`!jl_options.image_file`) then then :mod:`Core` and :mod:`Main` modules are created and ``boot.jl`` is evaluated: :code:`jl_core_module = jl_new_module(jl_symbol("Core"))` creates the Julia :mod:`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 :func:`Base.is` is bound to C function pointer :c:func:`jl_f_is` by calling :code:`add_builtin_func("eval", jl_f_top_eval)`. `jl_new_main_module() `_ creates the global "Main" module and sets :code:`jl_current_task->current_module = jl_main_module`. Note: _julia_init() `then sets `_ :code:`jl_root_task->current_module = jl_core_module`. :code:`jl_root_task` is an alias of :code:`jl_current_task` at this point, so the current_module set by :c:func:`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 `_. TODO -- drill down into eval? `jl_get_builtin_hooks() `_ initialises 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 :code:`jl_core_module->bindings.table` looking for :code:`jl_datatype_t` values and sets the type name's module prefix to :code:`jl_core_module`. `jl_add_standard_imports(jl_main_module) `_ does "using Base" in the "Main" module. Note: :c:func:`_julia_init` now reverts to :code:`jl_root_task->current_module = jl_main_module` as it was before being set to ``jl_core_module`` above. Platform specific signal handlers are initialised 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 deserialised module to run the :c:func:`__init__` function. Finally `sigint_handler() `_ is hooked up to ``SIGINT`` and calls :code:`jl_throw(jl_interrupt_exception)`. :c:func:`_julia_init` then returns `back to main() in julia/ui/repl.c `_ and main() calls :code:`true_main(argc, (char**)argv)`. .. sidebar:: sysimg If there is a sysimg file, it contains a pre-cooked image of the :mod:`Core` and :mod:`Main` modules (and whatever else is created by ``boot.jl``). See :ref:`dev-sysimg`. `jl_restore_system_image() `_ de-serialises the saved sysimg into the current Julia runtime environment and initialisation continues after :c:func:`jl_init_box_caches` below... Note: `jl_restore_system_image() (and dump.c in general) `_ uses the :ref:`dev-ios`. true_main() ----------- `true_main() `_ loads the contents of :code:`argv[]` into :data:`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 (:code:`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 :func:`Base.eval` to execute it. Base.eval --------- :func:`Base.eval` was `mapped to jl_f_top_eval `_ by :c:func:`jl_init_primitives`. `jl_f_top_eval() `_ calls `jl_toplevel_eval_in(jl_main_module, ex) `_, where "ex" is the parsed expression :code:`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 :func:`Base.println` and :func:`Base.print` before arriving at `write{T}(s::IO, a::Array{T}) `_ which does :code:`ccall(jl_uv_write())`. `jl_uv_write() `_ calls :c:func:`uv_write` to write "Hello World!" to :c:macro:`JL_STDOUT`. See :ref:`dev-libuv`.:: Hello World! ============================ ================= =============================================== Stack frame Source code Notes ============================ ================= =============================================== jl_uv_write() jl_uv.c called though :func:`Base.ccall` julia_write_282942 stream.jl function write!{T}(s::IO, a::Array{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 :c:func:`main`. jl_atexit_hook() ---------------- :c:func:`main` calls `jl_atexit_hook() `_. This calls _atexit for each module, then calls `jl_gc_run_all_finalizers() `_ and cleans up libuv handles. julia_save() ------------ Finally, :c:func:`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() `_.