More Related Content Similar to SECDマシン 実装と動きとその他もろもろについて
Similar to SECDマシン 実装と動きとその他もろもろについて (20) SECDマシン 実装と動きとその他もろもろについて10. SECDマシンの詳細: スタック
●
Stack (データスタック)
– 命令や関数への引数のやりとりに用いる
●
Environment (環境)
– 関数適用の実引数を積む場所
– 数字(添字)を使ってアクセスする
●
Controll (制御)
– 次に実行する命令列
– 木構造をしている(コンスセルでできている)
– プログラムカウンタに対応する
●
Dump
– 関数適用や条件分岐の終了時に戻ってくる先を保持
– じつは継続(この話はまたいつか)
11. SECDマシンの詳細: 命令一覧
●
ロード系命令
– ld, ldc, ldf
●
制御系命令
– ap, rtn, dum, rap
sel, join
●
コンスセル系命令
– cons, car, cdr
●
述語系命令
– eq, atom
●
数値演算・比較系命令
– add, sub, mul, div,
rem, leq
12. SECDマシンの詳細: 命令 (1)
●
ロード系命令
– ld, ldc, ldf
●
制御系命令
– ap, rtn, dum, rap
sel, join
●
コンスセル系命令
– cons, car, cdr
●
述語系命令
– eq, atom
●
数値演算・比較系命令
– add, sub, mul, div,
rem, leq
環境から
値をロード
定数を
ロード
関数を
ロード
13. SECDマシンの詳細: 命令 (2)
●
ロード系命令
– ld, ldc, ldf
●
制御系命令
– ap, rtn, dum, rap,
– sel, join
●
コンスセル系命令
– cons, car, cdr
●
述語系命令
– eq, atom
●
数値演算・比較系命令
– add, sub, mul, div,
rem, leq
関数適用
(再帰呼び出し)
関数適用
関数を抜ける
ダミー値を
環境に入れる
条件分岐 条件分岐を
抜ける
14. SECDマシンの詳細: 命令 (3)
●
ロード系命令
– ld, ldc, ldf
●
制御系命令
– ap, rtn, dum, rap
sel, join
●
コンスセル系命令
– cons, car, cdr
●
述語系命令
– eq, atom
●
数値演算・比較系命令
– add, sub, mul, div,
rem, leq
コンスセル
つくる
car部を取る cdr部を取る
オブジェクト
同値性!
アトムか否か
+, -, *, /,
%, <=
15. SECDマシンの詳細: 命令
●
ロード系命令
– ld, ldc, ldf
●
制御系命令
– ap, rtn, dum, rap,
sel, join
●
コンスセル系命令
– cons, car, cdr
●
述語系命令
– eq, atom
●
数値演算・比較系命令
– add, sub, mul, div,
rem, leq
これらの命令が
SECDマシンを
理解する上で重要
16. SECDマシンの詳細: ロード命令
●
ld (load)
– 環境から値をロードする
– 1つの定数を引数に取る
– 環境(Eスタック)のn番目の値をSスタックにpush
●
ldc (load constant)
– 引数の定数をSスタックにpush
●
ldf (load function)
– 引数に機械語コードを取る
– 引数のコードをSスタックにpush
– このとき実行時の環境をコピーしてコードと一緒に
push
●
これがレキシカルな束縛となる
17. SECDマシンの詳細: 制御命令
●
ap (apply)
– 関数適用するための命令
– やることは3つ
●
現在の実行状態の退避(戻り先の保持)
●
新しい環境を作って引数をpush
– カリー化されているので引数は1つだけであ
る点に注意
●
関数本体に入る準備(環境とコードを変更)
●
rtn (return)
– 関数本体の処理から抜ける命令
– apで退避した実行状態を復元し、
関数適用の次の処理を開始できるようにする
18. SECDマシンの詳細: 制御命令
●
dum (dummy)
– 環境のトップにダミー値を設定する
– これはrapで書き換えるためのプレースホルダ
– ldfの前(レキシカル環境を作る前)にdumする必要がある
●
rap (recursive apply)
– 再帰的に関数適用するための命令
– (dum)(ldf …) のように作られた関数に対して用いる
– apとの違いは
●
「新しい環境を作って引数をpush」するのではなく
●
「関数のレキシカル環境の先頭(ダミー値)を引数で上書き」する
●
以降の再帰呼び出しでもrapの引数は環境に置かれる
=参照できる
●
rapの引数を関数にするとその関数を無限に呼べる!
19. SECDマシンの詳細: 制御命令
●
sel (select)
– 条件分岐を行う命令
– コードを2つ引数に取る(true節とfalse節)
– やることは2つ
●
実行状態(EとC)をDにバックアップ
●
Sのトップの値によりCに設定するコードを変える
●
join (join)
– 分岐の後始末をする命令
– selの各節の最後に必ず置く
– Dにバックアップした実行状態(EとC)を元に戻す
30. SECDマシンの実装: 命令の定義(2)
●
命令はリスト
●
名前(0番目)と引数(1番目以降)を持つ
– 引数は値(定数)やコード(分岐など)を埋め込むとき用
;; https://github.com/t-sin/secdm/blob/master/vm.lisp
(defpackage #:secdm.op ; 命令のシンボルだけを入れる名前空間
(:use))
(defpackage #:secdm.symbol ; 命令以外のシンボルを入れる名前空間
(:use #:secdm.op))
;; 命令を定義するためのマクロ
(defmacro defop (name (&rest args) doc &body body)
(let ((fn-name (intern (symbol-name name) :secdm.op)))
`(progn
(defun ,fn-name ,args
,doc ,@body)
(export ',fn-name :secdm.op))))
32. SECDマシンの実装: 関数(2)
●
関数とはなにか
– 処理本体のマシン語
– 定義位置での束縛(レキシカルな束縛)
●
VMが扱えるデータで、
他のデータと区別が付けばよい
→じゃあリストでいいや!!!!!
;; https://github.com/t-sin/secdm/blob/master/vm.lisp
(defop ldf (vm code)
"Load a function to S register."
(let ((f (list :fn :code code :env (vm-e vm))))
(push f (vm-s vm))))
34. SECDマシンの実装: 関数(4)
●
実行コンテキスト(Sスタックや環境、コード)の切り替え
– 現在のコンテキスト退避(Dスタックに入れる)
– 引数を入れる環境の用意
– Cスタック(プログラム)の切り替え
;; https://github.com/t-sin/secdm/blob/master/vm.lisp
(defop ap (vm)
"Apply a function to a value on top of S register."
(let ((f (pop (vm-s vm)))
(v (pop (vm-s vm))))
(assert (eq (car f) :fn))
(let ((dump (list :s (vm-s vm) :e (vm-e vm) :c (vm-c vm)))
(env (append (list v) (vm-e vm))))
(setf (vm-s vm) nil
(vm-e vm) env
(vm-c vm) (getf (cdr f) :code))
(push dump (vm-d vm)))))
36. SECDマシンの実装: 再帰する(1)
●
やりかた
– dumを実行して環境にプレースホルダを置く
– 関数を引数に取る関数を定義する(1つめのldf)
– 再帰のスタート用関数を定義する(2つめのldf)
●
本体では、引数にきた関数をrapする
●
こうすると1つめの関数がずっと環境に残る(dumしたところ)
;; https://github.com/t-sin/secdm/blob/master/program/loop.l
(dum)
(ldc 0)
(ldf ((ld 0) (cdr) (ldc 10) (eq)
(sel ((ldc "stop counting up.") (println) (join))
((ld 0) (cdr) (println)
(ld 0) (cdr) (ldc 1) (add)
(ld 0) (car)
(cons)
(ld 0) (car) (ap)
(join)))
(rtn)))
(cons)
(ldf ((ld 0) (ld 0) (car) (rap)
(rtn)))
(ap)
(stop)
37. SECDマシンの詳細: 再帰する(2)
●
dum (dummy)
– 環境のトップにダミー値を設定する
– これはrapで書き換えるためのプレースホルダ
– ldfの前(レキシカル環境を作る前)にdumする必要がある
;; https://github.com/t-sin/secdm/blob/master/vm.lisp
(defop dum (vm)
"Load a dummy value to current enviroment.
This dummy value :omega will be replaced by `rap` operator."
(push :omega (vm-e vm)))
38. SECDマシンの詳細: 再帰する(3)
●
rap (recursive apply)
– apとの違いは
●
「新しい環境を作って引数をpush」するのではなく
●
「関数のレキシカル環境の先頭(ダミー値)を引数で上書き」する
●
以降の再帰呼び出しでもrapの引数は環境に置かれる
=参照できる
●
rapの引数を関数にするとその関数を無限に呼べる!
;; https://github.com/t-sin/secdm/blob/master/vm.lisp
(defop rap (vm)
(let ((f (pop (vm-s vm)))
(v (pop (vm-s vm))))
(assert (eq (car f) :fn))
(let ((dump (list :s (vm-s vm)
:e (cdr (vm-e vm)) ; :omegaは除く
:c (vm-c vm)))
(env (getf (cdr f) :env)))
(rplaca env v) ; car部を変更するCLの関数で:omegaを引数に変更
(setf (vm-s vm) nil
(vm-e vm) env
(vm-c vm) (getf (cdr f) :code))
(push dump (vm-d vm)))))
39. SECDマシンの実装: 分岐する(1)
●
分岐とは
– Sスタックの値により次に実行するコードを切り替えること
– 真と偽のときのコードは命令の引数で指定する
– つまり、以下のsel命令みたいになる
– もちろん、分岐の終了時に元のコンテキストに戻す必要あり
;; https://github.com/t-sin/secdm/blob/master/program/loop.l
(dum)
(ldc 0)
(ldf ((ld 0) (cdr) (ldc 10) (eq)
(sel ((ldc "stop counting up.") (println) (join))
((ld 0) (cdr) (println)
(ld 0) (cdr) (ldc 1) (add)
(ld 0) (car)
(cons)
(ld 0) (car) (ap)
(join)))
(rtn)))
(cons)
(ldf ((ld 0) (ld 0) (car) (rap)
(rtn)))
(ap)
(stop)
45. 参考文献
●
[1] SECDマシンとLispあれこれ - Arantium
Maestum
– https://zehnpaard.hatenablog.com/entry/
2018/12/23/075158
●
[2] SECD machine – Wikipedia
– https://en.wikipedia.org/wiki/SECD_machi
ne
●
[3] Peter Landin, The Mechanical
Evaluation of Expression
●
[4] Miloˇs Radovanovi, Mirjana Ivanovi, An
Implementation of LispKit Lisp in Java