Инициализация среды выполнения Julia
main()
Выполнение начинается с main()
в cli/loader_exe.c
, которая вызывает jl_load_repl()
в cli/loader_lib.c
, которая загружает несколько библиотек, в итоге вызывая repl_entrypoint()
в src/jlapi.c
.
repl_entrypoint()
вызывает libsupport_init()
для установки языкового стандарта библиотеки C и инициализации библиотеки ios (см.страницу ios_init_stdstreams()
и описание устаревшей библиотеки ios.c
).
Далее вызывается jl_parse_opts()
для обработки параметров командной строки. Обратите внимание, что jl_parse_opts()
работает только с параметрами, которые влияют на генерацию кода или раннюю инициализацию. Другие параметры обрабатываются позже функцией exec_options()
в файле base/client.jl
.
jl_parse_opts()
хранит параметры командной строки в глобальной структуре jl_options
.
julia_init()
julia_init()
в task.c
вызывается функцией main()
и вызывает _julia_init()
в init.c
.
Функция _julia_init()
начинается с повторного вызова libsupport_init()
(во второй раз она ничего не делает).
restore_signals()
вызывается для обнуления маски обработчика сигнала.
jl_resolve_sysimg_location()
выполняет поиск настроенных путей для базового образа системы. См главу Сборка образа системы Julia.
jl_gc_init()
задает пулы распределения и списки для слабых ссылок, сохраненных значений и финализации.
jl_init_frontend()
загружает и инициализирует предварительно скомпилированный образ femtolisp, содержащий сканер или анализатор.
jl_init_types()
создает объекты описания типов jl_datatype_t
для встроенных типов, определенных в 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.
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"))
создает модуль Core
Julia.
jl_init_intrinsic_functions()
создает новый модуль Julia Intrinsics
, содержащий постоянные символы jl_intrinsic_type
. Они определяют целочисленный код для каждой внутренней функции. 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()
затем задает 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()
выполняет итерацию jl_core_module->bindings.table
в поисках значений jl_datatype_t
и устанавливает jl_core_module
в качестве префикса модуля имени типа.
jl_add_standard_imports(jl_main_module)
применяет «использование Base» в модуле Main.
Примечание. _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_modules()
вызывает jl_module_run_initializer()
для каждого десериализованного модуля для выполнения функции __init__()
.
Наконец, sigint_handler()
присоединяется к SIGINT
и вызывает jl_throw(jl_interrupt_exception)
.
_julia_init()
затем возвращается обратно к main()
в cli/loader_exe.c
, и main()
вызывает repl_entrypoint(argc, (char**)argv)
.
sysimg
Если имеется файл sysimg, он содержит заранее подготовленный образ модулей |
repl_entrypoint()
repl_entrypoint()
загружает содержимое argv[]
в Base.ARGS
.
Если в командной строке был указан файл .jl
программы, exec_program()
вызывает jl_load(program,len)
, которая вызывает jl_parse_eval_all
, которая многократно вызывает jl_toplevel_eval_flex()
для выполнения программы.
Однако в нашем примере (julia -e 'println("Hello World!")'
) jl_get_global(jl_base_module, jl_symbol("_start"))
ищет Base._start
и jl_apply()
выполняет его.
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 для stdio.
Hello World!
Фрейм стека | Исходный код | Примечания |
---|---|---|
|
|
вызывается с помощью ключевого слова |
|
|
Функция |
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Поскольку в нашем примере есть только один вызов функции, которая выполнила свою работу и вывела «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()
.