Initialization of the Julia runtime
Wie führt die Julia-Laufzeit julia -e 'println("Hello World!")'
aus?
main()
Die Ausführung beginnt bei main()
in cli/loader_exe.c
, das jl_load_repl()
in cli/loader_lib.c
aufruft, das einige Bibliotheken lädt und schließlich jl_repl_entrypoint()
in src/jlapi.c
aufruft.
jl_repl_entrypoint()
ruft libsupport_init()
auf, um die C-Bibliothekslokalisierung festzulegen und die "ios"-Bibliothek zu initialisieren (siehe ios_init_stdstreams()
und Legacy ios.c
library).
Nächster jl_parse_opts()
wird aufgerufen, um Befehlszeilenoptionen zu verarbeiten. Beachten Sie, dass jl_parse_opts()
nur mit Optionen umgeht, die die Codegenerierung oder die frühe Initialisierung betreffen. Andere Optionen werden später von exec_options()
in base/client.jl
behandelt.
jl_parse_opts()
speichert Befehlszeilenoptionen in der global jl_options
struct.
julia_init()
julia_init()
in init.c
wird von main()
aufgerufen und ruft _julia_init()
in init.c
auf.
_julia_init()
beginnt damit, libsupport_init()
erneut aufzurufen (es tut beim zweiten Mal nichts).
restore_signals()
wird aufgerufen, um die Signalhandler-Maske auf Null zu setzen.
jl_resolve_sysimg_location()
durchsucht konfigurierte Pfade nach dem Basis-System-Image. Siehe Building the Julia system image.
jl_gc_init()
richtet Zuteilungspools und Listen für schwache Referenzen, erhaltene Werte und Finalisierung ein.
jl_init_frontend()
lädt und initialisiert ein vorcompiliertes Femtolisp-Image, das den Scanner/Parser enthält.
jl_init_types()
erstellt jl_datatype_t
Typbeschreibungsobjekte für die built-in types defined in julia.h
. z.B.
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()
erstellt das jl_datatype_t* jl_task_type
Objekt; initialisiert die globale jl_root_task
Struktur; und setzt jl_current_task
auf die Root-Aufgabe.
jl_init_codegen()
initialisiert die LLVM library.
jl_init_serializer()
initialisiert 8-Bit-Serialisierungstags für eingebaute jl_value_t
-Werte.
Wenn keine sysimg-Datei (!jl_options.image_file
) vorhanden ist, werden die Module Core
und Main
erstellt und boot.jl
wird ausgewertet:
jl_core_module = jl_new_module(jl_symbol("Core"))
erstellt das Julia Core
-Modul.
jl_init_intrinsic_functions()
erstellt ein neues Julia-Modul Intrinsics
, das die Konstanten jl_intrinsic_type
-Symbole enthält. Diese definieren einen ganzzahligen Code für jede intrinsic function. emit_intrinsic()
übersetzt diese Symbole in LLVM-Anweisungen während der Codegenerierung.
jl_init_primitives()
verbindet C-Funktionen mit Julia-Funktionssymbolen. z.B. ist das Symbol Core.:(===)()
an den C-Funktionszeiger jl_f_is()
gebunden, indem add_builtin_func("===", jl_f_is)
aufgerufen wird.
jl_new_main_module()
erstellt das globale "Main"-Modul und setzt jl_current_task->current_module = jl_main_module
.
Hinweis: _julia_init()
then sets jl_root_task->current_module = jl_core_module
. jl_root_task
ist zu diesem Zeitpunkt ein Alias von jl_current_task
, sodass das von jl_new_main_module()
oben gesetzte current_module
überschrieben wird.
jl_load("boot.jl", sizeof("boot.jl"))
ruft jl_parse_eval_all
auf, das wiederholt jl_toplevel_eval_flex()
aufruft, um boot.jl
auszuführen. <!– TODO – in eval vertiefen? –>
jl_get_builtin_hooks()
initialisiert globale C-Zeiger auf Julia-Globals, die in boot.jl
definiert sind.
jl_init_box_caches()
reserviert globale verpackte Ganzzahlwertobjekte für Werte bis zu 1024. Dies beschleunigt die Zuweisung von verpackten Ganzzahlen später. z.B.:
jl_value_t *jl_box_uint8(uint32_t x)
{
return boxed_uint8_cache[(uint8_t)x];
}
_julia_init()
iterates über das jl_core_module->bindings.table
, um nach jl_datatype_t
Werten zu suchen und das Modulpräfix des Typnamens auf jl_core_module
zu setzen.
jl_add_standard_imports(jl_main_module)
does "using Base" in the "Main" module.
Hinweis: _julia_init()
setzt jetzt jl_root_task->current_module = jl_main_module
zurück, wie es zuvor war, bevor es auf jl_core_module
oben gesetzt wurde.
Plattform-spezifische Signalhandler werden für SIGSEGV
(OSX, Linux) und SIGFPE
(Windows) initialisiert.
Andere Signale (SIGINFO, SIGBUS, SIGILL, SIGTERM, SIGABRT, SIGQUIT, SIGSYS
und SIGPIPE
) sind mit sigdie_handler()
verbunden, das einen Backtrace ausgibt.
jl_init_restored_module()
ruft jl_module_run_initializer()
für jedes deserialisierte Modul auf, um die Funktion __init__()
auszuführen.
Schließlich ist sigint_handler()
mit SIGINT
verbunden und ruft jl_throw(jl_interrupt_exception)
auf.
_julia_init()
gibt dann back to main()
in cli/loader_exe.c
zurück und main()
ruft repl_entrypoint(argc, (char**)argv)
auf.
repl_entrypoint()
repl_entrypoint()
lädt den Inhalt von argv[]
in Base.ARGS
ein.
Wenn eine .jl
"Programmdaten" Datei über die Befehlszeile bereitgestellt wurde, dann ruft exec_program()
jl_load(program,len)
auf, das jl_parse_eval_all
aufruft, das wiederholt jl_toplevel_eval_flex()
aufruft, um das Programm auszuführen.
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
ruft Base.exec_options
auf, das jl_parse_input_line("println("Hello World!")")
aufruft, um ein Ausdrucksobjekt zu erstellen, und Core.eval(Main, ex)
um den geparsten Ausdruck ex
im Modulkontext von Main
auszuführen.
Core.eval
Core.eval(Main, ex)
ruft jl_toplevel_eval_in(m, ex)
auf, das jl_toplevel_eval_flex
aufruft. jl_toplevel_eval_flex
implementiert eine einfache Heuristik, um zu entscheiden, ob ein gegebener Code-Thunk kompiliert oder vom Interpreter ausgeführt werden soll. Wenn println("Hello World!")
gegeben wird, würde es normalerweise entscheiden, den Code vom Interpreter auszuführen, in diesem Fall ruft es jl_interpret_toplevel_thunk
auf, das dann eval_body
aufruft.
Der Stack-Dump unten zeigt, wie der Interpreter sich durch verschiedene Methoden von Base.println()
und Base.print()
arbeitet, bevor er bei write(s::IO, a::Array{T}) where T
ankommt, das ccall(jl_uv_write())
ausführt.
jl_uv_write()
ruft uv_write()
auf, um "Hello World!" an JL_STDOUT
zu schreiben. Siehe 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_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 |
Da unser Beispiel nur einen Funktionsaufruf hat, der seine Aufgabe erfüllt hat, "Hello World!" auszugeben, entfaltet sich der Stack jetzt schnell zurück zu main()
.
jl_atexit_hook()
main()
ruft jl_atexit_hook()
auf. Dies ruft Base._atexit
auf, dann wird jl_gc_run_all_finalizers()
auf und bereinigt die libuv-Handles.
julia_save()
Schließlich ruft main()
julia_save()
auf, was, wenn es in der Befehlszeile angefordert wird, den Laufzeitstatus in einem neuen Systembild speichert. Siehe jl_compile_all()
und jl_save_system_image()
.