Документация Engee

Инициализация среды выполнения Julia

Каким образом среда выполнения Julia выполняет julia -e 'println("Hello World!")'?

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, он содержит заранее подготовленный образ модулей Core и Main (и что-либо еще, созданное с помощью boot.jl). См главу Сборка образа системы Julia. jl_restore_system_image() десериализует сохраненный sysimg в текущую среду выполнения Julia, и инициализация продолжается после jl_init_box_caches() ниже. Примечание. jl_restore_system_image()staticdata.c в целом) использует устаревшую библиотеку ios.c.

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!
Фрейм стека Исходный код Примечания

jl_uv_write()

jl_uv.c

вызывается с помощью ключевого слова ccall

julia_write_282942

stream.jl

Функция 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().