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とは直接関係ないけど、面白いものが出てきた。一期一会に なりそうなのでメモっておく。

gnu Serveez

socket

お手本から変更した部分は、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 in C

これいいな。最終的には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` に渡して次の処理を引き継ぎます。

### 利点

  1. 明示的な制御フロー: CPS変換によって、プログラムの制御フローが明示的になります。これにより、コンパイラーはより効率的な最適化が可能となります。
  2. 再帰の単純化: CPS変換を使用すると、再帰的な関数呼び出しをループや明示的な継続呼び出しに変換できます。これにより、再帰を扱うコードの最適化が容易になります。
  3. 高度な変換: 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変換は、コンパイラー設計やプログラム解析、最適化の研究で広く利用されており、その重要性は今後も増していくでしょう。


This year's Index

Home