parsing/parser.mly で構文木を生成し、 parse.ml でそれを処理する
typecore.ml で構文木読み込み。ここがメイン。型情報の前にstaticなcall graphを書きたい
関数呼出のパーズ。
Xmamewo:~/project/analyze_ocaml> drawMakefile -i .depend -g -sub typing/typecore > r.vcg
![]()
必要とされる側から必要とする側に枝を貼ったグラフで、 typing/typecore{ml, mli} から たどれる部分を描画している。上の三つの依存性に注目したい。
Typemod.type_structure -> Typecore.type_binding (* -> Typetexp.reset_type_variables(); *) -> type_let -> type_pattern_list -> unify_pat -> iter_pattern (when !Clflags.principal)
準備 ocaml-* ディレクトリで以下のコマンドを使ってコンパイルする。> ocamlc -I utils -I parsing `tsort_cmo .depend -g typing/typemod.ml -setE .cmo -e` -a -o typemod.cma
Type_mod.type_structure で型をつけてみる。つけた後とかどうなんの? customized_parsing ディレクトリで 以下のようにする# #use "init.ml";; ... # #load "typemod.cma" ;; # let sample = t ();; print expression let a = 1;; val sample : Parsetree.structure = ... # Typemod.type_structure Env.empty sample;; - : Typedtree.structure * Types.signature * Env.t = ([Typedtree.Tstr_value (Asttypes.Nonrecursive, [({Typedtree.pat_desc = Typedtree.Tpat_var <abstr>; Typedtree.pat_loc = {Location.loc_start = 4; Location.loc_end = 5; Location.loc_ghost = false}; Typedtree.pat_type = {Types.desc = Types.Tlink {Types.desc = Types.Tconstr (Path.Pident <abstr>, [], {contents = Types.Mnil}); Types.level = 100000000; Types.id = 29}; Types.level = 35; Types.id = 28}; Typedtree.pat_env = <abstr>}, {Typedtree.exp_desc = Typedtree.Texp_constant (Asttypes.Const_int 1); Typedtree.exp_loc = {Location.loc_start = 8; Location.loc_end = 9; Location.loc_ghost = false}; Typedtree.exp_type = {Types.desc = Types.Tconstr (Path.Pident <abstr>, [], {contents = Types.Mnil}); Types.level = 100000000; Types.id = 29}; Typedtree.exp_env = <abstr>})])], [Types.Tsig_value (<abstr>, {Types.val_type = {Types.desc = Types.Tlink {Types.desc = Types.Tconstr (Path.Pident <abstr>, [], {contents = Types.Mnil}); Types.level = 100000000; Types.id = 29}; Types.level = 35; Types.id = 28}; Types.val_kind = Types.Val_reg})], <abstr>)
parsing => typing => bytecomp parser->parse -> typemod -> translmod -> compile
この流れをうむものは driver ディレクトリ内で定義されている。
typing/typemod (型付け。型つきの木へ。Ident.t がでてくるということは名前から Id に変化するのかな?)
bytecomp/matching.ml (lub letのような静的パターンマッチング)
bytecomp/translmod.ml (Typedtree を lambda 式に変換)
Ident.t は抽象型であるけれども名前情報は持っている。createで渡す文字列は name フィールドに保存する。 (typing/ident.ml) そして Ident.name 関数で取り出せる。 まぁ、普通か。
clambda, printcmm なんかが asmcomp/ にある。中間言語の変換とか最適化かな (cmm は C--)
ここを触ってみたいと思った。 bytecomp/printlambda.ml があるのでとりあえず これを触ってみよう。その前に、もちろん lambda への変換が必要。で、 transl.mli をみてみるとval transl_implementation: string -> structure * module_coercion -> lambda
module_coercion ってなんだろう? これは Typedtree で定義されている。
じゃぁ、 transl_implementation ってどんなふうに使われるのかな?
ここまでくると、 driver/compile.ml までくるのであった。
transl_implementation の最初の文字列は モジュール名。
二番目の引数には Translmod.type_implementation の返り値をそのまま渡している。
{type, transl}_implementaion はコンパイル単位 (ファイル) に適用する。
tranls_structure は translmod.ml が公開していない。ちゃんと implementation をつかいますか。
Typemod.type_implementationの使用例Typemod.type_implementation sourcefile prefixname modulename env
simplif.ml にかけてみたい。とりあえず、ここまでのセッションをファイルに保存。
compile_session.txt
Xmamewo:~/test> ocamlopt.opt -dlambda dump_raw.ml -o dump_raw (seq (let (match/60 [0: 100 200]) (seq (setfield_imm 0 (global Dump_raw!) (field 0 match/60)) (setfield_imm 1 (global Dump_raw!) (field 1 match/60)))) (apply (field 27 (global Pervasives!)) (field 1 (global Dump_raw!))) 0a)もう名前が数字で表されたフィールド (オフセットのようだ) になっている。 (field 1 (global Dump_raw!)) は 直前で定義した b を表している。この オフセットと名前の対応をとりたいなぁ。例えばある型の値を文字列化したり、Serialize したり (保存できる形式にすること) するには型の関係を知らなければならない。例えばあるレコードを文字列化するには、 そのレコードのフィールドの文字列化の方法を知らなければならない。深くたどっていく うちに、ユーザー定義でないプリミティブな型にたどり着く。これらにはあらかじめ、 文字列化関数や、Serialize関数を対応付けておく。この構造をたどるのがメイン。 Polytypic とかいうのがにているトピック。
例えば、各基本型の表示関数 print_int, print_string, print_char は準備 されているものとして、ユーザー定義型(variant, record) の単純な表示関数を定義する。 単純なとは、プリミティブ型のフィールドはそれに対応する関数を呼び、そうで なければたどるということ。ぐっと範囲を狭めて Ocaml でそんなことをやってみようとする。そんなことをするには 多分型のついた後の抽象木が良いだろうと予測して、Typemod.type_structure で得られる 結果をいじることにする。
ちょっとした問題
引数に渡すEnv.tの取扱。 初期値と後で渡すときの環境
だいたいコンパイラ全体でこの部分はどのようにして使われているんだろう?
それよりもコンパイラは Typemod.type_implementation を使っている。これはファイル名、 プレフィックス名モジュール名と環境をとるようだ。環境は Compiile.initial_env () をつかっている。
あの、型でライブラリ関数を検索とかいうのはどうやってるのかちょっと興味があっただけ。
落書
Xmamewo|1:25|~/project/analyze_ocaml/otherlibs/labltk/browser> xvcg -silent -scale 80 -psoutput ocamlbrowser.ps ocamlbrowser.vcg
... gimp で変換 ...![]()
Search ラベルを手がかりにしてみてみる。
viewer.ml:428 search_string をSearchエントリボックスでリターンキーを押したときの ハンドラとして登録。
viewer.ml:163 search_string の定義
mode が "Type" だったら search_string_type ~mode:`Included
Exact なんてモードもある。
searchid.ml:280 search_string_type の定義。型は↓じゃ、searchid.ml から切り出してみよう。。。。。 topを作ろう。
val search_string_type : string -> mode:[< `Exact | `Included] -> (Longident.t * pkind) list type pkind = Pvalue | Ptype | Plabel | Pconstructor | Pmodule | Pmodtype | Pclass | Pcltype
otherlibs/labltk/browser> ocamlmktop -I ../../../parsing -I ../../../typing -I ../../../utils ../../../toplevel/toplevellib.cma list2.ml searchid.ml -o stop
toplevellib.cma を使うとそれなりに遊べそう〜。
うきゃ。できた。 search_type.ml
type_server.ml
型を表す値 (type_expr) を文字列にする
Printtyp モジュールでできる。 コンパイラを -i つきで起動したときにはこのモジュールの 関数が呼ばれて型が表示される。ocamlのバイトコード
例
type_server_with_type_annotation.ml
バイトコードインタプリタ ocamlrun を java で書き換えたら、ocamlのバイトコードが JVM上で走る。けど、 ocaml のバイトコードってどんなのだろう?
byterun/interp.c をみる
レジスタスタックは下に向かって延びる (-- が延びる方向)
pc sp accu accumulator env transp extra_args Next.. goto next_instr
ACCN (N = 0 .. 7) スタックのN番目の値を accuに入れる PUSH accu の値をスタックトップにつむ PUSHACCN (N = 0 .. 9) スタックトップに accu の値を積んで、つんだ後のスタックでN番目の値を accu に入れる。(PUSH と PUSHACC0は同じ) PUSHACC PUCHACC0, PUSHと同じ ACC スタックのをポップ。スタックのトップの値を accu に入れる POP sp += *pc++; !? ASSIGN sp[*pc++] = accu;
accu = Val_unit; !?ENVACC accu = Field(env, *pc++); ENVACCN (N = 1 .. 4) envのN番目の値をアキュムレーターに入れる PUSHENVACCN (N = 1 .. 4) accu の値をスタックにプッシュして、 accu に env の N番目の値を入れる PUSH_RETADDR 0: pc + *pc (戻りアドレス?)
1: env レジスタの内容
3: extra_args レジスタの内容APPLY extra_args = *pc - 1;
pc = Code_val(accu);
env = accu;APPLY, APPLYN (N = 1, 2, 3) 関数適用のようで。。。
extra_args: 引数の数 - 1
APPLY のときは *pc - 1 を入れている。。。。
accu の値をpc に入れているので APPLY 実行時には呼び出す関数の アドレスが accu に入っているのだろう。
スタックレイアウト (トップから)
引数1
...
引数N
pc
env
extra_argsAPPTERM, stackのレイアウトは!? APPTERMN (N = 1 .. 3) スタックレイアウト
sp = sp + *pc - 2; の後
引数をスタックに積む
accu に飛ぶ
env を accu の値にセットRETURN sp += *pc++ CLOSURE pc から nvars によみこみ。引数の数?
accu = pc + *pcCLOSUREREC PUSHOFFSETCLOSURE OFFSETCLOSURE PUSHOFFSETCLOSUREM2 OFFSETCLOSUREM2 PUSHOFFSETCLOSURE0 PUSHOFFSETCLOSURE2 accu の値をスタックにプッシュ OFFSETCLOSURE2 accu = env + 2 * sizeof (value) GETFIELDN (N = 0 .. 3) accu + Nを accu に読み込み GEFIELD accu + *pc を accu に読み込み BRANCH pc += *pc (相対ジャンプ) BRANCHIF accu の値がfalseでないならば pc += *pc (BRANCH) そうでなければ次の命令を実行 BRANCHIFNOT BRANCHIFNOT の条件部分を反転した命令 BOOLNOT accu の値を boolean 反転 CONSTN (N = 0 .. 3) accu に N を入れる PUSHCONSTN (N = 0 .. 3) accu の値をスタックに積んで、Nを accu に入れる PUSHCONSTINT acumulator の値をスタックに積む CONSTINT accu に *pc を入れる。(コードを読み込む?) NEGINT 2 - accu を accu に入れる。再下位ビットはタグで整数ならば1 ポインタならば0 C_CALLN (N = 1 .. 5) Cの関数を呼ぶ。第一引数はaccu第二引数はsp[1]!!(0ではない)
accuに結果を格納するC_CALLN ...? ADDINT, SUBINT, MULINT, DIVINT, MODINT
ANDINT, ORINT, XORINTaccu とスタックトップで演算を行い結果をaccuに入れる。スタックはポップされる。 LSLINT, LSRINT, ASRINT accu に入っている整数をスタックトップ にある整数分だけシフトする。スタックはポップされる。
結果を accu に入れる。整数としてタグがつけられる。EQ, NEQ, LTINT, .... (整数比較) accu とスタックトップを比較し、 結果を accu に入れる。スタックはポップされる。 BEQ, BNEQ, BLTINT .... (整数比較後分岐) 比較が真ならば pc += *pc (BRANCH) OFFSETINT accu += *pc << 1 OFFSETREF Field(accu, 0) += *pc << 1
accu がさすメモリ領域にpcの値を整数として入れる (1 bit shift)ISINT タグをみて整数か否かを調べる。結果は accuへ (accu & 1)
Fallthrough !? (PUSHACC, PUSHENVACC) Next; がなくて下に行く
演算結果は accumulator に入れる
curr_instr = *pc++
pc の型は int32
Cの外部関数を呼ぶ前に pc, sp, env を保存するbyterun で make ocamlrund とすることで、 -DDEBUG オプションつきで コンパイルされたバイトコードインタプリタ ocamlrund が作成される。 -t オプション つきで実行すると実行したバイトコード列を表示する
caml_main -> interprete
load_code (fix_code.c) で start_code へのコードよみこみ書式に関連しそうなところ
exec.h のコメントにデーターフォーマットの説明がある。基本的にセクションのあつまり になっていて、各セクションには4バイトの名前がつけられる。セクションの列のあとに目次 がつく。目次ではセクション番号とそのサイズがセクションの順に並べてかかれている。 最後は trailer。マジック番号とセクション数がかかれている。ocaml_main での処理
trailerを読む (attempt_open -> read_trailer)
セクション情報を読む (read_section_descriptors)
exec_trailer の section_descriptor はファイルから読んだセクション情報を格納するための場所。ここは section_descriptor の配列がはいる。 section_descriptor はセクションの名前 4 byte とセクションサイズ (unsigned int 4 byte) からなる。(からって num_sections * 8 って直書きはちょっと。。。。。)
セクション情報を読み終った後は seek_section, read_section でセクションを読める。
まず seek_section でコードセクションにファイルの位置をあわせる。このときコードセクションのサイズは seek_section の返り値からわかる。
load_code でバイトコードを読み込む。
プリミティブ関数の読み込み? PRIM セクションを読んで build_primitive_table をよぶ
DATAセクション読み込み
ファイルデスクリプタを Ocamlでのチャネルに変換。 Ocaml での値が読めるように (open_descriptor_in)
input_val で値を読み込み global_data という変数にいれる
close_channelでチャネルを閉じる。fd も閉じられる。attempt_open
read_section_descriptors
seek_section (fd, &trail, "CODE"); でコードサイズがわかる。。。
exec.h
まずはマジック番号、セクション数を書いたヘッダがある。exec.hのコメントと同じだけどファイルフォーマット
section1 section2 ... table_of_contents (toc) name_of_section1 size_of_section1 name_of_section1 size_of_section1 ... trailer number_of_sections magic
attempt_open
read_trailer
lseek -TRAILER_SIZE, SEEK_END (= ファイルサイズ+オフセットの位置にオフセットをあわせる)
つまりtrailer は最後に書かれている
TRAILER_SIZE は 4 + 12 と定義されている。
4 .. num_sections (uint32 = unsigned int)
12 .. magic
seek_section -> seek_optional_section
セクションの名前とサイズの配列が trailer.section にある。これを先頭から順に 読んで目的のセクションがでるまでサイズを足していき、オフセットを求める。目的のセクションの サイズを返す。セクションがなかった場合は -1 を返す。
read_section
目的のセクションを読んでそのデーターを返す。データの最後は 0 で区切られる。 セクションのサイズ + 1 のバッファを確保。