ulia debug, LLVM (haskell | ocaml)

『東大のディープな数学』(KADOKAWA)なんて本を読んだ。予備校の数学大好きな講師が 東大の数学入試問題を解説した本だ。えっ、お前、東大を受験するんかと早合点して もらっては困る。

自分達の研究者としてのすぐれた後継者を選ぶ試験

らしいです。いわば、弟子を取る試験と捉えるのが正しい認識。問題を出す方も真剣に ならざるを得ない。そうした真剣な行為の結果、素晴らしく観賞にも耐える作品が 生み出されているそう。

解くのが問題ではなく、観賞ならオイラーにも出来るよって事で、書いてある事は3歩 歩いたら忘れるけど、感動を与えてくれます。この本は佳き観賞の手引き書です。

小学生でも知っている直線も東大入試では思想性・品格を醸し出すって銘うった作品。

xy平面内の領域、xもyも±1の範囲内において
 1 - ax - by - axy
の最小値が正となるような定数a,bを座標とする点(a,b)を求めよ

考え方は、困難は分割せよとの事。あれ、これと同じ事がソフトウェア業界にも有るよ。 『分割して統治せよ』ってね。

もう一つ観賞のポイントは、最小値は問わないけど、最小値が正である範囲を求めよと 言う所が東大らしい出題との事。世の事例に即してる。

最小値は問わないけど絶対に正の範囲になければヤバい。企業の資金ぐり。ある1年の 決算期間でいつ資金が最小になったかより、資金が底をつかないことの方が絶対的に 重要。資金が負になれば倒産ですからね。

競馬とかのギャンブルもそう。勝ったり負けたりが続いていても手元のお金が正で ある限りゲームは続けられる。資金が欠乏すれば、府中なり中山から歩いて帰宅 せざるを得ませんからね。

もう一問。ちょい難しい(積分式)不等式を証明せよってのが出てる。式を整理して いくと、自然対数の底eをπ乗した時、21より大きい事を示す事に帰着する。

回答の方法としてグラフの接線を求める方法が示されていた。接線は何故求めるか? それは、接点の近傍を1次近似する。すなわち、どんな関数でも1次式に帰着させて 値を求めちゃえって事みたい。そうか、接線ってそういう意味だったのか。この歳に なって初めて知ったよ。

で、eのπ乗を求めてみる。登場願うは、我がjulia君。

julia> e^pi > 21
true
julia> e^pi
23.140692632779267

へぇー。jujia君って東大入試の受験ロボットの一部になりそう。

julia> e
e = 2.7182818284590...
julia> exp(1)
2.718281828459045
julia> pi
π = 3.1415926535897...
julia> 4 * atan(1)
3.141592653589793

eとかpiとかは有名だから定数として持ってるけど、それが無い場合は導出できっからね。 受験生は覚えておくといいよ。

ああ、余談つうか同本にも出てたんだけど、円に内接及び外接する図形からπの近似値を 求める方法。西洋流と東洋流の2派が有るとか。

西洋流は、内接する6角形から初めて、倍々に増やしていく。東洋流は、内接する4角形から 増やしていくとか。 6角形は、外辺の長さが半径と等しいという合理精神からの出発。内接する4角形からの 出発は、丸太から柱を切り出す、日常的な行為からの思想とか。こういう与太話は楽しいな。

万華鏡

と称するLLVMのtutorialが公開されてる。そのうちのJITの使い方を示唆した章が あったので、FreeBSD上でやってみると、 KaleidoscopeJIT.hが無いと抜かす。そんなの付けといてよ。文句を言っても しょうがないので、探してきた。

そしてコンパイルすると、llvm/ExecutionEngine/Orc/下のファイルがごっそりと無いと 言われた。これって、システム領域ですぜ。お遊びアプリのヘッダーファイルが無いと 言うようなレベルの話じゃ無い。

しょうがないので、Fedora23のllvm37でやったら、すんなりコンパイル出来た。 使うllvmのバージョンを明記しといてよね。それとも最新版を使えってのが、この界隈の 常識?

無謀にも、Orc配下のものをFreeBSDに輸入したら、

$ clang++ -g toy.cpp `llvm-config --cxxflags --ldflags --system-libs --libs core mcjit native` -O3 -o toy
In file included from toy.cpp:15:
In file included from ./KaleidoscopeJIT.h:19:
/usr/local/llvm36/include/llvm/ExecutionEngine/Orc/CompileUtils.h:17:10: fatal error:
      'llvm/ExecutionEngine/ObjectMemoryBuffer.h' file not found
