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

sysimg

Если есть файл sysimg, он содержит предварительно подготовленное изображение модулей Core и Main (и всего остального, что создается boot.jl). См. Building the Julia system image.

jl_restore_system_image() десериализует сохраненный sysimg в текущую среду выполнения Julia, и инициализация продолжается после jl_init_box_caches() ниже...

Примечание: jl_restore_system_image() (and staticdata.c in general) использует Legacy ios.c library.

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 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

Поскольку в нашем примере есть только один вызов функции, который выполнил свою задачу, напечатав "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().