Julia ランタイムの初期化
Julia ランタイムは julia -e 'println("Hello World!")' をどのように実行するのでしょうか?
main()
実行は ui/repl.c に含まれる main() から始まります。
main() は libsupport_init() を呼び、C ライブラリロケールの設定と ios ライブラリの初期化を行います (参照: ios_init_stdstreams() と レガシーの ios.c ライブラリ)。
続いて jl_parse_opts() が呼ばれてコマンドラインオプションが処理されます。jl_parse_opts() が処理するのは初期化の早い段階またはコード生成に関係するオプションだけであり、他のオプションは base/client.jl の exec_options()1 で処理されます。
jl_parse_opts() はコマンドラインオプションをグローバルな jl_options 構造体に格納します。
julia_init()
次に main() は src/task.c の julia_init() を呼び、そこから src/init.c の _julia_init() が呼び出されます。
_julia_init() は最初に libsupport_init() をもう一度呼び出します (二回目の呼び出しでは何も起こりません)。
restore_signals() が呼ばれてシグナルハンドラマスクがクリアされます。
jl_resolve_sysimg_location() がベースシステムイメージとして設定されたパスを検索します。詳しくは Julia システムイメージの構築の節を参照してください。
jl_gc_init() が弱参照・予約値・ファイナライザで使うアロケーションプールとリストを用意します。
jl_init_frontend() が事前コンパイルされた femtolisp イメージの読み込みと初期化を行います。このイメージにはスキャナーとパーサーが含まれます。
jl_init_types() が julia.h で定義される組み込み型に対応する型記述オブジェクト jl_datatype_t を作成します。例えば次のようなコードが並びます:
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_root_task()2 が jl_datatype_t* jl_task_type オブジェクトを作成し、グローバルな jl_root_task 構造体を初期化し、jl_current_task をルートタスクに設定します。
jl_init_codegen() が LLVM ライブラリを初期化します。
jl_init_serializer()3 が組み込みの jl_value_t 値に対するシリアライズ用の 8 ビットタグを初期化します。
システムが存在しない (!jl_options.image_file) ときは Core と Main モジュールが作成され、boot.jl が評価されます:
jl_core_module = jl_new_module(jl_symbol("Core")) が Core という Julia モジュールを作成します。
jl_init_intrinsic_functions() が Intrinsics という新しい Julia モジュールを作成します。このモジュールには jl_intrinsic_type 型の定数シンボルが含まれ、それぞれが組み込み命令関数を表す整数コードを定義します。コード生成時には emit_intrinsic() がこれらのシンボルを LLVM 命令に変換します。
jl_init_primitives() が C の関数を Julia の関数シンボルにフックします。例えばシンボル Core.:(===) が C 関数 jl_f_is に束縛されるのは、add_builtin_func("===", jl_f_is) という呼び出しがあるためです。
jl_init_main_module()4 がグローバルな Main モジュールを作成します。
jl_load(jl_core_module, "boot.jl")5 が jl_toplevel_eval_flex() を何度も呼び出し、boot.jl を実行します。
post_boot_hooks()6 が boot.jl で定義される Julia グローバル変数を指すグローバル C ポインタを初期化します。
jl_init_box_caches() がボックス化されるグローバルな整数値オブジェクトを 1024 まで事前にアロケートします。これにより将来のボックス化される整数のアロケートが高速化されます。例えば次のように使います:
jl_value_t *jl_box_uint8(uint32_t x)
{
return boxed_uint8_cache[(uint8_t)x];
}
post_boot_hooks() が jl_core_module->bindings.table を反復し、このテーブルに含まれる jl_datatype_t 値が属するモジュールを jl_core_module に設定します7。
プラットフォーム固有のシグナルハンドラが SIGSEGV (OSX, Linux) または SIGFPE (Windows) に対して初期化されます。
他のシグナル (SIGINFO, SIGBUS, SIGILL, SIGTERM, SIGABRT, SIGQUIT, SIGSYS, SIGPIPE) はバックトレースを出力する sigdie_handler() にフックされます。
jl_init_restored_modules()8 が jl_module_run_initializer()9 を呼び出し、デシリアライズされた各モジュールの __init__() 関数を実行します。
最後に、jl_throw(jl_interrupt_exception) の起動を設定する sigint_handler() が SIGINT に関連付けられます。
実行は _julia_init() から ui/repl.c 内の main() に戻り、main() が true_main(argc, (char**)argv) を呼び出します。
sysimg ファイルが存在するなら、そのファイルは事前に処理された Core モジュールと Main モジュールのイメージ (および boot.jl が作成した任意のファイル) を含みます。詳しくはシステムイメージの作成を参照してください。
jl_restore_system_image() が保存された sysimg を現在の Julia ランタイム環境にデシリアライズし、その下の jl_init_box_caches の後で初期化が続きます。
情報: jl_restore_system_image() (一般に staticdata.c に含まれる関数) は レガシーの ios.c ライブラリ を使用します。
true_main()
true_main() が argv[] の中身を Base.ARGS に読み込みます。
.jl の "プログラム" ファイルがコマンドラインで与えられたなら、true_main は exec_program() を呼び、それが jl_load(program,len) を呼び、そこから jl_parse_eval_all が呼び出され、そこで jl_toplevel_eval_flex() が何度も呼ばれることでプログラムが実行されます。
しかし今考えている julia -e 'println("Hello World!")' では式が与えられているので、true_main 内の jl_get_global(jl_base_module, jl_symbol("_start")) が Base._start を検索し、jl_apply() が入力を実行します。
Base._start
Base._start は exec_options()10 を呼び、そこから入力文字列をパースして式オブジェクトを作成するために jl_parse_input_line("println("Hello World!")") が呼び出され、最後に Base.eval() が作成された式オブジェクトを実行します。
Base.eval
Base.eval() は boot.jl で jl_toplevel_eval_in にマップされるので11、この関数が入力された文字列 println("Hello World!") をパースした式オブジェクトを引数として呼び出されます。実行を行うモジュールを表す引数には jl_main_module が渡されます。
jl_toplevel_eval_in()12 は jl_toplevel_eval_flex() を呼び、src/interpreter.c に含まれる様々な eval...() 関数が呼ばれます。
インタープリタが Base.println() と Base.print() を通って write(s::IO, a::Array{T}) where T に到達し、最終的に ccall(jl_uv_write()) を呼ぶまでのスタックダンプを次に示します13。
| スタックフレーム | ソースコード | 情報 |
|---|---|---|
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() |
interpreter.c |
|
jl_interpret_toplevel_expr() |
interpreter.c |
|
jl_toplevel_eval_flex() |
toplevel.c |
|
jl_toplevel_eval() |
toplevel.c |
|
jl_toplevel_eval_in() |
builtins.c |
|
jl_f_top_eval() |
builtins.c |
jl_uv_write() は uv_write() を呼び出して "Hello World!" を JL_STDOUT に書き込みます。詳しくは stdio に対する Libuv のラッパーを参照してください。
Hello World!
今考えている例には関数呼び出しが一つあるだけなので、"Hello World!" を出力するとスタックは main() まで巻き戻ります。
jl_atexit_hook()
main() は最後に jl_atexit_hook() を呼び出し、この関数が jl_gc_run_all_finalizers() を呼び、libuv ハンドルを掃除します14。
-
訳注: 関数の名前を修正した (参照: コミット fc0715)。また現在のバージョンでは
jl_task_typeはjl_init_typesで初期化される (参照: #31948)。[return] -
訳注: リンク先のファイルを修正した。[return]
-
訳注: 関数の名前を修正し、
current_moduleへの言及を削除した (参照: #29483)。[return] -
訳注: 実際のコードに合うよう修正した。[return]
-
訳注: 英語版では
jl_add_standard_import(jl_main_module)に関する段落が次にあるが、この文は現在のバージョンに存在しないので段落ごと削除した (参照: #34828)。[return] -
訳注: ファイル名を変更した。[return]
-
訳注: ファイル名を変更した。[return]
-
訳注: ファイル名を修正した。[return]
-
訳注: 現在のバージョンで得られるスタックダンプとは異なる。[return]
-
訳注: この後に
julia_saveに関する説明があったが、現在のバージョンに存在しないので削除した。[return]