#include "llvm/ExecutionEngine/ObjectMemoryBuffer.h"
         ^
1 error generated.

今度は、こんなのが。余り輸入にばかり頼っていると、危なくなるんでよく考えましょうね。>安倍ちゃん。

このCふらふらなコードを見てると頭がくらくらするぞ。綺麗なコードは他に無いか?

llvm haskell

LLVM Haskellとかで検索してみると、下記のようなのが引っかかってきた。 最初のリンクはHaskellとは無関係なんだけど、LLVMが学術的にも注目されているようで、 論文を読んだら面白かったのでメモしとく。

LLVMを用いたJust-in-timeコンパイラの設計と実装

Implementing a JIT Compiled Language with Haskell and LLVM

ghcのllvmバックエンドをmacで使う

オイラーのFreeBSDに入っているghc 7.10.2でllvm使えるの? 駄目もとでやってみる。

$ ghc -fllvm bld.hs
[1 of 1] Compiling Main             ( bld.hs, bld.o )
You are using a new version of LLVM that hasn't been tested yet!
We will try though...
opt: /tmp/ghc1180_0/ghc_2.ll:6:6: error: unexpected type in metadata definition
!0 = metadata !{metadata !"top", i8* null}
     ^

どうもllvmのversionを選ぶようだ。julia様が作ってくれたllvm3.3を選ぶと

$ ghc -fllvm bld.hs
[1 of 1] Compiling Main             ( bld.hs, bld.o )
Linking bld ...

成功だ。LLVM IRも吐き出せるそうなので、

$ ghc -O2 -fllvm  -fforce-recomp -ddump-llvm bld.hs > llvm-log
$ wc llvm-log
   13166   90522  619899 llvm-log
$ lv llvm-log
   :
==================== LLVM Code ====================
!0 = metadata !{metadata !"top", i8* null}
!1 = metadata !{metadata !"stack", metadata !0}
!2 = metadata !{metadata !"heap", metadata !0}
!3 = metadata !{metadata !"rx", metadata !2}
!4 = metadata !{metadata !"base", metadata !0}
!5 = metadata !{metadata !"other", metadata !0}
 :
c6Es:
  %Hp_Var = alloca i32*, i32 1
  store i32* %Hp_Arg, i32** %Hp_Var
  %Sp_Var = alloca i32*, i32 1
  store i32* %Sp_Arg, i32** %Sp_Var
  %R1_Var = alloca i32, i32 1
  store i32 %R1_Arg, i32* %R1_Var
  %ln6RS = load i32** %Hp_Var
  %ln6RT = getelementptr inbounds i32* %ln6RS, i32 3
 :

確かにllvmが使われていた。だからどうだって言われたら困ってしまうけどね。

先にやった万華鏡のhaskell版が公開されてた。ghcとllvmの間に懸ける橋をcabalで 入れろと言う、llvm36の環境でやったら、llvmが新し過ぎると言う。バージョン指定 してくるって、一体お前は何様だ。愚痴っててもしょうがないので、llvm35をいれた。 そして、えらく待たされて、橋がかかった。早速実行。

$ ghc --make Main.hs
[1 of 6] Compiling Syntax           ( Syntax.hs, Syntax.o )
[2 of 6] Compiling Lexer            ( Lexer.hs, Lexer.o )
[3 of 6] Compiling Codegen          ( Codegen.hs, Codegen.o )
[4 of 6] Compiling Emit             ( Emit.hs, Emit.o )
[5 of 6] Compiling Parser           ( Parser.hs, Parser.o )
[6 of 6] Compiling Main             ( Main.hs, Main.o )
Linking Main ...
/home/sakae/.cabal/lib/i386-freebsd-ghc-7.10.2/llvm-general-3.5.1.2-CFgyhdRknS08MsiapobT9e/libHSllvm-general-3.5.1.2-CFgyhdRknS08MsiapobT9e.a(RawOStreamC.o): 関数 `LLVM_General_WithFileRawOStream' 内:
(.text+0x144): `llvm::raw_fd_ostream::raw_fd_ostream(char const*, std::string&, llvm::sys::fs::OpenFlags)' に対する定義されていない参照です
 :

