guile (2)

Table of Contents

guile in ArchLinux

OpenBSDでguileをコンパイルしてるんだけど(gdbしたいのが、その理由)、い つまでたっても終了しない。2時間近く回しているのに、まだstage0の段階で すよ。stage0の次には、stage1,stage2が控えているから、何時終了しるか判 らない。

大体stageって何だ? かの昔、新らしい版のgccをコンパイルした時にも体験 したぞ。まず、種になる最低限の機能を持ったgccを使って、新しい版のgccを コンパイルする。これがstage0の段階。

stage1の段階になると、stage0で作成された(新らしい)gccを使って、もう一 度コンパイルする。 stage2では、stage1で出来たコンパイラーで、もう一度コンパイル。両者に差 が無いことを確認する。こうした厳重なチェックを経てコンパイラーがやっと、 一人前となる。とんでもない工数がかかるんだったな。まさか、昔やった事を やるとはな。

ここで問題になるのは、種のコンパイラー。卵が先か鶏が先か問題ね。 そして、こういうのも Gauche:VM命令セットの変更とビルド

guileもtar玉内に種のコンパイラーが内蔵されてる。*.goなんでバイナリーファ イルだ。作者さんが悪い事をやろうと思ったら、ここに悪意のあるコードを仕 込めばいいだろう。容易に悪事が発覚しないぞ。わざわざ逆アセする人なんて いないだろうから。

近頃の話題として、ウブの24.04リリースが遅れたってのがある。悪い人がxz ライブラリーに悪意のあるコードを埋めこんだ。それがたまたま発覚して、除 菌の為にディストロの再作成を余儀なくされた。そのせいで遅れた。 まあ、こんな危うい世界で生きているんだなあ。怖い事ですよ。 Linux環境で使用されている圧縮ツール「XZ Utils」のバックドアはどのように埋め込まれるのか

余談はともかくとして、 面倒なので、最速の環境でguileをコンパイルしてみる。

[sakae@arch guile-3.0.9]$ ./configure CFLAGS=-g
[sakae@arch guile-3.0.9]$ time make -j 2

real    53m44.953s
user    99m49.336s
sys     1m44.816s

2個のCPUに協力してもらって、1時間弱かかった。コンパイラー作りは、今も 昔も大変な作業なのね。

早速gdbしてみる。

[sakae@arch ~]$ gdb -q guile
Reading symbols from guile...
(gdb) r
Starting program: /usr/local/bin/guile
 :
(gdb) bt
#0  0x00007ffff799c6dc in read () from /usr/lib/libc.so.6
#1  0x00007ffff7ec6793 in ?? () from /usr/lib/libguile-3.0.so.1
 :
#26 0x00007ffff7f2e1cd in scm_with_guile () from /usr/lib/libguile-3.0.so.1
#27 0x00007ffff7ecffca in scm_boot_guile () from /usr/lib/libguile-3.0.so.1
#28 0x0000555555555311 in main (argc=1, argv=0x7fffffffe9b8)
    at /home/sakae/guile-3.0.9/libguile/guile.c:94

なんか変。libguileが、/usr/libにある物を使っているよ。

と言うことは、先客でguileが鎮座してるの?

[sakae@arch ~]$ ls -l /usr/bin/guile
-rwxr-xr-x 1 root root 14272 Jan 26  2023 /usr/bin/guile*

積極的に入れた覚えなんて無いんだけどな。どうだったか、確認してみる。

[sakae@arch ~]$ pacman -Qi guile
Name            : guile
Version         : 3.0.9-1
Description     : Portable, embeddable Scheme implementation written in C
Architecture    : x86_64
URL             : https://www.gnu.org/software/guile/
Licenses        : GPL
Groups          : None
Provides        : None
Depends On      : gmp  ncurses  libunistring  gc  libffi  libxcrypt
Optional Deps   : None
Required By     : gdb  gdb-common  make  riscv64-elf-gdb
Optional For    : None
Conflicts With  : None
Replaces        : None
Installed Size  : 52.98 MiB
Packager        : Frederik Schwan <freswa@archlinux.org>
Build Date      : Thu Jan 26 07:28:16 2023
Install Date    : Tue Jan 31 14:11:32 2023
Install Reason  : Installed as a dependency for another package

gdbやmakeでご利用なさっているのね。

