How does the Julia runtime executejulia -e 'println("Hello World!")'
?
main()
Execution starts atmain()
incli/loader_exe.c
, which callsjl_load_repl()
incli/loader_lib.c
which loads a few libraries, eventually callingjl_repl_entrypoint()
insrc/jlapi.c
.
jl_repl_entrypoint()
callslibsupport_init()
to set the C library locale and to initialize the "ios" library (seeios_init_stdstreams()
andLegacyios.c
library).
Nextjl_parse_opts()
is called to process command line options. Note thatjl_parse_opts()
only deals with options that affect code generation or early initialization. Other options are handled later byexec_options()
inbase/client.jl
.
jl_parse_opts()
stores command line options in theglobaljl_options
struct.
julia_init()
julia_init()
ininit.c
is called bymain()
and calls_julia_init()
ininit.c
.
_julia_init()
begins by callinglibsupport_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. SeeBuilding 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()
createsjl_datatype_t
type description objects for thebuilt-in types defined injulia.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 thejl_datatype_t* jl_task_type
object; initializes the globaljl_root_task
struct; and setsjl_current_task
to the root task.
jl_init_codegen()
initializes theLLVM library.
jl_init_serializer()
initializes 8-bit serialization tags for builtinjl_value_t
values.
If there is no sysimg file (!jl_options.image_file
) then theCore
andMain
modules are created andboot.jl
is evaluated:
jl_core_module = jl_new_module(jl_symbol("Core"))
creates the JuliaCore
module.
jl_init_intrinsic_functions()
creates a new Julia moduleIntrinsics
containing constantjl_intrinsic_type
symbols. These define an integer code for eachintrinsic 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 symbolCore.:(===)()
is bound to C function pointerjl_f_is()
by callingadd_builtin_func("===", jl_f_is)
.
jl_new_main_module()
creates the global "Main" module and setsjl_current_task->current_module = jl_main_module
.
Note:_julia_init()
then setsjl_root_task->current_module = jl_core_module
.jl_root_task
is an alias ofjl_current_task
at this point, so thecurrent_module
set byjl_new_main_module()
above is overwritten.
jl_load("boot.jl", sizeof("boot.jl"))
callsjl_parse_eval_all
which repeatedly callsjl_toplevel_eval_flex()
to executeboot.jl
. <!– TODO – drill down into eval? –>
jl_get_builtin_hooks()
initializes global C pointers to Julia globals defined inboot.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 thejl_core_module->bindings.table
looking forjl_datatype_t
values and sets the type name's module prefix tojl_core_module
.
jl_add_standard_imports(jl_main_module)
does "using Base" in the "Main" module.
Note:_julia_init()
now reverts tojl_root_task->current_module = jl_main_module
as it was before being set tojl_core_module
above.
Platform specific signal handlers are initialized forSIGSEGV
(OSX, Linux), andSIGFPE
(Windows).
Other signals (SIGINFO, SIGBUS, SIGILL, SIGTERM, SIGABRT, SIGQUIT, SIGSYS
andSIGPIPE
) are hooked up tosigdie_handler()
which prints a backtrace.
jl_init_restored_module()
callsjl_module_run_initializer()
for each deserialized module to run the__init__()
function.
Finallysigint_handler()
is hooked up toSIGINT
and callsjl_throw(jl_interrupt_exception)
.
_julia_init()
then returnsback tomain()
incli/loader_exe.c
andmain()
callsrepl_entrypoint(argc, (char**)argv)
.
If there is a sysimg file, it contains a pre-cooked image of theCore
andMain
modules (and whatever else is created byboot.jl
). SeeBuilding the Julia system image.
jl_restore_system_image()
deserializes the saved sysimg into the current Julia runtime environment and initialization continues afterjl_init_box_caches()
below...
Note:jl_restore_system_image()
(andstaticdata.c
in general) uses theLegacyios.c
library.
repl_entrypoint()
repl_entrypoint()
loads the contents ofargv[]
intoBase.ARGS
.
If a.jl
"program" file was supplied on the command line, thenexec_program()
callsjl_load(program,len)
which callsjl_parse_eval_all
which repeatedly callsjl_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 upBase._start
andjl_apply()
executes it.
Base._start
Base._start
callsBase.exec_options
which callsjl_parse_input_line("println("Hello World!")")
to create an expression object andCore.eval(Main, ex)
to execute the parsed expressionex
in the module context ofMain
.
Core.eval
Core.eval(Main, ex)
callsjl_toplevel_eval_in(m, ex)
, which callsjl_toplevel_eval_flex
.jl_toplevel_eval_flex
implements a simple heuristic to decide whether to compile a given code thunk or run it by interpreter. When givenprintln("Hello World!")
, it would usually decide to run the code by interpreter, in which case it callsjl_interpret_toplevel_thunk
, which then callseval_body
.
The stack dump below shows how the interpreter works its way through various methods ofBase.println()
andBase.print()
before arriving atwrite(s::IO, a::Array{T}) where T
which doesccall(jl_uv_write())
.
jl_uv_write()
callsuv_write()
to write "Hello World!" toJL_STDOUT
. SeeLibuv wrappers for stdio.:
Hello World!
Stack frame | Source code | Notes |
---|---|---|
jl_uv_write() | jl_uv.c | called thoughccall |
julia_write_282942 | stream.jl | functionwrite!(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_body() | interpreter.c | |
jl_interpret_toplevel_thunk | interpreter.c | |
jl_toplevel_eval_flex | toplevel.c | |
jl_toplevel_eval_in | toplevel.c | |
Core.eval | boot.jl |
Since our example has just one function call, which has done its job of printing "Hello World!", the stack now rapidly unwinds back tomain()
.
jl_atexit_hook()
main()
callsjl_atexit_hook()
. This callsBase._atexit
, then callsjl_gc_run_all_finalizers()
and cleans up libuv handles.
julia_save()
Finally,main()
callsjulia_save()
, which if requested on the command line, saves the runtime state to a new system image. Seejl_compile_all()
andjl_save_system_image()
.
Settings
This document was generated withDocumenter.jl version 1.8.0 onWednesday 9 July 2025. Using Julia version 1.11.6.