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.

sysimg

Wenn es eine sysimg-Datei gibt, enthält sie ein vorgefertigtes Bild der Core- und Main-Module (und alles andere, was von boot.jl erstellt wird). Siehe Building the Julia system image.

jl_restore_system_image() deserialisiert das gespeicherte sysimg in die aktuelle Julia-Laufzeitumgebung, und die Initialisierung wird nach jl_init_box_caches() unten fortgesetzt...

Hinweis: jl_restore_system_image() (and staticdata.c in general) verwendet die Legacy ios.c library.

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 frameSource codeNotes
jl_uv_write()jl_uv.ccalled though ccall
julia_write_282942stream.jlfunction write!(s::IO, a::Array{T}) where T
julia_print_284639ascii.jlprint(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.cBase.print(Base.TTY, String)
jl_apply()julia.h
jl_trampoline()builtins.c
jl_apply()julia.h
jl_apply_generic()gf.cBase.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.cBase.println(Base.TTY, String, String...)
jl_apply()julia.h
jl_trampoline()builtins.c
jl_apply()julia.h
jl_apply_generic()gf.cBase.println(String,)
jl_apply()julia.h
do_call()interpreter.c
eval_body()interpreter.c
jl_interpret_toplevel_thunkinterpreter.c
jl_toplevel_eval_flextoplevel.c
jl_toplevel_eval_intoplevel.c
Core.evalboot.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().