[sakae@arch ~]$ ls -l `locate libguile-3.0.so.1.6.0`
-rwxr-xr-x 1 sakae users 3607208 Apr 28 14:51 /home/sakae/guile-3.0.9/libguile/.libs/libguile-3.0.so.1.6.0*
-rwxr-xr-x 1 root  root  1275096 Jan 26  2023 /usr/lib/libguile-3.0.so.1.6.0*
-rwxr-xr-x 1 root  root  3607208 Apr 28 15:48 /usr/local/lib/libguile-3.0.so.1.6.0*

オイラーが作成した物は、肥大化してる。多分debug用のコードを内蔵してる んだろうね。ならば、それを利用する様にguileを作成しなおせばいいだろう。 ちょいと、/usr/local/lib/pkgconfig/guile-3.0.pcを参考にしてみる。

gcc guile-guile.o -lgc -lpthread -ldl -lgmp  -lffi -lunistring -lcrypt  -lm -L/usr/local/lib -lguile-3.0
[sakae@arch libguile]$ ldd a.out
        linux-vdso.so.1 (0x00007fff49758000)
        libgc.so.1 => /usr/lib/libgc.so.1 (0x000071adedbf8000)
        libgmp.so.10 => /usr/lib/libgmp.so.10 (0x000071adedb52000)
        libffi.so.8 => /usr/lib/libffi.so.8 (0x000071adedb47000)
        libunistring.so.5 => /usr/lib/libunistring.so.5 (0x000071aded997000)
        libcrypt.so.2 => /usr/lib/libcrypt.so.2 (0x000071aded965000)
        libm.so.6 => /usr/lib/libm.so.6 (0x000071aded879000)
        libguile-3.0.so.1 => /usr/lib/libguile-3.0.so.1 (0x000071aded736000)
        libc.so.6 => /usr/lib/libc.so.6 (0x000071aded554000)
        /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x000071adedc7a000)

うーん、駄目だなあ。

[sakae@arch libguile]$ cc guile-guile.o -lgc -lpthread -ldl -lgmp  -lffi -lunistring -lcrypt  -lm   -Wl,-rpath=/usr/local/lib -L/usr/local/lib -lguile-3.0

ふとリンカー本を見てて-rpathなんてのが目にはいってきた(262ページ)。こ れが効くか半信半疑だけど、藁にも縋るつもりでやってみたら、

[sakae@arch ~]$ ldd /usr/local/bin/guile | grep libguile
        libguile-3.0.so.1 => /usr/local/lib/libguile-3.0.so.1 (0x0000701d47b05000)

効き目が有ったぞ。ldconfig一発で解決してた鴨。まあ結果オーライって事で。

#0  0x00007ffff79436dc in read () from /usr/lib/libc.so.6
#1  0x00007ffff7a8ffeb in fport_read (port=0x7ffff762b580, dst=0x7ffff762ca80,
    start=0, count=1) at /home/sakae/guile-3.0.9/libguile/fports.c:600
#2  0x00007ffff7ae9342 in scm_i_read_bytes (port=0x7ffff762b580,
    dst=0x7ffff762ca80, start=0, count=1)
    at /home/sakae/guile-3.0.9/libguile/ports.c:1566
#3  0x00007ffff7aebde5 in scm_fill_input (port=0x7ffff762b580, minimum_size=1,
    cur_out=0x7fffffffca60, avail_out=0x7fffffffca58)
    at /home/sakae/guile-3.0.9/libguile/ports.c:2693
  :
#36 0x00007ffff7b2693f in scm_with_guile (func=0x7ffff7a9d883 <invoke_main_func>,
    data=0x7fffffffe850) at /home/sakae/guile-3.0.9/libguile/threads.c:698
#37 0x00007ffff7a9d864 in scm_boot_guile (argc=1, argv=0x7fffffffe9b8,
    main_func=0x5555555551a9 <inner_main>, closure=0x0)
    at /home/sakae/guile-3.0.9/libguile/init.c:295
#38 0x0000555555555311 in main (argc=1, argv=0x7fffffffe9b8)
    at /home/sakae/guile-3.0.9/libguile/guile.c:94

real code

実コードで実験って事で、前回のUDPパケットを投げるのを関数にしてファイ ルに定義。そうしておいてgdbする。結構gdbへのサポートが有るんで、事前に 下記を ~/.gdbinitに登録しとく。

