elisp (2)

大掃除の季節。長年使っていた掃除機が壊れた。マーフィーの法則が適用されたんだな。

しょうがないので、ヤマダ君の所までひとっ走り。目に付いたのは、ダイソンの旋風だったか竜巻だったか。

五月蝿くて重くて高い(7万円)んで即却下。大体かの国の掃除機は五月蝿いので、夜間は掃除が禁止されているそうな。掃除機はきちんと掃除をすればいいのであって、五月蝿い事などお構いなくって事らしい。

ならば、おもてなしの日本製。竜巻バージョンに切り替わりつつあるのかな。ダイソンが高値を提示してるんで、心置きなく高値に誘導。いや、特許料が入っているんだろうね。

こういう物は試して買うのが常識。カーペットに、ゴミをばら撒き(掃除機のゴミボックスから)、それを心置きなく吸って試してみて下さいと、店員が愛想を振りまく。

家では、カーペットじゃなくて、ペルシャ絨毯(もどき)なんですけど、きちんと吸ってくれますかね? なんや、かんやで、軽いのが決め手になって、フィルター式のやつにした。1年分のフィルターが付いて、4万円なり。

吸込み口にLEDライトが付いている。炬燵で昼寝してる時、女房が掃除機をかけていた。小さい音なんで、気にならず。但し、吸込み口が目前に迫った時、思わず、でこトラックと言うか、電飾トラックに見えてしまって、思わず飛び起きたぞ。

elisp 資料

優しい Emacs-Lisp 講座

Emacs Lisp あれこれ

Emacs Lisp勉強会(基礎編)

GNU Emacs Lispリファレンスマニュアル

あ、geocitiesの方だ。ちゃんと移行されるのかなあ。(人の心配より自分の心配をしろよ)

obarrayなんて語句が出て来たので、C-h a で引いてみたけどHitせず。M-x aproで引いたら

Type RET on a type label to view its full documentation.

obarray
  Variable: Symbol table for use by `intern' and `read'.
  Properties: variable-documentation
obarray-default-size
  Variable: The value 59 is an arbitrary prime number that gives a
            good hash.
  Properties: variable-documentation risky-local-variable
obarray-get
  Function: Return symbol named NAME if it is contained in obarray OB.
obarray-make
  :

と、色々出てきたぞ。使う人と作る人で、使い分けているのか。こういうのは、手を動かしてみないと、なかなか分からんな。

emacs in OpenBSD

新しい版が出たと言うのに放置したままになっているOpenBSD6.3。それでも、doas syspatchすれば、穴を防ぐpatchが当てられているよ。まだ暫くUPGRADEしなくても大丈夫だな。

余り放置もよくないとおもってはいるんだけどね。少しはお相手するか。パッケージから入れた emacsが古いので、自前でコンパイルして入れちゃえ。

portsの人はどうしてるか、人のレシピを覗かしてもらいましょ。レシピってMakefileの事ね。

WANTLIB=                c m ncurses pthread gnutls xml2 z

.if ${FLAVOR} == "no_x11"
CONFIGURE_ARGS+=        --without-x \
                        --without-dbus \
                        --without-gconf \
                        --without-gsettings \
                        --without-jpeg
.else

フレーバーね。カッコいいな。何のトラブルもなくインストール出来た。

CONFIGURE_ARGS+=        --with-sound=no --without-cairo --without-dbus \
                        --without-gconf --without-gif --without-gsettings \
                        --with-x-toolkit=no --without-jpeg --without-lcms2 \
                        --without-m17n-flt --without-imagemagick \
                        --without-libotf --without-png \
                        --without-toolkit-scroll-bars --without-rsvg \
                        --without-tiff --without-x --without-xim --without-xpm \
                        --without-xwidgets

こちらは、FreeBSDのレシピ。OpenBSDと微妙に違っていて、面白いな。面白いと言えば、未だにcanna用のフラグが用意されてた。もう手に馴染んでいるんですかね。mozcなんて新興勢力だものね。

