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]