何がいけないのだろう。この所リンクの失敗ばかりだな。まあ、アプリで万華鏡が 動くだけですから。そんな事より、美しいコードを眺める方が先だよ。

$ wc *.hs
     273    1196    8351 Codegen.hs
      95     334    2480 Emit.hs
      58     227    1732 JIT.hs
      28      84     753 Lexer.hs
      48     162    1083 Main.hs
      84     262    1702 Parser.hs
      14      44     250 Syntax.hs
     600    2309   16351 total
$ wc ~/t/toy.cpp
     672    1997   18648 /home/sakae/t/toy.cpp

同じ事をやるのにCフラフラは非常に冗長。haskellはすっきりさっぱりですから、本質 だけに注目出来ます。

llvm ocaml

先の万華鏡を見ていると、ocamlとllvmを組み合わせるのが出てた。パーサーとかはocamlが得意でしょ って訳だな。

Real World Ocaml には専用の章が無かったけど、 字句解析器(lexer)と構文解析器(parser) にあるからいいかな。

で、実験には、ウブを使う。haskellだと既存のllvmとのやり取りは、libffiを経由する 方式なんで、llvmを変更する必要が無い(たぶん)。それに対してocamlの場合は、llvm側に 手を入れて、ocaml専用のllvmライブラリィーを作っておく必要が有るようだ。

そんな酔狂な事をやってくれているのは、ウブに属する人達だけのよう。fedoraにも freebsdにもパッケージが存在しない。ああ、archLinuxには酔狂な人が居るみたいだけど。

juliaはどっち系かと言うと、システム側で用意したllvmを使えるようになってるので、 後者系、julia側でなんとかするよって方式みたい。

さて、ocamlだけど、普通はocamlのパッケージマネージャopamだかを通じてllvmもコンパイル しちゃうってのが流行りのよう。これは富者が金にまかして豪華な設備を用意してやる 事になる。なんせ遅い、メモリー喰いまくりのCフラフラでllvmをコンパイルしますから。

貧者は出来合いのものlibllvm-ocaml-devを入れる事になります。それに合わせてllvmと ocamlを入れる事になります。で、入れてから万華鏡をコンパイル。

コンパイルには、makeじゃなくてocamlbuildっているocaml付属のものを使うよう。 なんじゃそれ、少し周辺事情を知っときましょ。 ocamlbuild 事始め とか、 OCamlプログラムをコンパイルする を見ておきましょ。(評判は悪いよう。なんたって、ocamlはおフランスの学者さん達が 作っていて、骨の部分しか興味がなく、周辺部分はおろそかですから)

sakae@uB:~/OCaml-Kaleidoscope/Chapter2$ ocamlbuild toy.byte
+ /usr/bin/ocamldep -pp camlp4of -modules lexer.ml > lexer.ml.depends
sh: 1: camlp4of: not found
Preprocessing error on file lexer.ml
Error while running external preprocessor
Command line: camlp4of 'lexer.ml' > /tmp/ocamlppe0ee43

Command exited with code 2.
Compilation unsuccessful after building 2 targets (0 cached) in 00:00:00.

走らせてみると、早速のエラーの洗礼。camlp4ofとかいうのが必要みたい。

sakae@uB:~$ apt-cache search camlp4-extra
camlp4-extra - Pre Processor Pretty Printer for OCaml - extras
sakae@uB:~$ sudo apt-get install camlp4-extra

ぐぐる様経由で入れた。で、今度は

Error:Unbound module Llvm

こんなエラーを喰らった。いろいろ調べてみると、 OCaml LLVM bindings tutorial, part 1 所に行きついた。万華鏡より易しいぞってやつのようだ。

それによると、新しいllvmは駄目みたいで少し古いのを用意しろと。入れて、part1を 実行してみる。

sakae@uB:~/ocaml-llvm-tutorial/part1/build/tutorial01/src$ ./tutorial01.byte hello.bc
; ModuleID = 'hello.bc'
target datalayout = "e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i386-pc-linux-gnu"

@.str = private unnamed_addr constant [12 x i8] c"HellowLLVM\0A\00", align 1

; Function Attrs: nounwind
define i32 @main() #0 {
  %1 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([12 x i8]* @.str, i32 0, i32 0))
  ret i32 0
}

declare i32 @printf(i8*, ...) #1
  :
!0 = metadata !{metadata !"Ubuntu clang version 3.5.2-0ubuntu1 (tags/RELEASE_352/final) (based on LLVM 3.5.2)"}

