Initialization of the Julia runtime
Как выполняет среда выполнения Julia команду julia -e 'println("Hello World!")'?
main()
Выполнение начинается с main() in cli/loader_exe.c, который вызывает jl_load_repl() в cli/loader_lib.c, который загружает несколько библиотек, в конечном итоге вызывая jl_repl_entrypoint() in src/jlapi.c.
jl_repl_entrypoint() вызывает libsupport_init() для установки локали C библиотеки и инициализации библиотеки "ios" (см. ios_init_stdstreams() и Legacy ios.c library).
Следующий jl_parse_opts() вызывается для обработки параметров командной строки. Обратите внимание, что jl_parse_opts() обрабатывает только параметры, которые влияют на генерацию кода или раннюю инициализацию. Другие параметры обрабатываются позже с помощью exec_options() in base/client.jl.
jl_parse_opts() хранит параметры командной строки в global jl_options struct.
julia_init()
julia_init() in init.c вызывается main() и вызывает _julia_init() in init.c.
_julia_init() начинает с вызова libsupport_init() снова (он ничего не делает во второй раз).
restore_signals() называется для обнуления маски обработчика сигнала.
jl_resolve_sysimg_location() ищет настроенные пути для базового системного образа. См. Building the Julia system image.
jl_gc_init() настраивает пулы выделения и списки для слабых ссылок, сохраненных значений и финализации.
jl_init_frontend() загружает и инициализирует предварительно скомпилированное изображение femtolisp, содержащее сканер/парсер.
jl_init_types() создает объекты описания типа jl_datatype_t для built-in types defined in julia.h. например.
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() создает объект jl_datatype_t* jl_task_type; инициализирует глобальную структуру jl_root_task; и устанавливает jl_current_task на корневую задачу.
jl_init_codegen() инициализирует LLVM library.
jl_init_serializer() инициализирует 8-битные теги сериализации для встроенных значений jl_value_t.
Если файл sysimg отсутствует (!jl_options.image_file), то создаются модули Core и Main, и выполняется boot.jl:
jl_core_module = jl_new_module(jl_symbol("Core")) создает модуль Julia Core.
jl_init_intrinsic_functions() создает новый модуль Julia Intrinsics, содержащий символы константы jl_intrinsic_type. Эти символы определяют целочисленный код для каждого intrinsic function. emit_intrinsic() переводит эти символы в инструкции LLVM во время генерации кода.
jl_init_primitives() связывает функции C с символами функций Julia. Например, символ Core.:(===)() связан с указателем функции C jl_f_is() путем вызова add_builtin_func("===", jl_f_is).
jl_new_main_module() создает глобальный модуль "Main" и устанавливает jl_current_task->current_module = jl_main_module.
Примечание: _julia_init() then sets jl_root_task->current_module = jl_core_module. jl_root_task является псевдонимом jl_current_task в этот момент, поэтому current_module, установленный jl_new_main_module() выше, перезаписывается.
jl_load("boot.jl", sizeof("boot.jl")) вызывает jl_parse_eval_all, который многократно вызывает jl_toplevel_eval_flex() для выполнения boot.jl. <!– TODO – drill down into eval? –>
jl_get_builtin_hooks() инициализирует глобальные указатели C для глобальных переменных Julia, определенных в boot.jl.
jl_init_box_caches() предварительно выделяет глобальные объекты значений целых чисел для значений до 1024. Это ускоряет выделение упакованных целых чисел позже. например:
jl_value_t *jl_box_uint8(uint32_t x)
{
return boxed_uint8_cache[(uint8_t)x];
}_julia_init() iterates над jl_core_module->bindings.table, ища значения jl_datatype_t и устанавливая префикс имени типа модуля на jl_core_module.
jl_add_standard_imports(jl_main_module) does "using Base" in the "Main" module.
Примечание: _julia_init() теперь возвращается к jl_root_task->current_module = jl_main_module, как это было до установки на jl_core_module выше.
Специфические для платформы обработчики сигналов инициализируются для SIGSEGV (OSX, Linux) и SIGFPE (Windows).
Другие сигналы (SIGINFO, SIGBUS, SIGILL, SIGTERM, SIGABRT, SIGQUIT, SIGSYS и SIGPIPE) подключены к sigdie_handler(), который выводит трассировку стека.
jl_init_restored_module() вызывает jl_module_run_initializer() для каждого десериализованного модуля, чтобы выполнить функцию __init__().
Наконец, sigint_handler() подключен к SIGINT и вызывает jl_throw(jl_interrupt_exception).
_julia_init() затем возвращает back to main() in cli/loader_exe.c и main() вызывает repl_entrypoint(argc, (char**)argv).
repl_entrypoint()
repl_entrypoint() загружает содержимое argv[] в Base.ARGS.
Если файл ".jl" "программы" был передан в командной строке, то exec_program() вызывает jl_load(program,len), который вызывает jl_parse_eval_all, который многократно вызывает jl_toplevel_eval_flex() для выполнения программы.
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 вызывает Base.exec_options, который вызывает jl_parse_input_line("println("Hello World!")"), чтобы создать объект выражения, и Core.eval(Main, ex), чтобы выполнить разобранное выражение ex в контексте модуля Main.
Core.eval
Core.eval(Main, ex) вызывает jl_toplevel_eval_in(m, ex), который вызывает jl_toplevel_eval_flex. jl_toplevel_eval_flex реализует простую эвристику, чтобы решить, компилировать ли данный код или выполнять его интерпретатором. Когда ему передают println("Hello World!"), он обычно решает выполнить код интерпретатором, в этом случае он вызывает jl_interpret_toplevel_thunk, который затем вызывает eval_body.
Стековый дамп ниже показывает, как интерпретатор проходит через различные методы Base.println() и Base.print(), прежде чем добраться до write(s::IO, a::Array{T}) where T, который выполняет ccall(jl_uv_write()).
jl_uv_write() вызывает uv_write(), чтобы записать "Hello World!" в JL_STDOUT. См. 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 |
Поскольку в нашем примере есть только один вызов функции, который выполнил свою задачу, напечатав "Hello World!", стек теперь быстро разворачивается обратно к main().
jl_atexit_hook()
main() вызывает jl_atexit_hook(). Это вызывает Base._atexit, затем вызывает jl_gc_run_all_finalizers() и очищает дескрипторы libuv.
julia_save()
Наконец, main() вызывает julia_save(), который, если запрашивается в командной строке, сохраняет состояние выполнения в новое системное изображение. См. jl_compile_all() и jl_save_system_image().