SUBR

emacsはelispで各種の編集機能を実現してる。じゃ、そのelispを動かす元になるのは何か? それはC語で書かれたelispのインタープリターだ。

昔LispやSICPをやった時、わずかな基本Lisp関数を使って、インタープリターを組み立てるって勉強した。基本は、car,cdr,cons,eqとかの類だ。スピードを稼ぐためC語になってる。こういうのをプリミティブ関数、Lisp用語ではSUBRって言うんだった。

ちょいとcarでも調べてみるか。ファンクション(関数)なんで、C-h f して、carって指定すれば、説明が出て来るはず。

car is a built-in function in `C source code'.

(car LIST)

Return the car of LIST.  If arg is nil, return nil.
Error if arg is not nil and not a cons cell.  See also `car-safe'.

See Info node `(elisp)Cons Cells' for a discussion of related basic
Lisp concepts such as car, cdr, cons cell and list.

C語で書かれたbuilt-inだと言っている。シングルクォートで囲まれた所がリンクになってる。 'C source code'って所にカーソルを持って行ってRETすると、リンク先へ飛べる。htmlのemacs風ってやつだ。

data.c

DEFUN ("car", Fcar, Scar, 1, 1, 0,
       doc: /* Return the car of LIST.  If arg is nil, return nil.
Error if arg is not nil and not a cons cell.  See also `car-safe'.