llvmアセンブラーのビット形式を人間様が読める(いわゆるll形式)に変換してみたのだな。 llvmの売りの一つにそれをJITでターゲットマシンのネイティブ形式に変換したものを、 メモリー上に用意。それを即実行ってのが有る。

juliaはJITでメモリー上にネイティブコードを展開し、それを実行してるだけ。 そこへ持ってくためにjulia語をfemotoLispでASTに変換。それを元にllvmのアセンブラ 作成(きっと、ビット形式だろうな)、JITした結果をポインターで受けて、そこに ジャンプ(実行)って事をやるんだろうね。

で、ocaml版の万華鏡をコンパイルしようとすると、相変わらずLlvmモジュールが見つからんと 言われる。もう面倒なので、/usr/lib/ocaml/llvm-3.5内のものを、一つ上の階層に移動。 これなら、ちゃんと拾ってくれるだろう。

sakae@uB:~/OCaml-Kaleidoscope/Chapter3$ ocamlbuild toy.byte
Finished, 1 target (0 cached) in 00:00:00.
SANITIZE: a total of 1 file that should probably not be in your source tree
  has been found. A script shell file
  "/home/sakae/OCaml-Kaleidoscope/Chapter3/_build/sanitize.sh" is being
  created. Check this script and run it to remove unwanted files or use other
  options (such as defining hygiene exceptions or using the -no-hygiene
  option).
Warning: Leftover OCaml type annotation files:
  File codegen.annot in . has suffix .annot
Finished, 15 targets (0 cached) in 00:00:00.

やっとコンパイル出来た。いさんで実行。

sakae@uB:~/OCaml-Kaleidoscope/Chapter3$ ./toy.byte
Fatal error: cannot load shared library dllllvm
Reason: dllllvm.so: cannot open shared object file: No such file or directory

例のやつ発動かな。

sakae@uB:~/OCaml-Kaleidoscope/Chapter3$ export LD_LIBRARY_PATH=/usr/lib/ocaml
sakae@uB:~/OCaml-Kaleidoscope/Chapter3$ ./toy.byte
ready> x = 3;
parsed a top-level expr

define double @0() {
entry:
  ret double 3.000000e+00
}

次の章はどうかな?

sakae@uB:~/OCaml-Kaleidoscope/Chapter4$ ocamlbuild toy.byte
Finished, 1 target (0 cached) in 00:00:00.
+ /usr/bin/ocamlc -c -o toy.cmo toy.ml
File "toy.ml", line 30, characters 2-16:
Error: Unbound value DataLayout.add
Command exited with code 2.
Compilation unsuccessful after building 14 targets (0 cached) in 00:00:00.

そんなやつは、どこを探しても無かったぞ。もうこれぐらいにしよう。

llvm bindings

以上、llvmとhaskellもしくはocamlが仲良くなる方法(余り友好的になれなかったけど)を 見てきた。ここらで、llvmのソースを観賞しておくのも後学のためになるだろう。 幸いな事に、juliaを作った時の残骸が残っているので、手間いらず。

で、、llvm-3.3の下を覗いて眼についたのが、例題集。

$ ls examples/
BrainF/                 HowToUseJIT/            ModuleMaker/
CMakeLists.txt          Kaleidoscope/           OCaml-Kaleidoscope/
ExceptionDemo/          LLVMBuild.txt           ParallelJIT/
Fibonacci/              Makefile

万華鏡の例題コードがここに収録されてたとは、灯台元暗し。 更に、興味を引きそうなやつが有った。

$ ls bindings/
LLVMBuild.txt   Makefile        README.txt      ocaml/          python/

pythonの方はどうでもいいので、ocamlに潜って行く。

$ ls
Makefile                bitreader/              llvm/
Makefile.ocaml          bitwriter/              target/
analysis/               executionengine/        transforms/

コンパイルの手順書と、それぞれのモジュールに分かれているのか。

$ ls -l llvm/
total 224
-rw-r--r--  1 sakae  wheel   1700 Jan 26  2012 META.llvm.in
-rw-r--r--  1 sakae  wheel   1155 Nov  9  2011 Makefile
-rw-r--r--  1 sakae  wheel  51512 Dec 29  2011 llvm.ml
-rw-r--r--  1 sakae  wheel  93786 Oct  9  2012 llvm.mli
-rw-r--r--  1 sakae  wheel  70670 Sep  2  2012 llvm_ocaml.c

