Initialization of the Julia runtime

كيف ينفذ وقت تشغيل جوليا 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")) ينشئ وحدة Core في جوليا.

jl_init_intrinsic_functions() ينشئ وحدة جوليا جديدة Intrinsics تحتوي على رموز ثابتة jl_intrinsic_type. هذه الرموز تعرف رمزًا عدديًا لكل intrinsic function. emit_intrinsic() يترجم هذه الرموز إلى تعليمات LLVM أثناء توليد الشيفرة.

jl_init_primitives() يربط دوال C برموز دوال جوليا. على سبيل المثال، الرمز 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 العالمية إلى المتغيرات العالمية في جوليا المعرفة في 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 المحفوظ إلى بيئة وقت تشغيل جوليا الحالية وتستمر التهيئة بعد 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() لتنفيذ البرنامج.

ومع ذلك، في مثالنا (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 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().