Initialization of the Julia runtime · The Julia Language

# Initialization of the Julia runtime

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`. <!– TODO – drill down into eval? –>

`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)`.

## `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()`.

Theme

This document was generated with Documenter.jl version 0.27.10 on Wednesday 18 May 2022. Using Julia version 1.7.3.