nptclのブログ

Common Lisp処理系nptの開発メモです。https://github.com/nptcl/npt

npt amalgamationを作り直す

開発のメモです。
npt amalgamationとは、sqliteのamalgamationの考え方をまねて作ったものであり、 nptのソースを1つのファイルに結合するというものです。

現時点でnptのソースは770個くらいですが、amalgamationを使えば

  • lisp.c
  • lisp.h
  • shell.c

の3つになるというもの。
これはこれで良かったのですが、実際に開発に使ってみると、 ソースファイルが大きすぎてデバッグ時にまともに動かなくなりました。
gdbでソースを参照するときにものすごく時間がかかります。
こんな感じ。

$ cc -g -DLISP_FREEBSD -DLISP_DEBUG lisp.c shell.c -lm
$ gdb a.out
(gdb) b lispd_invoke_handler_control_
Breakpoint 1 at 0x3b0b70: file lisp.c, line 120648.
(gdb) r
Starting program: npt/develop/amalgamation/a.out
(error "Hello")
★★★ここから
Breakpoint 1, lispd_invoke_handler_control_ (ptr=0x800a83140,
    instance=0x80127efd0 "\003\003") at lisp.c:120648
120648          next = ptr->control;
★★★ここまで15

うちのパソコンが遅いだけだとか、 nptのデバッグなんて全世界探しても製作者一人しかやらねえよとか、 そう言う正論もあると思いますが、これを何とかしたかった。

解決方法としてはソースファイルを複数に分割することです。
ということで、amalgamation.lispを作りなおしました。

使い方を説明します。

singleモード

今まで通り単一のファイルを作成したい場合は次のようにします。

$ cd develop/amalgamation/
$ sbcl --script amalgamation-single.lisp
Name: npt
Output: lisp.c
Output: lisp.h
Output: shell.c

せっかくなんでnptでやろうよという場合は次のようになりますが、 非常に遅いので自分はsbclでやっています。

$ npt --script amalgamation-single.lisp

ファイルサイズを確認します。

$ wc lisp.c
  292601  764366 7459280 lisp.c

29万行というとても大きなソースファイルができています。

multpleモード

複数のファイルを生成したい場合は次の通り。

$ cd develop/amalgamation/
$ sbcl --script amalgamation-multiple.lisp
Name: npt
Output: lisp_file_01.c
Output: lisp_file_02.c
Output: lisp_file_03.c
Output: lisp_file_04.c
Output: lisp_file_05.c
Output: lisp_file_06.c
Output: lisp_file_07.c
Output: lisp_file_08.c
Output: lisp_file_09.c
Output: lisp.h
Output: shell.c

見てわかる通り、lisp.cの代わりに、lisp_file_01~09.cというファイルが生成されました。
コンパイルは次の通りとなります。

$ cc -g -DLISP_FREEBSD -DLISP_DEBUG lisp_file*.c shell.c -lm

gdbの時間を確かめてみます。

$ gdb a.out
(gdb) b lispd_invoke_handler_control_
Breakpoint 1 at 0x3bf4c0: file lisp_file_03.c, line 48019.
(gdb) r
Starting program: npt/develop/amalgamation/a.out
(error "Hello")
★★★ここから
Breakpoint 1, lispd_invoke_handler_control_ (ptr=0x800a83140,
    instance=0x80127efd0 "\003\003") at lisp_file_03.c:48019
48019           next = ptr->control;
★★★ここまで3

まあまあですね。
実はひどい欠点があり、ファイルサイズが恐ろしく膨れ上がっています。

$ wc lisp_file_*.c
   51060  138673 1419576 lisp_file_01.c
   52655  137592 1512174 lisp_file_02.c
   51280  126202 1460601 lisp_file_03.c
   51516  137111 1485953 lisp_file_04.c
   52211  164221 1680275 lisp_file_05.c
   51081  132727 1494255 lisp_file_06.c
   51924  135576 1473222 lisp_file_07.c
   52005  132025 1471599 lisp_file_08.c
   23583   61900  717012 lisp_file_09.c
  437315 1166027 12714667 total

29万行→43万行になっています。
個別のファイルは5万行くらいで収まっているのですが、 元々のソースよりはるかに多くなっています。

なぜこんなことが起きているかというと、 コンパイル用のヘッダーファイルを出力しないようにしているため、 必要な情報を全て*.cファイルに埋め込んでいるからです。

lisp.hコンパイル用のヘッダーファイルではなく、 ユーザーが開発に使うためのものなのです。

これはちょっと許容できないなということで、 開発用のヘッダーファイルを生成するモードも作成しました。

headerモード

本モードは開発用のヘッダーファイルも出力します。

$ cd develop/amalgamation/
$ sbcl --script amalgamation-header.lisp
Name: npt
Output: lisp_file.h
Output: lisp_file_01.c
Output: lisp_file_02.c
Output: lisp_file_03.c
Output: lisp_file_04.c
Output: lisp_file_05.c
Output: lisp_file_06.c
Output: lisp_file_07.c
Output: lisp_file_08.c
Output: lisp_file_09.c
Output: lisp.h
Output: shell.c

multipleとは違い、lisp_file.hというファイルが作成されています。
それぞれの行数を見てみましょう。

$ wc lisp_file*
   30602   81184  679396 lisp_file_01.c
   30355   74714  715103 lisp_file_02.c
   30542   69511  721342 lisp_file_03.c
   30446   74189  681212 lisp_file_04.c
   30192  106862  950764 lisp_file_05.c
   30656   73963  726365 lisp_file_06.c
   30993   75485  710006 lisp_file_07.c
   30287   72128  709222 lisp_file_08.c
   19986   51134  474525 lisp_file_09.c
   28907   85953 1098544 lisp_file.h
  292966  765123 7466479 total

個別のファイルはおよそ3万行で収まっており、 全ファイルの合計はlisp.cとほぼ同じになっています。
ソースファイルは3万行で区切っているのですが、 lisp_file.hは分割せず全部結合して出力しています。
lisp_file.hも3万行に近いのはただの偶然です。

コンパイルの方法はmultipleと同じです。

$ cc -g -DLISP_FREEBSD -DLISP_DEBUG lisp_file*.c shell.c -lm

ではgdbはどうなっているのか?
gdbの時間確認は次の通り。

・・・
(error "Hello")
★★★ここから
Breakpoint 1, lispd_invoke_handler_control_ (ptr=0x800a84140,
    instance=0x80127ed10 "\003\003") at lisp_file_04.c:400
400             next = ptr->control;
★★★ここまで1

いいですね。
個人的にはheaderモードを使って行こうと思います。
multipleモードなんて作る必要なかったかもしれない。

まとめ

headerモードを使おう。
やり方は次の通り。

$ cd develop/amalgamation/
$ sbcl --script amalgamation-header.lisp
$ cc -g -DLISP_FREEBSD -DLISP_DEBUG lisp_file*.c shell.c -lm
$ ./a.out