各モジュールは、C語とocaml語とocamlのヘッダーファイル相当(*mli)で、構成されてるとな。 そんでもって、このbindingをコンパイルするには、

configure --enable-targets=x86 --enable-bindings=ocaml \
  --enable-debug-runtime --enable-shared

に行うようだ。あらかじめllvmの開発者達が、ocamlとの接続方法を定義してくれて いるので、コンパイルするだけで、即使えるようになる(はず)。

juliaとかだと、上記に相当するものを、juliaの開発者が考えながら書く事になるんだな。 コードにBugが有ると、即セグフォの厳しい世界が待ってるようですよ。juliaの開発者達は 偉いねぇ。成果物を疎かにしてはいけませんぞ!!

julia debug

Breakpoint 1, jl_parse_input_line (str=0x2feb58b0 "f(x) = x * 2", len=12) at ast.c:516

(gdb) bt
#0  jl_parse_input_line (str=0x2feb58b0 "f(x) = x * 2", len=12) at ast.c:516
#1  0x2b67cce1 in julia_parse_input_line_18823 () at client.jl:156
#2  0x2847e2ad in jl_apply (f=0x2e1f14d0, args=0xbfbfd948, nargs=1) at julia.h:1325
#3  0x28482425 in jl_trampoline (F=0x2e1f14d0, args=0xbfbfd948, nargs=1) at builtins.c:1037
#4  0x28471f0b in jl_apply (f=0x2e1f14d0, args=0xbfbfd948, nargs=1) at julia.h:1325
#5  0x28477796 in jl_apply_generic (F=0x2e1f1450, args=0xbfbfd948, nargs=1) at gf.c:1684
#6  0x292a7160 in ?? ()
#7  0x2847e2ad in jl_apply (f=0x2feb1a20, args=0x0, nargs=0) at julia.h:1325
#8  0x28482425 in jl_trampoline (F=0x2feb1a20, args=0x0, nargs=0) at builtins.c:1037
#9  0x2b655ff9 in julia_syntax_deprecation_warnings_17934 () at client.jl:141
#10 0x2b655dee in julia_return_callback_17933 () at REPL.jl:585
#11 0x2b655eee in jlcall_return_callback_17933 () from /usr/home/sakae/src/julia-0.4.3/usr/lib/julia/sys-debug.so
#12 0x2847e2ad in jl_apply (f=0x2dc48c30, args=0xbfbfdbfc, nargs=1) at julia.h:1325
#13 0x28482425 in jl_trampoline (F=0x2dc48c30, args=0xbfbfdbfc, nargs=1) at builtins.c:1037
#14 0x28471f0b in jl_apply (f=0x2dc48c30, args=0xbfbfdbfc, nargs=1) at julia.h:1325
#15 0x28477796 in jl_apply_generic (F=0x2dc48be0, args=0xbfbfdbfc, nargs=1) at gf.c:1684
#16 0x2b648bc7 in julia_on_enter_17593 () at LineEdit.jl:1297
#17 0x2847e2ad in jl_apply (f=0x2dbd7490, args=0xbfbfdd58, nargs=1) at julia.h:1325
#18 0x28482425 in jl_trampoline (F=0x2dbd7490, args=0xbfbfdd58, nargs=1) at builtins.c:1037
#19 0x28471f0b in jl_apply (f=0x2dbd7490, args=0xbfbfdd58, nargs=1) at julia.h:1325
#20 0x28477796 in jl_apply_generic (F=0x2dbd7430, args=0xbfbfdd58, nargs=1) at gf.c:1684
  :

結構julia語で書かれた所を回っているので、そちらをソースレベルで観光した方が 理解への早道になりそう。

次は、--lisp オプションを付けた時のflisp呼び出し。結論を書いてしまえば、repl.cから 呼び出されている。