add-auto-load-safe-path /home/sakae/guile-3.0.9/.gdbinit
add-auto-load-safe-path /usr/local/lib/libguile-3.0.so.1.6.0-gdb.scm
[sakae@arch z]$ gdb -q guile
Reading symbols from guile...
(gdb) r -l udp.scm
 :
scheme@(guile-user)> ^C
Thread 1 "guile" received signal SIGINT, Interrupt.
0x00007ffff79436dc in read () from /usr/lib/libc.so.6
(gdb) b scm_send
Breakpoint 1 at 0x7ffff7b5bc77: file /home/sakae/guile-3.0.9/libguile/socket.c, line 1473.
(gdb) c
Continuing.
(udp)

Thread 1 "guile" hit Breakpoint 1, scm_send (
    sock=#<port #<port-type file 7ffff75f4b40> 7ffff4732a00>,
    message="#<vu8vector>" = {...}, flags=#<undefined 904>)
    at /home/sakae/guile-3.0.9/libguile/socket.c:1473
1473      sock = SCM_COERCE_OUTPORT (sock);

お決まりのバックトレレースする。

#0  scm_send (sock=#<port #<port-type file 7ffff75f4b40> 7ffff4732a00>,
    message="#<vu8vector>" = {...}, flags=#<undefined 904>)
    at /home/sakae/guile-3.0.9/libguile/socket.c:1473
#1  0x00007ffff7a960cb in scm_apply_subr (sp=0x7ffff7006c08, idx=562, nslots=4)
    at /home/sakae/guile-3.0.9/libguile/gsubr.c:482
#2  0x00007ffff7b34bc9 in vm_debug_engine (thread=0x7ffff75aed80)
    at /home/sakae/guile-3.0.9/libguile/vm-engine.c:972
#3  0x00007ffff7b3de8a in scm_call_n (proc=#<program 7ffff7594030>,
    argv=0x7fffffffd7b8, nargs=1) at /home/sakae/guile-3.0.9/libguile/vm.c:1615
#4  0x00007ffff7a7c200 in scm_primitive_eval (
    exp=((@ (ice-9 control) %) (begin (load-user-init) ((@@ (ice-9 command-line) load/lang) "udp.scm") ((@ (ice-9 top-repl) top-repl)))))
    at /home/sakae/guile-3.0.9/libguile/eval.c:671
#5  0x00007ffff7a7c2a9 in scm_eval (
    exp=((@ (ice-9 control) %) (begin (load-user-init) ((@@ (ice-9 command-line) load/lang) "udp.scm") ((@ (ice-9 top-repl) top-repl)))),
    module_or_state="#<struct module>" = {...})
    at /home/sakae/guile-3.0.9/libguile/eval.c:705
#6  0x00007ffff7b00564 in scm_shell (argc=3, argv=0x7fffffffe9b8)
    at /home/sakae/guile-3.0.9/libguile/script.c:357
:

仮想マシンにもdebug対応の奴と普通の物が有るみたいだよ。

jit

[sakae@arch guile-3.0.9]$ grep ENABLE config.h
#define ENABLE_JIT 1
#define ENABLE_NLS 1
#define ENABLE_REGEX 1
#define ENABLE_TMPNAM 1

configureの結果では、JITが有効になってた。guile-3.0で導入された。 JIT compilation is enabled by default on x86-64, i686, ARMv7, and AArch64 targets. と、案内されてた、自慢の種。

お馴染のコードで効用を調べてみる。

[sakae@arch z]$ cat fib.scm
(define (fib n)
  (if (< n 2) n
      (+ (fib (- n 1)) (fib (- n 2)))))

(fib 40)
[sakae@arch z]$ time GUILE_JIT_THRESHOLD=-1  guile fib.scm
real    0m38.445s
user    0m37.612s
sys     0m0.007s

[sakae@arch z]$ time GUILE_JIT_THRESHOLD=0  guile fib.scm
real    0m4.783s
user    0m4.671s
sys     0m0.007s

上段はjitをdisableした場合。下段は、即時にjitを有効にした場合。良く効 くコードを選んだんで、8倍高速になった。

