guile
Table of Contents
guile udp
前回から、schemeを始め(再開)てみた。gauche一択でいいんだけど、OpenBSD とかではコンパイル失敗が決定してるんで、guileです。こちらがその The Guile Reference Manual です。
見様見真似で、UDPなパケットを投げるコード片を作成。
(use-modules (rnrs bytevectors)) (let ((s (socket AF_INET SOCK_DGRAM 0))) (connect s AF_INET (inet-pton AF_INET "127.0.0.1") 5555) (send s (string->utf8 "Hello OpenBSD\r\n") ))
最初は、途中でハングですよ。んで、なんでかなとネットを漁ってみたのさ。 そしたら、schemeとは直接関係ないけど、面白いものが出てきた。一期一会に なりそうなのでメモっておく。
お手本から変更した部分は、sendの所だ。変更した部分が怪しい。infoすると 、
-- Scheme Procedure: send sock message [flags] -- C Function: scm_send (sock, message, flags) Transmit bytevector MESSAGE on socket port SOCK. SOCK must already be bound to a destination address. The value returned is the number of bytes transmitted_it's possible for this to be less than the length of MESSAGE if the socket is set to be non-blocking. The optional FLAGS argument is a value or bitwise OR of `MSG_OOB', `MSG_PEEK', `MSG_DONTROUTE' etc. Note that the data is written directly to the socket file descriptor: any unflushed buffered port data is ignored.
メッセージはバイトベクターだよってのを軽く考えていて、#"Hello.." とし てたんだ。これでもエラーにならずにハングになっていたので、UDP独自の作 法が有るかと、さ迷った次第。改めてバイトベクターを調べて、事無きを得た 次第。ちょいと悔しいので、ソース読めしてみます。
libguile/socket.c
SCM_DEFINE (scm_send, "send", 2, 1, 0, (SCM sock, SCM message, SCM flags), "Transmit bytevector @var{message} on socket port @var{sock}.\n" : "This operation is defined only for strings containing codepoints\n" "zero to 255.") #define FUNC_NAME s_scm_send { int rv, fd, flg; sock = SCM_COERCE_OUTPORT (sock); SCM_VALIDATE_OPFPORT (1, sock); if (SCM_UNBNDP (flags)) flg = 0; else flg = scm_to_int (flags); fd = SCM_FPORT_FDES (sock); SCM_VALIDATE_BYTEVECTOR (1, message); SCM_SYSCALL (rv = send (fd, SCM_BYTEVECTOR_CONTENTS (message), SCM_BYTEVECTOR_LENGTH (message), flg)); if (rv == -1) SCM_SYSERROR; scm_remember_upto_here_1 (message); return scm_from_int (rv); }
*.go
/usr/local/bin/guild3.0なんてのが有るんだけど、manでは言及されていない。 でも、helpしたら、どうもコンパイル系って事が分ったので、ちと実験。
ob$ cat >z.scm (define (bai+ a b)(+ a (* b b))) ob$ guild3.0 compile z.scm wrote `/home/sakae/.cache/guile/ccache/3.0-LE-4-4.6/home/sakae/z.scm.go'
超簡単な関数を定義して、それをコンパイル。サフィックスがgoになるんか。
ob$ file z.scm.go z.scm.go: ELF 32-bit LSB shared object, no machine, version 1
ふーん、ELFフォーマットになるっては、良い選択と思うよ。
ob$ readelf -h /usr/local/bin/gdb ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: Intel 80386 :
アプリのELFヘッダーの例
ob$ readelf -h z.scm.go ELF Header: Magic: 7f 45 4c 46 01 01 01 ff 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: Standalone App ABI Version: 0 Type: DYN (Shared object file) Machine: None :
そして、こちらは、コンパイルされたコードのヘッダー例。emacsやsbclみた いに、独自形式を放棄してロードとかの扱かいを標準化するって見識が有るな あ。
で、逆変換もで きるみたいなんで、試してみる。
ob$ guild3.0 disassemble /home/sakae/.cache/guile/ccache/3.0-LE-4-4.6/home/sakae/a.scm.go Disassembly of <unnamed function> at #x98: 0 (instrument-entry 16354) at z.scm:1:0 2 (assert-nargs-ee/locals 1 1) ;; 2 slots (0 args) 3 (call-scm<-thread 1 62) ;; current-module 5 (static-ref 0 16345) ;; bai+ 7 (call-scm<-scm-scm 1 1 0 41) ;; define! 9 (make-non-immediate 0 16343) ;; #<procedure bai+ (a b)> 11 (scm-set!/immediate 1 1 0) 12 (make-immediate 1 2052) ;; #<unspecified> 13 (reset-frame 1) ;; 1 slot 14 (handle-interrupts) 15 (return-values) Disassembly of bai+ at #xd8: 0 (instrument-entry 16342) at z.scm:1:0 2 (assert-nargs-ee/locals 3 0) ;; 3 slots (2 args) 3 (call-scm<-scm-scm 2 0 0 4) ;; mul at z.scm:1:23 5 (call-scm<-scm-scm 2 1 2 0) ;; add at z.scm:1:18 7 (reset-frame 1) ;; 1 slot 8 (handle-interrupts) 9 (return-values) Disassembly of <unnamed function> at #x100: 0 (instrument-entry 16336) at z.scm:1:18 2 (assert-nargs-ee/locals 1 1) ;; 2 slots (0 args) 3 (static-patch! 16318 15) 6 (make-non-immediate 1 16314) ;; "bai+" 8 (call-scm<-scm 1 1 14) ;; string->symbol 10 (static-set! 1 16314) ;; bai+ 12 (static-patch! 16315 -22) 15 (reset-frame 1) ;; 1 slot 16 (make-immediate 0 2052) ;; #<unspecified> 17 (return-values)
仮想マシンのコードなんだな。じゃ、同じ関数をインタープリタ上で定義して、 それを逆アセンブルしてみる。
scheme@(guile-user)> (define (bai+ a b)(+ a (* b b))) scheme@(guile-user)> ,x bai+ Disassembly of #<procedure bai+ (a b)> at #x6c9218d8: 0 (instrument-entry 56) at (unknown file):1:0 2 (assert-nargs-ee/locals 3 0) ;; 3 slots (2 args) 3 (call-scm<-scm-scm 2 0 0 4) ;; mul at (unknown file):1:23 5 (call-scm<-scm-scm 2 1 2 0) ;; add at (unknown file):1:18 7 (reset-frame 1) ;; 1 slot 8 (handle-interrupts) 9 (return-values)
,c bai+ すれば、その場でコンパイルもできるのね。
debug, trace and profile …
replから、簡単にオペレーションできるdebuggerが用意されてる。
debug
scheme@(guile-user)> ,help debug Debug Commands [abbrev]: ,backtrace [COUNT] [#:width W] [#:full? F] [,bt] - Print a backtrace. ,up [COUNT] - Select a calling stack frame. ,down [COUNT] - Select a called stack frame. ,frame [IDX] [,fr] - Show a frame. ,locals - Show local variables. ,error-message [,error] - Show error message. ,break PROCEDURE [,br ,bp] - Break on calls to PROCEDURE. ,break-at-source FILE LINE [,break-at ,bs] - Break when control reaches the given source location. ,step [,s] - Step until control reaches a different source location. ,step-instruction [,si] - Step until control reaches a different instruction. ,next [,n] - Step until control reaches a different source location in the current frame. ,next-instruction [,ni] - Step until control reaches a different instruction in the current frame. ,finish - Run until the current frame finishes. ,tracepoint PROCEDURE [,tp] - Add a tracepoint to PROCEDURE. ,traps - Show the set of currently attached traps. ,delete IDX [,del] - Delete a trap. ,disable IDX - Disable a trap. ,enable IDX - Enable a trap. ,registers [,regs] - Print registers.
残念な事に、このdebuggerではプリミティブな関数にはbreakが反応しない。 で、ちょっと細工をする。
(define (my* x y) (* x y)) (define (bai+ a b)(+ a (my* b b)))
何の事は無い。* をラッピングしただけ。そうしておいてmy*にBPを追いて実 行。
scheme@(guile-user)> ,br my* Trap 0: Breakpoint at #<procedure my* (x y)>. scheme@(guile-user)> (bai+ 3 4) Trap 0: Breakpoint at #<procedure my* (x y)> Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue. scheme@(guile-user) [1]> ,bt In current input: 10:39 1 (bai+ 3 _) 8:16 0 (my* 4 4) scheme@(guile-user) [1]> ,q $6 = 19
breakってTrapの仮の姿なのね。ちょっと新鮮な面持ちだよ。主関数を実行す ると、トラップが発生する。そしてreplは一段深いレベルに移行。バックトレー スしてみると、呼出の履歴が表示された。継続するには、replの深い階層から 抜け出る事って事で、,qを入力。フレーム番号1の所の _ は、まだ未評価です よって印なんだな。
trace
トレースも取れーす。
scheme@(guile-user)> (define (fact1 n) (if (zero? n) 1 (* n (fact1 (1- n))))) scheme@(guile-user)> ,trace (fact1 3) trace: | (fact1 3) trace: | | (fact1 2) trace: | | | (fact1 1) trace: | | | | (fact1 0) trace: | | | | 1 trace: | | | 1 trace: | | 2 trace: | 6
一度 ,tr 設定した関数を解除する方法は提供されてないみたい。まあ、トレー スしなけりゃ、それで済む話だけどね。
scheme@(guile-user)> ,time (fact1 30) $10 = 265252859812191058636308480000000 ;; 0.034000s real time, 0.026000s run time. 0.000000s spent in GC.
profile
プロファイルも簡単に採取できる。
(use-modules (statprof)) (statprof (lambda () (map 1+ (iota 500000)) #f))
こんな、ありがちなコードをrepl上から実行。
Loading /tmp/z.scm ... % cumulative self time seconds seconds procedure 57.50 54565.94 0.18 ice-9/boot-9.scm:220:5:map1 20.00 0.06 0.06 1+ 17.50 0.07 0.05 ice-9/boot-9.scm:904:0:iota 5.00 0.02 0.02 %after-gc-thunk 0.00 0.31 0.00 /tmp/z.scm:2:15 0.00 0.02 0.00 anon #x68a610f4 --- Sample count: 40 Total time: 0.31 seconds (2.081 seconds in GC)
後、repl上の統計情報
scheme@(guile-user)> ,stat GC times: 30 / 33 times Memory size: current / limit heap 243286016 / 369631232 bytes Bytes allocated: diff / total allocated 930464488 / 932018324 bytes GC time taken: diff / total total 2.43 / 2.44 s Process time spent: diff / total user 27.69 / 27.77 s system 0.70 / 0.70 s child user 0.00 / 0.00 s child system 0.00 / 0.00 s
pp
コンパイル時の情報が保持されてる。
scheme@(guile-user)> %guile-build-info $6 = ((buildstamp . "") (CFLAGS . " -I/usr/local -I/usr/local/include -pthread") (LIBS . " -lm") ...
が、得意のアレになってて見るに耐えない。そんな時は、綺麗に表示してよね、 だ。
scheme@(guile-user)> ,pp %guile-build-info $7 = ((buildstamp . "") (CFLAGS . " -I/usr/local -I/usr/local/include -pthread") (LIBS . " -lm") (libguileinterface . "7:0:6") (guileversion . "3.0.9") (extensiondir . "/usr/local/lib/guile/3.0/extensions") (pkgincludedir . "/usr/local/include/guile") (pkglibdir . "/usr/local/lib/guile") : (prefix . "/usr/local") (top_srcdir . "/pobj/guile3-3.0.9/guile-3.0.9") (srcdir . "/pobj/guile3-3.0.9/guile-3.0.9/libguile"))
lang
scheme@(guile-user)>
今迄余り気にする事が無かったけれど、replのプロンプトにschemeって表示し てるのは何故? 気になったので調べてみると、
6.24 Support for Other Languages ================================ In addition to Scheme, a user may write a Guile program in an increasing number of other languages. Currently supported languages include Emacs Lisp and ECMAScript.
欲張りな事に、scheme以外にelispとJavaScriptもサポートしてるようだ。
scheme@(guile-user)> ,L elisp Happy hacking with Emacs Lisp! To switch back, type `,L scheme'.
elispに変身する。
elisp@(guile-user)> (eq 1 2) $1 = #f elisp@(guile-user)> ,L scheme Happy hacking with Scheme! To switch back, type `,L elisp'. scheme@(guile-user)> (eq 1 2) ;;; <stdin>:5:1: warning: possibly unbound variable `eq' ice-9/boot-9.scm:1685:16: In procedure raise-exception: Unbound variable: eq
elispにはeqって関数が存在するので、ちゃんと利用できる。でもscheme側に は、そんな関数が存在しないのでエラーになった。
scheme@(guile-user) [1]> ,q scheme@(guile-user)> (= 1 2) $2 = #f
脳内変換しろよとな。脳が沸騰しそうなので、封印しとく。
(guile-user)って表示されてるプロンプトの部分は?
scheme@(guile-user)> ,use (ice-9 readline) scheme@(guile-user)> ,m (ice-9 readline)
guile in C
皆さん、どんな風にguileを利用してるか調べてみた。
Guile 入門(pdf)
これいいな。最終的にはguileの核になってるlibguileを使うしか。組み込み の世界。作図ソフトで有名なgimpだったかもも、取り込んでいたはず。
guileの歴史によると、emacsをguileで実現したかったみたいだ。Lisp-2より Lisp-1であるschemeの方が綺麗だしね。でも諸般の事情で実現できなかったみ たい。
そんな不幸な例は、巷に溢れているね。例えば、西日本の60Hzと東日本の50Hz 問題。だれがどう考えたって不合理なのに、統一の機会を逸してしまった。ま あ、そんなものか。
CPS
Q: コンパイラーの技法の一つであるCPS変換について、説明して ください。
A: CPS(Continuation-Passing Style)変換は、プログラムの制御フローを明示的な継続(continuation)をパラメータとして受け取る形式に変換する技法です。CPS変換はコンパイラーの最適化や高度な変換技法の基盤としても広く利用されています。以下に、CPS変換の基本的なアイデアとその利点、適用例について詳しく説明します。
### 基本的なアイデア
CPS変換の基本的な考え方は、関数呼び出しの戻り値として「次に実行すべき処理」を示す継続を明示的に渡すことです。通常の関数呼び出しでは、関数が計算を行い、その結果を返します。しかし、CPS変換を適用すると、関数は結果を継続に渡し、制御を次の処理に移すようになります。
例えば、通常の関数呼び出し `f(x)` の場合、CPS変換後は `f(x, k)` となります。ここで `k` は継続を表します。`f` は計算を行った後、結果を `k` に渡して次の処理を引き継ぎます。
### 利点
- 明示的な制御フロー: CPS変換によって、プログラムの制御フローが明示的になります。これにより、コンパイラーはより効率的な最適化が可能となります。
- 再帰の単純化: CPS変換を使用すると、再帰的な関数呼び出しをループや明示的な継続呼び出しに変換できます。これにより、再帰を扱うコードの最適化が容易になります。
- 高度な変換: CPS変換は、コルーチンや非同期プログラミング、例外処理など、高度なプログラム構造の変換にも適用できます。
### 適用例
以下に、簡単なC言語の関数をCPS変換する例を示します。
通常の関数: ```c int add(int a, int b) { return a + b; } ```
CPS変換後: ```c void addcps(int a, int b, void (*k)(int)) { k(a + b); } ```
この例では、`addcps`関数は結果を継続 `k` に渡しています。継続 `k` は、計算結果を受け取り次の処理を行う関数を表します。
### まとめ
CPS変換は、プログラムの制御フローを明示的にし、コンパイラーの最適化や高度な変換技法の基盤として非常に有用です。明示的な継続を使用することで、再帰的なコードの最適化や高度なプログラム構造の変換が容易になります。CPS変換は、コンパイラー設計やプログラム解析、最適化の研究で広く利用されており、その重要性は今後も増していくでしょう。