541int main(int argc, char *argv[])
542{
543    uv_setup_args(argc, argv); // no-op on Windows
544#else...
587#endif
588    libsupport_init();
589    parse_opts(&argc, (char***)&argv);
590=>  if (lisp_prompt) {
591        jl_lisp_prompt();
592        return 0;
593    }

589行目のparse_optsを経た時、lisp_promptがtrueになり、jl_lisp_prompt()が呼ばれる。 その先は、ast.cに有った。

 152DLLEXPORT void jl_lisp_prompt(void)
 153=>
 154    if (jvtype==NULL) jl_init_frontend();
 155    fl_applyn(1, symbol_value(symbol("__start")), fl_cons(FL_NIL,FL_NIL));

jl_init_frontend()が呼ばれて、lisp環境を整えるとな。

 121void jl_init_frontend(void)
 122=>
 123    fl_init(4*1024*1024);
 124
 125    if (fl_load_system_image_str((char*)flisp_system_image,
 126                                 sizeof(flisp_system_image))) {
 127        jl_error("fatal error loading system image");
 128    }
 129
 130    fl_applyn(0, symbol_value(symbol("__init_globals")));
 131
 132    jvtype = define_opaque_type(symbol("julia_value"), sizeof(void*),
 133                                NULL, NULL);
 134
 135    assign_global_builtins(julia_flisp_ast_ext);
 136    true_sym = symbol("true");
 137    false_sym = symbol("false");
 138    fl_error_sym = symbol("error");
 139    fl_null_sym = symbol("null");
 140    fl_jlgensym_sym = symbol("jlgensym");
   :

juliaのコンパイル時に作った(と思われる)lispのイメージをロードし、重要なシンボル類を julia側に束縛してるんだな。symbol関数はflip.cに定義されてる。 ちなみに、lispのイメージは、

(gdb) p sizeof(flisp_system_image)
$6 = 201030

こんなに小さい。まさにfemtoな使用だ。そして、イニシャライズが終ると julia側から、lisp側の__startに制御を移すって寸法だ。 以下、lispの領分で活動が始まる。

(gdb) bt
#0  apply_cl (nargs=1) at flisp.c:1098
#1  0x2854fa4a in _applyn (n=1) at flisp.c:729
#2  0x2854fcb9 in fl_applyn (n=1, f=725629834) at flisp.c:774
#3  0x284796b5 in jl_lisp_prompt () at ast.c:155
#4  0x0804a909 in main (argc=0, argv=0xbfbfec0c) at repl.c:591

flisp_system_imageって、どういう風に定義してんの? 調べてみたら、ast.cに こんな記述が。

static uint8_t flisp_system_image[] = {
#include "julia_flisp.boot.inc"
};

julia_flisp.boot.inc の inc ってincludeするよって意味だったんだ。実はこのincが 有る事は知ってたけど、これで謎が解けた。ホームズ君に習って、証拠を開陳。 (小学生の頃に読んだ、シャーロックホームズの冒険なんてのを読み直してる)

$ head -1 julia_flisp.boot*
==> julia_flisp.boot <==
(*banner* ";  _\n; |_ _ _ |_ _ |  . _ _\n; | (-||||_(_)|__|_)|_)\n;-------------------|----------------------------------------------------------\n\n"

==> julia_flisp.boot.inc <==
0x28, 0x2a, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2a, 0x20, 0x22, 0x3b, 0x20, 0x20, 0x5f, 0x5c

Lispのソースコードを16進の文字コードに変換して、C語のソースに持ち込むって、 思わぬトリックだな。で、その変換器はflispからは浮いた存在だった、bin2hex.scmに 振ってる。有る物は使おうよって見上げた精神です。

上に出てきた __start がどうなっているか追ってみたら、system.lspに定義が 有った。渡す引数がpairなら、スクリプトのロード(実行)、そうでないならreplに 飛び込め。けちけち作戦で、2つの用途に使ってる。

(define (__script fname)
  (trycatch (load fname)
            (lambda (e) (begin (top-level-exception-handler e)
                               (exit 1)))))

(define (__start argv)
  (__init_globals)
  (if (pair? (cdr argv))
      (begin (set! *argv* (cdr argv))
             (set! *interactive* #f)
             (__script (cadr argv)))
      (begin (set! *argv* argv)
             (set! *interactive* #t)
             (princ *banner*)
             (repl)))
  (exit 0))

上記のreplを追ってみたら、thatって変数に直近の結果がbindされてる事を発見。

> (+ 1 2 3)
6

> that
6

ソースを見ていると、色々と発見が有って楽しいな。秘密の県民ショーならぬ、ソースの 秘密発掘ツアーだな。これだから、ソース観光は止められない。

etc

独自CPU開発で学ぶ コンピュータのしくみ

はじめてのLisp関数型プログラミング

あれもPython,これもPython