このfib関数は、仮想マシンで43命令にコンパイルされて実行される。仮想命 令だから、内部的にはC言語での命令列になる。ならば、それをx86のアセンブ ラに変換してしまって、それを実行しちゃうのが良い。動作中にそれをやっちゃ う仕掛けがJITだ。ただ、やみくもにそれをやってしまうと、変換したはいい けど、3回しかそれが実行されなかったってんじゃ、変換時間がコストになっ てしまって、かえって遅くなる可能性がある。

だから、何回実行したらJITを発動させるかってのが制御できるようになって る。デフォルトでは1000回、同じ関数が呼ばれたら、発動するようになってる。 fibみたいな関数は、もう何千万回と呼出がかかるんで(引数によるけど)、顕 著に効果が表われるのさ。

下記は、jitが発動して、マシンコードの生成を始める所を、スナップショッ トしてみた図。

#0  compute_mcode (thread=0x7ffff75aed80, entry_ip=0x7ffff6768ec4,
    data=0x7ffff67ddd88) at /home/sakae/guile-3.0.9/libguile/jit.c:5929
#1  0x00007ffff7ac2eb3 in scm_jit_compute_mcode (thread=0x7ffff75aed80,
    data=0x7ffff67ddd88) at /home/sakae/guile-3.0.9/libguile/jit.c:6031
#2  0x00007ffff7b333f5 in vm_debug_engine (thread=0x7ffff75aed80)
    at /home/sakae/guile-3.0.9/libguile/vm-engine.c:411
#3  0x00007ffff7b3de8a in scm_call_n (proc=#<program 7ffff7594030>,
    argv=0x7fffffffd788, nargs=1) at /home/sakae/guile-3.0.9/libguile/vm.c:1615
#4  0x00007ffff7a7c200 in scm_primitive_eval (
    exp=((@ (ice-9 control) %) (begin (load-user-init) ((@@ (ice-9 command-line) load/lang) "fib.scm") ((@ (ice-9 top-repl) top-repl)))))
    at /home/sakae/guile-3.0.9/libguile/eval.c:671
#5  0x00007ffff7a7c2a9 in scm_eval (
    exp=((@ (ice-9 control) %) (begin (load-user-init) ((@@ (ice-9 command-line) load/lang) "fib.scm") ((@ (ice-9 top-repl) top-repl)))),
    module_or_state="#<struct module>" = {...})
    at /home/sakae/guile-3.0.9/libguile/eval.c:705
#6  0x00007ffff7b00564 in scm_shell (argc=3, argv=0x7fffffffe988)
    at /home/sakae/guile-3.0.9/libguile/script.c:357

このJIT機構は、emacsに導入されてたり、rubyなんかだと、色々な方式のJIT が利用できる様になってる。仮想マシンを高速化する魔法なんだな。

VM

そうそう仮想マシンがどうなってるかも、見ておくかな。

その前に、ArchLinuxは64Bitでgdbすると目がチカチカするから、32bit機でも gdb出来るようにする。

guile for OpenBSD

コンパイルで時間がかかるのは、stageNの部分。これは既にパッケージで入れ てあるんで、それを流用。欲しいのは、gdb情報を搭載したlibguileとそれを 利用したguileだ。これなら、そんなに時間を喰うことは無いだろう。

コンパイルはログを取っていて

