ocamloptを読むことになりました。 - Togetter
Camlp4だけはこわいんでゲソ… 逃げるしかないでゲソ。
考えてみると、確かにアラフラにはjhcを採用する予定でゲソが、 ひょっとしたらこのデザインに致命的な欠陥が見つかる可能性も高いでゲソ。 その場合、OCamlは代替案として大変有望でゲソ。 今のうちに調べておいて損はないんじゃなイカ?
簡単なプログラム を作ってみて、コンパイル結果とocamloptを比較すればだいたいわかるはずでゲソ。
$ uname -a
Linux casper 3.2.0-4-amd64 #1 SMP Debian 3.2.35-2 x86_64 GNU/Linux
$ pwd
/home/kiwamu/src/read_ocamlopt
$ ocamlopt -version
4.00.1
$ cat helloworld/helloworld.ml
let hello _ = print_endline "Hello world!";;
let _ = hello ();
$ make
ocamlopt -verbose -dstartup -o helloworld/helloworld helloworld/helloworld.ml
+ as -o 'helloworld/helloworld.o' '/tmp/camlasm034093.s'
+ as -o '/tmp/camlstartup43810e.o' 'helloworld/helloworld.startup.s'
+ gcc -o 'helloworld/helloworld' '-L/usr/local/lib/ocaml' '/tmp/camlstartup43810e.o' '/usr/local/lib/ocaml/std_exit.o' 'helloworld/helloworld.o' '/usr/local/lib/ocaml/stdlib.a' '/usr/local/lib/ocaml/libasmrun.a' -lm -ldl
ということは以下のファイルについて調べればocamloptの全体像がわかるはずでゲソ。
$ pwd
/home/kiwamu/src/read_ocamlopt/helloworld
$ gdb helloworld
(gdb) b camlHelloworld__hello_1030
Breakpoint 1 at 0x403560
(gdb) run
Starting program: /home/kiwamu/src/read_ocamlopt/helloworld/helloworld
warning: Could not load shared library symbols for linux-vdso.so.1.
Do you need "set solib-search-path" or "set sysroot"?
Breakpoint 1, 0x0000000000403560 in camlHelloworld__hello_1030 ()
(gdb) bt
#0 0x0000000000403560 in camlHelloworld__hello_1030 ()
#1 0x0000000000403591 in camlHelloworld__entry ()
#2 0x00000000000003e8 in ?? ()
#3 0x0000000000403229 in caml_program ()
#4 0x000000000002950d in ?? ()
#5 0x0000000000411a52 in caml_start_program ()
#6 0x0000000000000000 in ?? ()
スタックが小細工されているけれど、 caml_start_program関数を呼び出すのはasmrun/startup.cなので、 以下のようなコールグラフになるはずでゲソ。
caml_main (asmrun/startup.c)
=> caml_init_custom_operations(); /* これocamlopt nativeでも必要? */
caml_register_custom_operations(&caml_int32_ops);
caml_register_custom_operations(&caml_nativeint_ops);
caml_register_custom_operations(&caml_int64_ops);
parse_camlrunparam(); /* OCAMLRUNPARAM環境変数の解析 */
=> caml_init_gc (minor_heap_init, heap_size_init, heap_chunk_init,
percent_free_init, max_percent_free_init);
caml_page_table_initialize(Bsize_wsize(minor_size) + major_heap_size);
caml_set_minor_heap_size (Bsize_wsize (norm_minsize (minor_size)));
caml_major_heap_increment = Bsize_wsize (norm_heapincr (major_incr));
caml_percent_free = norm_pfree (percent_fr);
caml_percent_max = norm_pmax (percent_m);
caml_init_major_heap (major_heap_size);
init_atoms();
/* なんだろ?Initialize the atom table and the static data and code area limits. */
caml_init_signals();
/* スタックあふれ検出 */
sigsetjmp(caml_termination_jmpbuf.buf, 0); /* 例外かな? */
=> caml_start_program (asmrun/amd64.S)
/* r12-r15はcallee-savedなので、レジスタで取りまわすことはないはず... */
push caml_gc_regs
push caml_last_return_address
push caml_bottom_of_stack
/* caml_young_ptrをr15にロード */
/* caml_exception_pointerをr14にロード */
/* Exception handlerをr13にロード */
push r13
push r14
/* スタックポインタをr14にロード */
/* r12にentry_pointを取りcall */
=> caml_program (helloworld.startup.s)
=> camlPervasives__entry
=> register_named_value "Pervasives.do_at_exit" do_at_exit
=> caml_register_named_value /* プロセス終了時にflushするように */
caml_globals_inited += 1
=> camlHelloworld__entry /* <= OCamlコード */
ところで、上記のような解析をする際にはocamloptでコンパイルされた実行バイナリをobjdump -Sで見てみると良いでゲソ。 C言語の場合と同じように元のOCamlソースコードがまざって見えるので読みやすいでゲソ。 OCamlは本当に取り回しの良い、安心して使える言語でゲソ。 これが歴史の重みという奴じゃなイカ?
んー。やっぱり無茶苦茶多いじゃなイカ… でもこれ全部本当に必要なのカ? 機能を限定すれば削れそうなものもありそうでゲソ。 ちょっと今はocamloptを本格的に使うかわからないので、これらの詳細調査は遠慮したいでゲッソ。
もし使う時が来たら、また 簡約! λカ娘(4) - 参照透明な海を守る会 でやったようにランタイムのサイズ削減をやってみれば、自然に中身に手垢をつけられるはずでゲソ。 その時が楽しみでゲッソ!
$ size /usr/local/lib/ocaml/libasmrun.a|grep libasmrun.a|sort -k 6
1696 0 0 1696 6a0 alloc.o (ex /usr/local/lib/ocaml/libasmrun.a)
1108 24 0 1132 46c amd64.o (ex /usr/local/lib/ocaml/libasmrun.a)
3656 0 0 3656 e48 array.o (ex /usr/local/lib/ocaml/libasmrun.a)
1342 8 16 1366 556 backtrace.o (ex /usr/local/lib/ocaml/libasmrun.a)
1150 0 104 1254 4e6 callback.o (ex /usr/local/lib/ocaml/libasmrun.a)
3165 0 8 3173 c65 compact.o (ex /usr/local/lib/ocaml/libasmrun.a)
2585 16 6144 8745 2229 compare.o (ex /usr/local/lib/ocaml/libasmrun.a)
633 0 16 649 289 custom.o (ex /usr/local/lib/ocaml/libasmrun.a)
94 12 4 110 6e debugger.o (ex /usr/local/lib/ocaml/libasmrun.a)
421 0 0 421 1a5 dynlink.o (ex /usr/local/lib/ocaml/libasmrun.a)
6569 24 20640 27233 6a61 extern.o (ex /usr/local/lib/ocaml/libasmrun.a)
1272 40 40 1352 548 fail.o (ex /usr/local/lib/ocaml/libasmrun.a)
1815 0 56 1871 74f finalise.o (ex /usr/local/lib/ocaml/libasmrun.a)
3836 0 0 3836 efc floats.o (ex /usr/local/lib/ocaml/libasmrun.a)
2791 64 8056 10911 2a9f freelist.o (ex /usr/local/lib/ocaml/libasmrun.a)
4628 0 72 4700 125c gc_ctrl.o (ex /usr/local/lib/ocaml/libasmrun.a)
1524 0 476 2000 7d0 globroots.o (ex /usr/local/lib/ocaml/libasmrun.a)
2369 0 24 2393 959 hash.o (ex /usr/local/lib/ocaml/libasmrun.a)
7196 16 6240 13452 348c intern.o (ex /usr/local/lib/ocaml/libasmrun.a)
6343 184 0 6527 197f ints.o (ex /usr/local/lib/ocaml/libasmrun.a)
7217 56 40 7313 1c91 io.o (ex /usr/local/lib/ocaml/libasmrun.a)
938 0 0 938 3aa lexing.o (ex /usr/local/lib/ocaml/libasmrun.a)
80 0 0 80 50 main.o (ex /usr/local/lib/ocaml/libasmrun.a)
3960 0 80 4040 fc8 major_gc.o (ex /usr/local/lib/ocaml/libasmrun.a)
3529 0 0 3529 dc9 md5.o (ex /usr/local/lib/ocaml/libasmrun.a)
3256 0 40 3296 ce0 memory.o (ex /usr/local/lib/ocaml/libasmrun.a)
310 0 0 310 136 meta.o (ex /usr/local/lib/ocaml/libasmrun.a)
2373 0 200 2573 a0d minor_gc.o (ex /usr/local/lib/ocaml/libasmrun.a)
701 0 8 709 2c5 misc.o (ex /usr/local/lib/ocaml/libasmrun.a)
1721 0 0 1721 6b9 natdynlink.o (ex /usr/local/lib/ocaml/libasmrun.a)
1531 0 0 1531 5fb obj.o (ex /usr/local/lib/ocaml/libasmrun.a)
2220 0 4 2224 8b0 parsing.o (ex /usr/local/lib/ocaml/libasmrun.a)
917 0 0 917 395 printexc.o (ex /usr/local/lib/ocaml/libasmrun.a)
2316 8 80 2404 964 roots.o (ex /usr/local/lib/ocaml/libasmrun.a)
1347 24 32 1403 57b signals.o (ex /usr/local/lib/ocaml/libasmrun.a)
804 0 8200 9004 232c signals_asm.o (ex /usr/local/lib/ocaml/libasmrun.a)
1502 48 288 1838 72e startup.o (ex /usr/local/lib/ocaml/libasmrun.a)
1261 0 4 1265 4f1 str.o (ex /usr/local/lib/ocaml/libasmrun.a)
2706 36 8 2750 abe sys.o (ex /usr/local/lib/ocaml/libasmrun.a)
202 0 0 202 ca terminfo.o (ex /usr/local/lib/ocaml/libasmrun.a)
1532 0 0 1532 5fc unix.o (ex /usr/local/lib/ocaml/libasmrun.a)
2181 8 16 2205 89d weak.o (ex /usr/local/lib/ocaml/libasmrun.a)
まずはコールグラフを書いてみたでゲソ。
main
Arg.parse (Arch.command_line_options @ Options.list) anonymous usage;
=> process_file Format.err_formatter
=> process_implementation_file ppf name
=> Optcompile.implementation ppf name opref (* <= コンパイルパイプライン本体 *)
let (++) x f = f x in
let (+++) (x, y) f = (x, f y) in
let inputfile = Pparse.preprocess sourcefile in
Pparse.file ppf inputfile Parse.implementation ast_impl_magic_number
(* :: Parsetree.structure *)
++ print_if ppf Clflags.dump_parsetree Printast.implementation
(* :: Parsetree.structure -> Parsetree.structure *)
++ Typemod.type_implementation sourcefile outputprefix modulename env
(* :: Parsetree.structure -> Typedtree.structure * Typedtree.module_coercion *)
++ Translmod.transl_store_implementation modulename
(* :: Typedtree.structure * Typedtree.module_coercion -> int * Lambda.lambda *)
+++ print_if ppf Clflags.dump_rawlambda Printlambda.lambda
(* :: Lambda.lambda -> Lambda.lambda *)
+++ Simplif.simplify_lambda
(* :: Lambda.lambda -> Lambda.lambda *)
+++ print_if ppf Clflags.dump_lambda Printlambda.lambda
(* :: Lambda.lambda -> Lambda.lambda *)
++ Asmgen.compile_implementation outputprefix ppf;
(* :: int * Lambda.lambda -> unit *)
Compilenv.save_unit_info cmxfile; (* <= コンパイル済みcurrent_unitをファイル書き込み *)
=> Asmlink.link ppf (List.rev !objfiles) target;
make_startup_file ppf startup units_tolink; (* <= helloworld.startup.sファイル生成 *)
=> Proc.assemble_file startup startup_obj
Ccomp.command (Config.asm ^ " -o " ^
Filename.quote outfile ^ " " ^ Filename.quote infile)
(* gasでオブジェクトファイルに *)
call_linker (List.map object_file_name objfiles) startup_obj output_name;
=> Ccomp.call_linker mode output_name files c_lib;
少し混乱しやすいでゲソが、 Argモジュールは単にコマンド引数を解析するだけでなく、 標準ライブラリArgモジュール にある通り、anonymous関数を引数を適用するんでゲソ。
Optcompile.implementation関数がコンパイルパイプラインの本体で、 Haskellのdoと比較すると、 ++ が >> で、 +++ が >>= みたいな感じでゲソ。 あくまで感じであってモナドとかそんな高尚なもんじゃないでゲソ。 つまり内側から外側に向かって処理が進むようでゲソ。 最初ギョっとするでゲソが、慣れれば読みやすいでゲソ。 print_ifは無視できるので、コンパイルパイプラインを抜き出すと…
let inputfile = Pparse.preprocess sourcefile in
Pparse.file ppf inputfile Parse.implementation ast_impl_magic_number
(* :: Parsetree.structure *)
++ Typemod.type_implementation sourcefile outputprefix modulename env
(* :: Parsetree.structure -> Typedtree.structure * Typedtree.module_coercion *)
++ Translmod.transl_store_implementation modulename
(* :: Typedtree.structure * Typedtree.module_coercion -> int * Lambda.lambda *)
+++ Simplif.simplify_lambda
(* :: Lambda.lambda -> Lambda.lambda *)
++ Asmgen.compile_implementation outputprefix ppf;
(* :: int * Lambda.lambda -> unit *)
それでは、このコンパイルパイプラインに注目して再度コールグラフを書いてみようじゃなイカ。
Optcompile.implementation ppf name opref
=> let inputfile = Pparse.preprocess sourcefile in
let comm = Printf.sprintf "%s %s > %s" pp (Filename.quote sourcefile) tmpfile in
Ccomp.command comm (* 指定されたプリプロセッサコマンドにかける *)
=> Pparse.file ppf inputfile Parse.implementation ast_impl_magic_number
Location.input_name := inputfile;
let lexbuf = Lexing.from_channel ic in
Location.init lexbuf inputfile;
=> Parse.implementation lexbuf
=> wrap Parser.implementation (* パーサ本体 *)
=> ++ Typemod.type_implementation sourcefile outputprefix modulename env
let (str, sg, finalenv) = type_structure initial_env ast Location.none in
let simple_sg = simplify_signature sg in
check_nongen_schemes finalenv str.str_items;
normalize_signature finalenv simple_sg;
let coercion = Includemod.compunit sourcefile sg "(inferred signature)" simple_sg in
Typecore.force_delayed_checks (); (* 型推論の本体 *)
(str, coercion) (* :: Typedtree.structure * Typedtree.module_coercion *)
=> ++ Translmod.transl_store_implementation modulename
=> transl_store_gen module_name (str, restr) false
let (map, prims, size) = build_ident_map restr (defined_idents str) in
let f = function
| [ { str_desc = Tstr_eval expr } ] when topl ->
assert (size = 0);
subst_lambda !transl_store_subst (transl_exp expr)
| str -> transl_store_structure module_id map prims str in
transl_store_label_init module_id size f str
(* λ項に変換してるらしい、返値のintはサイズみたい *)
=> +++ Simplif.simplify_lambda
=> let lam = simplify_exits lam (* なんだろう? *)
=> simplify_lets lam
(* 最適化。だいたい以下?
* コンパイル時β簡約
* 未使用のletを削除。またlet参照を値にする *)
=> ++ Asmgen.compile_implementation outputprefix ppf;
(* λ項からアセンブリコードへ *)
Emit.begin_assembly(); (* アセンブラファイルのヘッダを書き込む *)
=> Closure.intro size lam (* :: Clambda.ulambda *)
let (ulam, approx) = close Tbl.empty Tbl.empty lam in ulam
(* アンカリー化、直接呼び出し *)
++ Cmmgen.compunit size (* :: Clambda.ulambda -> Cmm.phrase list *)
(* λ項からC--へ *)
++ List.iter (compile_phrase ppf) (* :: Cmm.phrase list -> unit *)
compile_phrase ppf
(Cmmgen.reference_symbols
(List.filter (fun s -> s <> "" && s.[0] <> '%')
(List.map Primitive.native_name !Translmod.primitive_declarations))
); (* 関数とデータをアセンブラファイルに書き込む *)
Emit.end_assembly(); (* アセンブラファイルのフッタを書き込む *)
Proc.assemble_file asmfile (prefixname ^ ext_obj) (* gasでオブジェクトファイルに *)
このコンパイルパイプラインが実際には一瞬で完了するのだから、すごいでゲソ。
blog comments powered by