Initialization of the Julia runtime
El runtime de Julia ejecuta julia -e 'println("Hello World!")' de la siguiente manera:
main()
La ejecución comienza en main() in cli/loader_exe.c, que llama a jl_load_repl() en cli/loader_lib.c, que carga algunas bibliotecas, llamando eventualmente a jl_repl_entrypoint() in src/jlapi.c.
jl_repl_entrypoint() llama a libsupport_init() para establecer la configuración regional de la biblioteca C y para inicializar la biblioteca "ios" (ver ios_init_stdstreams() y Legacy ios.c library).
A continuación, jl_parse_opts() se llama para procesar las opciones de la línea de comandos. Tenga en cuenta que jl_parse_opts() solo se ocupa de las opciones que afectan la generación de código o la inicialización temprana. Otras opciones se manejan más tarde por exec_options() in base/client.jl.
jl_parse_opts() almacena las opciones de línea de comandos en el global jl_options struct.
julia_init()
julia_init() in init.c es llamado por main() y llama a _julia_init() in init.c.
_julia_init() comienza llamando a libsupport_init() nuevamente (no hace nada la segunda vez).
restore_signals() se llama para poner a cero la máscara del manejador de señales.
jl_resolve_sysimg_location() busca rutas configuradas para la imagen base del sistema. Ver Building the Julia system image.
jl_gc_init() configura grupos de asignación y listas para referencias débiles, valores preservados y finalización.
jl_init_frontend() carga e inicializa una imagen de femtolisp precompilada que contiene el escáner/parsing.
jl_init_types() crea objetos de descripción de tipo jl_datatype_t para built-in types defined in julia.h. p.ej.
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() crea el objeto jl_datatype_t* jl_task_type; inicializa la estructura global jl_root_task; y establece jl_current_task como la tarea raíz.
jl_init_codegen() inicializa el LLVM library.
jl_init_serializer() inicializa etiquetas de serialización de 8 bits para valores jl_value_t incorporados.
Si no hay un archivo sysimg (!jl_options.image_file), entonces se crean los módulos Core y Main y se evalúa boot.jl:
jl_core_module = jl_new_module(jl_symbol("Core")) crea el módulo Core de Julia.
jl_init_intrinsic_functions() crea un nuevo módulo de Julia Intrinsics que contiene símbolos constantes jl_intrinsic_type. Estos definen un código entero para cada intrinsic function. emit_intrinsic() traduce estos símbolos en instrucciones LLVM durante la generación de código.
jl_init_primitives() conecta funciones C a símbolos de funciones de Julia. Por ejemplo, el símbolo Core.:(===)() está vinculado al puntero de función C jl_f_is() al llamar a add_builtin_func("===", jl_f_is).
jl_new_main_module() crea el módulo global "Main" y establece jl_current_task->current_module = jl_main_module.
Nota: _julia_init() then sets jl_root_task->current_module = jl_core_module. jl_root_task es un alias de jl_current_task en este punto, por lo que el current_module establecido por jl_new_main_module() arriba es sobrescrito.
jl_load("boot.jl", sizeof("boot.jl")) llama a jl_parse_eval_all que llama repetidamente a jl_toplevel_eval_flex() para ejecutar boot.jl. <!– TODO – profundizar en eval? –>
jl_get_builtin_hooks() inicializa punteros globales de C a globales de Julia definidos en boot.jl.
jl_init_box_caches() pre-asigna objetos de valor entero global en caja para valores de hasta 1024. Esto acelera la asignación de enteros en caja más adelante. p.ej.:
jl_value_t *jl_box_uint8(uint32_t x)
{
return boxed_uint8_cache[(uint8_t)x];
}_julia_init() iterates sobre el jl_core_module->bindings.table buscando valores de jl_datatype_t y establece el prefijo del nombre del tipo en el módulo jl_core_module.
jl_add_standard_imports(jl_main_module) hace "uso de Base" en el módulo "Principal".
Nota: _julia_init() ahora revierte a jl_root_task->current_module = jl_main_module como lo era antes de ser establecido en jl_core_module arriba.
Los controladores de señales específicos de la plataforma se inicializan para SIGSEGV (OSX, Linux) y SIGFPE (Windows).
Otros señales (SIGINFO, SIGBUS, SIGILL, SIGTERM, SIGABRT, SIGQUIT, SIGSYS y SIGPIPE) están conectadas a sigdie_handler() que imprime un backtrace.
jl_init_restored_module() llama a jl_module_run_initializer() para cada módulo deserializado para ejecutar la función __init__().
Finalmente sigint_handler() está conectado a SIGINT y llama a jl_throw(jl_interrupt_exception).
_julia_init() luego devuelve back to main() in cli/loader_exe.c y main() llama a repl_entrypoint(argc, (char**)argv).
repl_entrypoint()
repl_entrypoint() carga el contenido de argv[] en Base.ARGS.
Si se suministró un archivo de "programa" .jl en la línea de comandos, entonces exec_program() llama a jl_load(program,len) que llama a jl_parse_eval_all que llama repetidamente a jl_toplevel_eval_flex() para ejecutar el programa.
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 llama Base.exec_options que llama jl_parse_input_line("println("Hello World!")") para crear un objeto de expresión y Core.eval(Main, ex) para ejecutar la expresión analizada ex en el contexto del módulo de Main.
Core.eval
Core.eval(Main, ex) llama a jl_toplevel_eval_in(m, ex), que llama a jl_toplevel_eval_flex. jl_toplevel_eval_flex implementa una heurística simple para decidir si compilar un código dado o ejecutarlo mediante un intérprete. Cuando se le da println("Hello World!"), generalmente decidiría ejecutar el código mediante el intérprete, en cuyo caso llama a jl_interpret_toplevel_thunk, que luego llama a eval_body.
El volcado de pila a continuación muestra cómo el intérprete trabaja a través de varios métodos de Base.println() y Base.print() antes de llegar a write(s::IO, a::Array{T}) where T que hace ccall(jl_uv_write()).
jl_uv_write() llama a uv_write() para escribir "¡Hola Mundo!" en JL_STDOUT. Ver 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 |
Dado que nuestro ejemplo tiene solo una llamada a la función, que ha cumplido su tarea de imprimir "¡Hola Mundo!", la pila ahora se desenrolla rápidamente de vuelta a main().
jl_atexit_hook()
main() llama a jl_atexit_hook(). Esto llama a Base._atexit, luego llama a jl_gc_run_all_finalizers() y limpia los manejadores de libuv.
julia_save()
Finalmente, main() llama a julia_save(), que si se solicita en la línea de comandos, guarda el estado de ejecución en una nueva imagen del sistema. Consulta jl_compile_all() y jl_save_system_image().