gmake[2]: Entering directory '/usr/ports/pobj/guile3-3.0.9/guile-3.0.9/stage0'
GUILE_BOOTSTRAP_STAGE=stage0 ../meta/build-env guild compile --target="i386-unk\
nown-openbsd7.5" -W0 -O1 -L "/usr/ports/pobj/guile3-3.0.9/guile-3.0.9/module" -\
o "ice-9/eval.go" "../module/ice-9/eval.scm"
wrote `ice-9/eval.go'

これが出てきたら、gdb機能付きのguileは完成している。下記は、guileを作 成する主要部分だ。最終成果物は、libguile/.libs/guileに有る。

libtool: link: cc -shared  -fPIC -DPIC -o .libs/libguile-3.0.so.7.0  .libs/libg\
uile_3.0_la-alist.o .libs/libguile_3.0_la-array-handle.o .libs/libguile_3.0_la-\
array-map.o ....

/bin/sh ../libtool  --tag=CC   --mode=link cc -I/usr/local -I/usr/local/include\
 -pthread -Wall -Wmissing-prototypes -Wpointer-arith -fno-strict-aliasing -fwra\
pv -fvisibility=hidden -I/usr/local/include -g -flto -I/usr/local -I/usr/local/\
include -pthread  -o guile guile-guile.o libguile-3.0.la -lm

libtool: link: cc -I/usr/local -I/usr/local/include -Wall -Wmissing-prototypes \
-Wpointer-arith -fno-strict-aliasing -fwrapv -fvisibility=hidden -I/usr/local/i\
nclude -g -flto -I/usr/local -I/usr/local/include -o .libs/guile guile-guile.o \
 -L./.libs -lguile-3.0 -L/usr/local/lib -lgc -lffi -lintl -lunistring -liconv -\
lgmp -lm -pthread -Wl,-rpath,/usr/local/lib

libtoolの気紛れで、/usr/local/lib/libguile-3.0.so.7.0になってたので、 これを所定の場所に置けばOK。

libtool - コマンド (プログラム) の説明

libtool in Qiita

libtool --mode=execute gdb main

こういう使い方もできるとな。

run guile on OpenBSD

(fib 40)を実行させておいて、そのguileにgdbをアッタチすると言う技を使っ て、流れを追跡。下記の様にスレッドが2個作成されていた。

(gdb) info threads
  Id   Target Id         Frame
   1    thread 554302     _thread_sys_read () at /tmp/-:2
 * 2    thread 529496     0x0f301310 in scm_integer_add_ii (x=2, y=1) at integers.c:2842

thread 1の方はガベコレ用みたい。

(gdb) bt 6
#0  _thread_sys_read () at /tmp/-:2
#1  0x04f20ec6 in _libc_read_cancel (fd=7, buf=0x5836668c, nbytes=1) at /usr/src/lib/libc/sys/w_read.c:27
#2  0x0f2e231d in read_finalization_pipe_data (data=0x5836668c) at finalizers.c:215
#3  0x0c53572a in GC_do_blocking_inner () from /usr/local/lib/libgc.so.6.0
#4  0x0c529772 in GC_with_callee_saves_pushed () from /usr/local/lib/libgc.so.6.0
#5  0x0c52fbcc in GC_do_blocking () from /usr/local/lib/libgc.so.6.0
(More stack frames follow...)

thread 2 は、メインスレッドだな。

(gdb) bt
#0  0x0f301310 in scm_integer_add_ii (x=2, y=1) at integers.c:2842
#1  0x0f321d82 in sum (x=0xa, y=0x6) at numbers.c:4922
#2  0x0f321a93 in scm_sum (x=0xa, y=0x6) at numbers.c:5012
#3  0x0f37a9dc in vm_regular_engine (thread=0x681ece00) at ./vm-engine.c:1560
#4  0x0f3781f2 in scm_call_n (proc=0x5e99e020, argv=0xcf7f094c, nargs=1) at vm.c:1615
#5  0x0f2d3048 in scm_primitive_eval (exp=0x68290730) at eval.c:671
#6  0x0f2d312d in scm_eval (exp=0x68290730, module_or_state=0x682816e0) at eval.c:705
#7  0x0f348352 in scm_shell (argc=2, argv=0xcf7f14a4) at script.c:357
#8  0x15789b02 in inner_main (closure=0x0, argc=2, argv=0xcf7f14a4) at guile.c:50
#9  0x0f2f7676 in invoke_main_func (body_data=0xcf7f1428) at init.c:312
#10 0x0f2cc530 in c_body (d=0xcf7f1370) at continuations.c:430
#11 0x0f2d5ad7 in call_thunk (clo=0x6828d2f0) at exceptions.c:91
  :
#22 0x0f2cc4d7 in scm_c_with_continuation_barrier (func=0xf2f7620 <invoke_main_func>, data=0xcf7f1428) at continuations.c:464
#23 0x0f370fec in with_guile (base=0xcf7f13c4, data=0xcf7f13f0) at threads.c:649
#24 0x0c52fb60 in GC_call_with_stack_base () from /usr/local/lib/libgc.so.6.0
#25 0x0f370f20 in scm_i_with_guile (func=0xf2f7620 <invoke_main_func>, data=0xcf7f1428, dynamic_state=0x0) at threads.c:692
#26 0x0f370ebf in scm_with_guile (func=0xf2f7620 <invoke_main_func>, data=0xcf7f1428) at threads.c:698
#27 0x0f2f75c9 in scm_boot_guile (argc=2, argv=0xcf7f14a4, main_func=0x15789ad0 <inner_main>, closure=0x0) at init.c:295
#28 0x15789a9f in main (argc=2, argv=0xcf7f14a4) at guile.c:94

実例

仮想マシンがどう構成されてるか? 実例を見るのが一番だろう。fibな関数

1  (define (fib n)
2    (if (< n 2) n
3        (+ (fib (- n 1)) (fib (- n 2)))))

これをguileにロードして、逆アセしてみる。

ob$ guile -l fib.scm

scheme@(guile-user)> ,x fib
Disassembly of #<procedure fib (n)> at #x74b500e0:

   0    (instrument-entry 16344)                              at /tmp/fib.scm:1:0
   2    (assert-nargs-ee/locals 2 6)    ;; 8 slots (1 arg)
   3    (make-immediate 7 10)           ;; 2                  at /tmp/fib.scm:2:11
   4    (<? 6 7)                                              at /tmp/fib.scm:2:6
   5    (jl 36)                         ;; -> L2
   6    (static-ref 7 16336)            ;; #f                 at /tmp/fib.scm:3:10
   8    (immediate-tag=? 7 7 0)         ;; heap-object?
  10    (je 9)                          ;; -> L1
  11    (static-ref 7 16317)            ;; #<directory (guile-user) 4dbcc6e0>
  13    (static-ref 5 16321)            ;; fib
  15    (call-scm<-scm-scm 7 7 5 111)   ;; lookup-bound
  17    (static-set! 7 16325)           ;; #f
L1:
  19    (scm-ref/immediate 2 7 1)
  20    (call-scm<-scm-uimm 1 6 1 3)    ;; sub/immediate      at /tmp/fib.scm:3:14
  22    (handle-interrupts)                                   at /tmp/fib.scm:3:9
  23    (call 5 2)
  25    (receive 2 5 8)
  27    (scm-ref/immediate 7 7 1)                             at /tmp/fib.scm:3:24
  28    (call-scm<-scm-uimm 0 6 2 3)    ;; sub/immediate      at /tmp/fib.scm:3:28
  30    (mov 1 7)                                             at /tmp/fib.scm:3:23
  31    (handle-interrupts)
  32    (call 6 2)
  34    (receive 0 6 8)
  36    (call-scm<-scm-scm 7 5 7 0)     ;; add                at /tmp/fib.scm:3:6
  38    (reset-frame 1)                 ;; 1 slot
  39    (handle-interrupts)
  40    (return-values)
L2:
  41    (mov 7 6)
  42    (reset-frame 1)                 ;; 1 slot
  43    (handle-interrupts)
  44    (return-values)

一番左が、命令の格納されてるアドレス。よく見ると飛び飛びになってる所が ある。括弧内は、命令とその引数になる。引数の数も命令によって0個から4個 まで、バラバラである。;; 以降はコメント、場合によっては、対応するファ イル名と行番号、列番号が表示されてて、コードを読む時の良い指標になる。

Instruction Set

仮想マシンの説明が下記に載ってる。ちゃんと説明書が公開されてるって、某 Intelみたいだな。命令の種類によって区分けしてあるのも良い方針だ。

9.3.7 Instruction Set

8Bitで命令(オペコード)を指定し、後は引数(オペランド)になる。 オペランドのサイズと個数により、命令長は、変ってくる。 一番短かい命令長は、32Bitだ。これが基本長(1word)になる。命令長は、1か ら4wordまであった。そう言えばインテルの石も、可変長な命令だったね。

レジスターは、IP,SP,FPの3種。JITが有効になると、もう2個のレジスターが 追加されるみたいだ。

ひとつ命令を取り挙げて、どうなってるかソースにあたってみる。 vm-engine.c そして、対象は、

36   (call-scm<-scm-scm 7 5 7 0)     ;; add      at /tmp/fib.scm:3:6

にする。だって、上でgdbした時にひっかかってきたから。

/* call-scm<-scm-scm dst:8 a:8 b:8 IDX:32
 *
 * Call the SCM-returning instrinsic with index IDX, passing the SCM
 * locals A and B as arguments.  Place the SCM result in DST.
 */
VM_DEFINE_OP (52, call_scm_from_scm_scm, "call-scm<-scm-scm", DOP2 (X8_S8_S8_S8, C32))
  {
    uint8_t dst, a, b;
    SCM res;
    scm_t_scm_from_scm_scm_intrinsic intrinsic;

    UNPACK_8_8_8 (op, dst, a, b);
    intrinsic = intrinsics[ip[1]];

    SYNC_IP ();
    res = intrinsic (SP_REF (a), SP_REF (b));
    CACHE_SP ();
    SP_SET (dst, res);

    NEXT (2);
  }

コメントで命令が説明されてる。8Bitの引数3個と、32Bitの引数が1個必要で すとな。だから、全体としては、2wordの命令になりますと(DOP2)。 オペランドを命令の中からデコードする。1word目から、dst,a,bに分離。 2word目から演算の種類。そして、計算を実行して、結果をスタックにセット。 IPを2つ進めて、次の命令に備える。

仮想マシンはC言語のマクロを駆使して記述されている。その核心部分は、下 記の通り。

# define BEGIN_DISPATCH_SWITCH                  \
  vm_start:                                     \
    NEXT_HOOK ();                               \
    op = *ip;                                   \
  switch (op & 0xff)                            \
    {
# define END_DISPATCH_SWITCH                    \
    }
# define NEXT(n)                                \
  do                                            \
    {                                           \
      ip += n;                                  \
      goto vm_start;                            \
    }                                           \
  while (0)
# define VM_DEFINE_OP(opcode, tag, name, meta)  \
  op_##tag:                                     \
  case opcode:
#endif

マクロは展開してなんぼだ。cc -E vm-engine.c しよう。上記は、次のように 展開されてたぞ。

op_call_scm_from_scm_scm: case 52:
  {
    uint8_t dst, a, b;
    SCM res;
    scm_t_scm_from_scm_scm_intrinsic intrinsic;

    do { dst = (op >> 8) & 0xff; a = (op >> 16) & 0xff; b = op >> 24; } while (0);
    intrinsic = intrinsics[ip[1]];

    (&thread->vm)->ip = (ip);
    res = intrinsic ((sp[a].as_scm), (sp[b].as_scm));
    sp = (&thread->vm)->sp;
    (sp[dst].as_scm = res);

    do { ip += 2; goto vm_start; } while (0);
  }

なお、展開されて全体像は、下記のようになってた。

static SCM
VM_NAME (scm_thread *thread)
{
  register uint32_t *ip ;
  register union scm_vm_stack_element *sp ;
  register uint32_t op;
  void **intrinsics = (void**) &scm_vm_intrinsics;

# 297 "vm-engine.c"
  do { ip = (&thread->vm)->ip; sp = (&thread->vm)->sp; } while (0);
  do { ip += 0; goto vm_start; } while (0);
  vm_start: ; op = *ip; switch (op & 0xff) {;
# 311 "vm-engine.c"
  op_halt: case 0:
   :
# 3447 "vm-engine.c"
  op_call_scm_from_scmn_scmn: case 166:
  :
   op_unused_254: case 254:
   op_unused_255: case 255:
    {
      vm_error_bad_instruction (op);
      abort ();
    }
  };
}

もしデビ

巷では、もしトラって囁かれている。トランプ野郎が、大統領に返り咲いたらっ て仮定。日本の大親分がバイデンにヘーコラしに行ったと思ったら、もしを仮 定して子分の石炭富豪はトランプのご機嫌取りにでかけてる。どちらに転んで もいいように、対策してる訳だ。

バイデンは自分の事を大人だよ、トラ野郎は6才のわがままだと言ってるけど、 そりゃ身の程をわきまえていないな。大人も歳喰うと体も脳もボロボロになる 事を自覚してないぞ。どっちに転ぶんでしょうかね?

もしデビって、もしもデビアンにguileを入れるとしたら、、って仮定の事ね。 前回掘り出してきたguile入門の著者の方が、新版を入れる騒動記を公開され てたんで、ふと気になったのさ。

READMEによると

Guile depends on the following external libraries.
- libgmp
- libiconv
- libintl
- libltdl
- libunistring
- libgc
- libffi
It will also use the libreadline library if it is available.

これが必須の必要品。それぞれのヘッダーもコンパイル時には必要。

ChatGPT

Q: Debian系のパッケージは、libncurses と libncurses-dev の様に分離(lib とinclude)されています。何故ですか?

A: Debianパッケージがlibncursesとlibncurses-devのように分かれているのは、一般的にソフトウェアをビルドする際に必要なライブラリと、それを使ってアプリケーションをコンパイルするためのヘッダーファイル(インクルードファイル)を分けるためです。

  • libncurses: このパッケージには、実際のランタイムライブラリが含まれています。つまり、アプリケーションが実行されるときに必要な実行可能なコードが含まれています。
  • libncurses-dev: このパッケージには、アプリケーションをビルドするために必要なヘッダーファイルや開発ツールが含まれています。これには、ncursesを使用してアプリケーションを作成する際に必要な関数や定数の宣言が含まれています。

これにより、ユーザーがソフトウェアをインストールする際に、ランタイムに 必要な部分と開発時に必要な部分を分けて管理できます。開発者がコンパイル 時に必要なファイルのみを取得し、ランタイムには不要なファイルが含まれな いようにすることで、システムを軽量化し、不要なリソースの消費を避けるこ とができます。

Q: 今までの経験だと、xxx-devを入れるとxxxも関連でインストールされます。 上記の説明のような理想には程遠いようです。インストール時に、関連を抑制 する方法はありますか?

A: xxx-devパッケージをインストールするとxxxパッケージも一緒にインストールされる場合、その動作はディストリビューションやパッケージマネージャによって異なります。一般的には、開発パッケージが依存するランタイムパッケージを自動的にインストールする傾向があります。

しかし、特定のパッケージマネージャや環境によっては、この動作を抑制する方法が提供されていることがあります。以下にいくつかの一般的な方法を示します:

  1. –no-install-recommendsフラグの使用: DebianやUbuntuなどのAPTベースのシステムでは、`apt-get install`や`apt install`コマンドに`–no-install-recommends`フラグを追加することで、推奨されるパッケージをインストールしないように指定できます。例えば、`sudo apt install –no-install-recommends xxx-dev`とすることで、推奨されるパッケージをインストールせずにxxx-devパッケージだけをインストールできます。
  2. 依存関係を手動で解決する: 開発パッケージをインストールする際に、関連するランタイムパッケージが依存関係としてリストされる場合があります。この場合、開発パッケージをインストールする前に、関連するランタイムパッケージを手動でインストールすることで、重複を避けることができます。
  3. パッケージマネージャの設定変更: 一部のパッケージマネージャは、依存関係の解決方法をカスタマイズするための設定オプションを提供しています。これらのオプションを使用して、開発パッケージが依存するランタイムパッケージの自動インストールを無効にすることができます。ただし、これは特定のパッケージマネージャに固有の機能であり、利用可能であるかどうかは環境に依存します。

これらの方法を試してみて、開発パッケージのインストール時に不要な依存関 係を抑制できるかどうか確認してください。

Q: libxxxがインストール済で、libxxx-dev が未インストールと言うのを検出 して、自動でインストールするスクリプトを提示してください。

A: 以下は、Bashスクリプトの例です。このスクリプトは、libxxx-devがインストールされていない場合にのみlibxxx-devをインストールします。

#!/bin/bash

# チェックするパッケージ名
package_name="libxxx-dev"

# パッケージがインストールされているかどうかを確認
dpkg -s $package_name &> /dev/null

# ステータスコードを取得
status=$?

# インストールされていない場合はインストールを試みる
if [ $status -ne 0 ]; then
    echo "$package_name がインストールされていません。インストールを試みます..."
    sudo apt update
    sudo apt install $package_name
    # インストールが成功したかを確認
    if [ $? -eq 0 ]; then
        echo "$package_name をインストールしました。"
    else
        echo "エラー: $package_name のインストール中に問題が発生しました。"
        exit 1
    fi
else
    echo "$package_name は既にインストールされています。"
fi

exit 0

このスクリプトを実行すると、libxxx-devパッケージがインストールされてい ない場合に自動的にインストールを試みます。インストールが成功すると、 「 $package_name をインストールしました。」というメッセージが表示されま す。インストールに失敗した場合は、エラーメッセージが表示されます。


This year's Index

Home