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.jlexec_options()1 で処理されます。

jl_parse_opts() はコマンドラインオプションをグローバルな jl_options 構造体に格納します。

julia_init()

次に main()src/task.cjulia_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()2jl_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) ときは CoreMain モジュールが作成され、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")5jl_toplevel_eval_flex() を何度も呼び出し、boot.jl を実行します。

post_boot_hooks()6boot.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()8jl_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

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_mainexec_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._startexec_options()10 を呼び、そこから入力文字列をパースして式オブジェクトを作成するために jl_parse_input_line("println("Hello World!")") が呼び出され、最後に Base.eval() が作成された式オブジェクトを実行します。

Base.eval

Base.eval()boot.jljl_toplevel_eval_in にマップされるので11、この関数が入力された文字列 println("Hello World!") をパースした式オブジェクトを引数として呼び出されます。実行を行うモジュールを表す引数には jl_main_module が渡されます。

jl_toplevel_eval_in()12jl_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


  1. 訳注: 関数名が変更されていたので修正した (参照: #25753)。[return]

  2. 訳注: 関数の名前を修正した (参照: コミット fc0715)。また現在のバージョンでは jl_task_typejl_init_types で初期化される (参照: #31948)。[return]

  3. 訳注: リンク先のファイルを修正した。[return]

  4. 訳注: 関数の名前を修正し、current_module への言及を削除した (参照: #29483)。[return]

  5. 訳注: 実際のコードに合うよう修正した。[return]

  6. 訳注: 関数の名前を修正した (参照: #31948)。[return]

  7. 訳注: 英語版では jl_add_standard_import(jl_main_module) に関する段落が次にあるが、この文は現在のバージョンに存在しないので段落ごと削除した (参照: #34828)。[return]

  8. 訳注: ファイル名を変更した。[return]

  9. 訳注: ファイル名を変更した。[return]

  10. 訳注: 関数名が変更されていたので修正した (参照: #25753)。[return]

  11. 訳注: 現在のバージョンに合うよう英語版の記述を修正した (参照: コミット f1664bc)。[return]

  12. 訳注: ファイル名を修正した。[return]

  13. 訳注: 現在のバージョンで得られるスタックダンプとは異なる。[return]

  14. 訳注: この後に julia_save に関する説明があったが、現在のバージョンに存在しないので削除した。[return]

広告