See Info node `(elisp)Cons Cells' for a discussion of related basic
Lisp concepts such as car, cdr, cons cell and list.  */)
  (register Lisp_Object list)
{
  return CAR (list);
}

ソースが残っていれば、ちゃんとcarの定義場所に案内してくれるぞ。ドキュメントも埋め込まれていて、それが出てきたんだな。

文字列のcarは、elisp上の名前、FcarはC語の関数名、Scarは、管理情報、次の2つの数字は、 引数の最低個数と、最大個数。次の0は、emacsのコマンドとして呼び出せるか否。

普通のlispとかだと、このあたりの情報は、ヘッダーファイルとかに表の形で置いてあって、起動時にそれを読み込んで設定する方式が多いな。こういうemacs方式だと、その場で定義なんで、楽なんだろうね。

それはそうと、DEFUNってのは、C語の常識ではマクロのはず。その定義を探してみる。

lisp.h

#define DEFUN(lname, fnname, sname, minargs, maxargs, intspec, doc)     \
   static struct Lisp_Subr sname =                              \
     { { PVEC_SUBR << PSEUDOVECTOR_AREA_BITS },                         \
       { .a ## maxargs = fnname },                                      \
       minargs, maxargs, lname, intspec, 0};                            \
   Lisp_Object fnname

定義されてるCARもマクロだな。定義はやっぱり、lisp.hでなされている。

INLINE Lisp_Object
CAR (Lisp_Object c)
{
  if (CONSP (c))
    return XCAR (c);
  if (!NILP (c))
    wrong_type_argument (Qlistp, c);
  return Qnil;
}

更に辿っていくと

#define lisp_h_XCAR(c) XCONS (c)->u.s.car
# define XCAR(c) lisp_h_XCAR (c)

まあ、大変だわ。

cent:src$ grep DEFUN *.c
alloc.c:DEFUN ("make-string", Fmake_string, Smake_string, 2, 2, 0,
alloc.c:DEFUN ("make-bool-vector", Fmake_bool_vector, Smake_bool_vector, 2, 2, 0,
alloc.c:DEFUN ("bool-vector", Fbool_vector, Sbool_vector, 0, MANY, 0,
alloc.c:DEFUN ("cons", Fcons, Scons, 2, 2, 0,
alloc.c:DEFUN ("list", Flist, Slist, 0, MANY, 0,
 :

沢山ありそうなので、個数だけ数えてみる。

cent:src$ grep DEFUN *.c | wc
   1439    8082  105003

emacs with gdb

~/.gdbinit

add-auto-load-safe-path /tmp/emacs-26.1/src/.gdbinit

こんな風に設定して、gdbを起動させた時に、gdbに機能を追加する

(gdb) print val
$35 = <value optimized out>
(gdb) xtype
Lisp_Int
(gdb) xint
$36 = 0
(gdb) 
 
まずは調べたい変数をprintで表示し、カレントオブジェクトの$に暗黙的にセットします。
次にLisp Typeをxtypeマクロで調べ、typeに合わせたマクロをxintマクロ等(他にはxstring,
xsymbolマクロ等があります)で呼び出しています。

資料によると、こんな風に使うみたい。

document ppt
Print current buffer's point and boundaries.
Prints values of point, beg, end, narrow, and gap for current buffer.
end

document xtype
Print the type of $, assuming it is an Emacs Lisp value.
If the first type printed is Lisp_Vector or Lisp_Misc,
a second line gives the more precise type.
end

document xpr
Print $ as a lisp object of any type.
end

山程、.gdbinitに登録されてた。

FevalにBPを置いてから、(+ 1 2 3)C-j してみた。

(gdb) ppt
BUF PT: 158 of 1..158 GAP: 158 SZ=1988
(gdb) p form
$1 = XIL(0x249185273)
(gdb) xlist
$2 = 0x8ee0
Lisp_Symbol
$3 = (struct Lisp_Symbol *) 0xa73500 <lispsym+36576>
"+"
---
$4 = 0x6
Lisp_Int1
$5 = 1
---
$6 = 0xa
Lisp_Int0
$7 = 2
---
$8 = 0xe
Lisp_Int1
$9 = 3
---
nil

ちゃんとS式が受け取られて、これから評価が始まるんだな。納得。

(gdb) p form
$11 = XIL(0x249185273)
(gdb) xpr
Lisp_Cons
$12 = (struct Lisp_Cons *) 0x249185270
{
  u = {
    s = {
      car = XIL(0x8ee0),
      u = {
        cdr = XIL(0x249185283),
        chain = 0x249185283
      }
    },
    gcaligned = 0xe0
  }
}

consの内部表現が構造体になってる。面白いな、将来の拡張に備えて余裕も持たせてありますって事なのかな。

(gdb) p Seval
$8 = {
  header = {
    size = 4611686018595160064,
    gcaligned = 0 '\000'
  },
  function = {
    a0 = 0x50d470 <Feval>,
    a1 = 0x50d470 <Feval>,
    a2 = 0x50d470 <Feval>,
    a3 = 0x50d470 <Feval>,
    a4 = 0x50d470 <Feval>,
    a5 = 0x50d470 <Feval>,
    a6 = 0x50d470 <Feval>,
    a7 = 0x50d470 <Feval>,
    a8 = 0x50d470 <Feval>,
    aUNEVALLED = 0x50d470 <Feval>,
    aMANY = 0x50d470 <Feval>
  },
  min_args = 1,
  max_args = 2,
  symbol_name = 0x58be6d "eval",
  intspec = 0x0,
  doc = 601500
}

これ、何だろう? lisp.hを見ればいいんだな。

/* This structure describes a built-in function.
   It is generated by the DEFUN macro only.
   defsubr makes it into a Lisp object.  */

struct Lisp_Subr
  {
    union vectorlike_header header;
    union {
      Lisp_Object (*a0) (void);
      Lisp_Object (*a1) (Lisp_Object);
        :
      Lisp_Object (*aUNEVALLED) (Lisp_Object args);
      Lisp_Object (*aMANY) (ptrdiff_t, Lisp_Object *);
    } function;
    short min_args, max_args;
    const char *symbol_name;
    const char *intspec;
    EMACS_INT doc;
  };

a1とかは、引数の評価結果でも保持するのかな?

(gdb) bt
#0  Feval (form=XIL(0x8673cab), lexical=XIL(0)) at eval.c:2050
 :
#37 0x0804ecff in main (argc=<optimized out>, argv=<optimized out>) at emacs.c:1713

Lisp Backtrace:
"eval" (0xbfd20174)
"posn-at-point" (0xbfd22f20)
"line-move-visual" (0xbfd231f8)
"line-move" (0xbfd23470)
"previous-line" (0xbfd236c8)
"funcall-interactively" (0xbfd236c4)
"call-interactively" (0xbfd23840)
"command-execute" (0xbfd23aac)

gdbのバックトレースが拡張されてる。.gdbinitの威力か? pythonの仕業?

cons

(gdb) xlist
$2 = 0x19f8
Lisp_Symbol
$3 = (struct Lisp_Symbol *) 0x846f298 <lispsym+6648>
"cons"
---
$4 = 0x9b662cc
Lisp_String
$5 = (struct Lisp_String *) 0x9b662c8
"HELLO"
---
$6 = 0x2c32
Lisp_Int0
$7 = 2828
---
nil

(cons "HELLO" 2828)をエバッた。次はFconsへ突入。

(gdb) p car
$9 = XIL(0x9b662cc)
(gdb) xpr
Lisp_String
$10 = (struct Lisp_String *) 0x9b662c8
"HELLO"
(gdb) p cdr
$11 = make_number(2828)
(gdb) xpr
Lisp_Int0
$12 = 2828

alloc.cの中。

    XSETCAR (val, car);
    XSETCDR (val, cdr);
    eassert (!CONS_MARKED_P (XCONS (val)));
    consing_since_gc += sizeof (struct Lisp_Cons);
  =>total_free_conses--;
    cons_cells_consed++;
    return val;

構造体にセットし終わって、フリーセルの個数を減算。使ってるcons数を増加。きっちりと管理してるのね。

              case 4:
                val = (XSUBR (fun)->function.a4
                       (argvals[0], argvals[1], argvals[2], argvals[3]));
  =>            break;

eval.cの中でfunはFconsなんだけど、引数が4個ってのが解せない。

(gdb) p argvals[0]
$20 = XIL(0x9b662cc)
(gdb) xpr
Lisp_String
$21 = (struct Lisp_String *) 0x9b662c8
"HELLO"
(gdb) p argvals[1]
$22 = make_number(2828)
(gdb) xpr
Lisp_Int0
$23 = 2828

これは、予想通り

(gdb) p argvals[2]
$24 = XIL(0x81385a9)
(gdb) xpr
Lisp_Misc
33279
(gdb) p argvals[3]
$25 = XIL(0x823f000)
(gdb) xpr
Lisp_Symbol
$26 = (struct Lisp_Symbol *) 0x106ac8a0
Cannot access memory at address 0x106ac8a4

ゴミと思しきものだなあ。はみ出し者が居るよ。まあ、ゴミと言う不都合な真実は見なかった事にして、ステップ実行していくと、bytecode.cの中に誘導されたぞ。

gc

メモリーの状況を32Bit版のdebian機で確認。結果はS式で帰って来るので、見やすいように手動pp。(自動のppって、何処かに無いかな?)

(garbage-collect)

((conses 8 195859 12313)
(symbols 24 23146 4)
(miscs 20 24 67)
(strings 16 62411 2290)
(string-bytes 1 1480807)
(vectors 12 18020)
(vector-slots 4 609075 20734)
(floats 8 52 289)
(intervals 28 51 226)
(buffers 536 7))

何の説明も無いけど、cadrは、オブジェクトのサイズだろうね。caddrは、現在の使用量だろう。次は、まだ割り当てられていない予備なんだろうね。

(dolist (e (garbage-collect))
  (prin1 e)
  (terpri))

(conses 16 258112 11727)
(symbols 48 30143 3)
(miscs 40 39 186)
(strings 32 80454 1417)
(string-bytes 1 2122850)
(vectors 16 23920)
(vector-slots 8 688154 38526)
(floats 8 130 337)
(intervals 56 1769 10)
(buffers 992 10)
nil

こちらは、amd64なOpenBSD機の結果。半自動ppを適用。オブジェクトサイズが32Bit版の2倍になってる物が多いな。

ガベッジコレクションに説明が有るけど、現在の状況と乖離してる。

disas

(defun dow (n)
  (+ n n n))
(disassemble 'dow)

byte code for dow:
  args: (n)
0       constant  +
1       varref    n
2       dup
3       dup
4       call      3
5       return

call 3 ってのは、引数の数を3個として演算しろって事らしい。不定個の引数を許す関数用に特別設計された、elispの為のバイトマシン命令なんだな。

スタックマシンなんで、

n n n +

って具合にスタックに積まれて、それが計算されるとな。forthを曲がりなりにも、かじっていて良かったな。詳しい事は、bytecode.c 当たりを参照かな。

#define BYTE_CODES                                                      \
DEFINE (Bstack_ref, 0) /* Actually, Bstack_ref+0 is not implemented: use dup.  */ \
DEFINE (Bstack_ref1, 1)                                                 \
  :
DEFINE (Bswitch, 0267)                                                  \
                                                                        \
DEFINE (Bconstant, 0300)

bytecodeって言うだけあって、コードは1byteになってるな。300って、1バイトに収まらないじゃんと言うなかれ。頭に0が付く数値は、8進数よ。そんなの知らんと言う人向けに、Hexに変換しとくと、0xC0 になるよ。

forthとlispのフィージョンの趣を呈しているよ。必見の価値有ですな。これと対になる コンパイラー系は、bytecomp.elかな。 zapしたら、5000行を超える大作でした。これを読むための対策をしなければな。

バイトコードの逆アセンブル

;;; lapcode generator
;;
;; the byte-compiler now does source -> lapcode -> bytecode instead of
;; source -> bytecode, because it's a lot easier to make optimizations
;; on lapcode than on bytecode.
;;
;; Elements of the lapcode list are of the form (<instruction> . <parameter>)
;; where instruction is a symbol naming a byte-code instruction,
;; and parameter is an argument to that instruction, if any.

lapcodeを、マシン語に落とせば、sbclに成るんかな。sbclって、鉄鋼銀行の支援を受けて開発されてるCLね。 Steel Bank Common Lisp

そんなに大上段に構えなくたって、 clパッケージで、つかの間のGNU Emacs Common Lisp Emulationをやるのが、お手頃かな。

This package is distributed with Emacs, so there is no need to install
any additional files in order to start using it.  Lisp code that uses
features from this package should simply include at the beginning:

     (require 'cl-lib)

safety

OpenBSDでemacs観光しようと思ったんだ。

ob6$ gdb -q emacs 44784
Reading symbols from emacs...done.
Attaching to program: /usr/local/bin/emacs, process 44784
ptrace: Operation not permitted.
/tmp/emacs-26.1/src/44784: No such file or directory.
SIGINT is used by the debugger.
  :

そしたら、セキュリティーに厳しくて、拒否されたよ。確かに、何処の馬の骨と思われる輩に、 アタッチさせるなんて、重大な脅威になるんで当然の処遇である。逆に言えば、他のOSはスカスカなセキュリティーって訳だ。

で、アタッチしたければ、権限を得てねって言うのがセオリー。

ob6$ doas gdb -q emacs 44784
doas (sakae@ob6.localdomain) password:
Reading symbols from emacs...done.
Attaching to program: /usr/local/bin/emacs, process 44784
Reading symbols from /usr/lib/libossaudio.so.4.0...done.
Reading symbols from /usr/lib/libcurses.so.14.0...done.
Reading symbols from /usr/local/lib/libgnutls.so.44.1...(no debugging symbols found)...done.
Reading symbols from /usr/lib/libm.so.10.1...done.
  :
Breakpoint 1 at 0x490e50: file emacs.c, line 363.
Temporary breakpoint 2 at 0x4ad190: file sysdep.c, line 1071.
(gdb)

これで使える。もっと手軽に世間一般のOS風にしちゃうかな。