由于想为所做的几何画板(类)和几何推理引入一种驱动语言,近期研究了 lisp 语言,
其中 emacs lisp 方言的实现看起来规模大小适合,我基本选择它作为研究对象,以
期待能引入到几何软件中。选择 lisp 还有一些其它的原因,例如它天生就和数学有着
紧密的联系关系,所以我偏向于认为为数学软件选择 lisp 是比起其它语言有着一些
优点的。
以下具体看 emacs lisp 语言的实现,重点是考察其细节实现,以引入(或称借鉴、抄袭?)
到我们的几何软件中。
这一研究从头文件 lisp.h 开始。前期从 emacs 官网下载源码,找到 src 目录等就略去了。
似乎 lisp 的传统 C 实现使用 Lisp_Object 来表示所有 lisp 中使用的数据对象,如整数、
点对单元(cons)、符号(symbol)、浮点数(float)、等等(还有很多)。因为我在其它如
steel bank common lisp, common lisp,femotelisp 等大小 lisp 中也似乎看到这个
结构。一个 Lisp_Object 本质上可以看做是到真正 lisp 数据对象实体的指针,但这个指针
只使用了一个字 (word, int 32 bits) 的部分位来表示真正地址,另一小部分位表示对象
的类型,叫做 tag。下面给出 Lisp_Object 结构的定义:
typedef struct {
int i; // 原定义使用类型 EMACS_INT,为简化我们用 int 代替。
} Lisp_Object;
在这个结构中只有一个字段 i,即前面所说的 指针+类型(val+tag) 放入的字。那么如下几个
重要常量用于表示在这个结构中哪些位用作指针(值),和类型。
enum Lisp_Bits {
GCTYPEBITS = 3, // 从名字就知道还和 gc 有关,后续会遇到。
VALBITS = 32 - GCTYPEBITS, // 原32为常量 BITS_PER_EMACS_INT, 当前配置下=29
};
常量 GCTYPEBITS 表示在结构 Lisp_Object 中,字段 i 表示的字中,有 3 个bits 用于
表示对象类型。现在的值为 3,这个值对于后续的程序一直有着重大的影响,因此这里不得不
详细地给出。
常量 VALBITS 指字段 i 表示的字中,有多少 bits 用于对象的指针(也有特殊情况,以后说明)。
为简化问题,我们直接将原常量 BITS_PER_EMACS_INT(每个 emacs int 类型有多少个 bit)
代换为 32,这样 VALBITS 的值就是 29。(如果在 64位系统下使用 emacs int 是 int 64,
则这个值会有所不同。当然我们的研究可暂不考虑这一点)
现在我们知道了在结构 Lisp_Object 的字段 i 中,包含了两部分信息 val,tag (也可叫做 pointer+type),
则下一个问题是这两部分信息是如何布局在字段 i 中的。 emacs lisp 支持两种布局方式:
1. USE_LSB_TAG -- 将 tag 信息布局到 i 的最低有效位 (Least Significant Bit)
2. USE_MSB_TAG -- 将 tag 信息布局到 i 的最高有效位 (Most ...)
布局到最低有效位(LSB)意味着,在一个 word i 中,最低的 3 个bit 用作 tag,高 29 bit 用作
val。这最重要的意味是 val 如果是作为指针,寻址范围为 4G 的内存空间,但是 Lisp_Object 对象
必须对齐到 8 字节的边界 (这对内存分配与回收系统提出重要的要求)。
另一种布局 MSB 则意味着,在一个 word i 中,最高的 3 个bit 用作 tag,低 29 bit 用作 val。
这意味着寻址范围是 512M,以及对象不需对齐到 8 字节边界。
当然再次为了简化问题,我们直接选用 LSB 模式了。也许某些特殊环境(如内存很少的单片机?)
可能有足够的动机选择 MSB 模式。。。
一上来就接触到这么麻烦的 bit 问题和选项,看起来是不是太底层了?实在没有办法,由于 emacs lisp
就是这么实现的,我也只好先不厌其烦地、不辞辛苦地理解这些 bit 才能更深入地理解后续的内容。
由于已知使用了 3 个 bit 作为类型标记(tag),则我们可知,在一个 Lisp_Object 中可最多表示 2^3=8
种类型标记,那么有哪些类型,以及每种类型对应的标记值就可以研究了:
enum Lisp_Type {
// 整形使用两个 tag
Lisp_Int0 = 0,
Lisp_Int1 = 1 << INTTYPEBITS, // INTTYPEBITS=2, Lisp_Int1=4
Lisp_Symbol = 2,
Lisp_Misc = 3,
Lisp_String = 1,
Lisp_Vectorlike = 5,
Lisp_Cons = 6,
Lisp_Float = 7,
};
上面已知最多有 8 种 tag(type),也即 Lisp_Type 最多有 8 种取值,即 0~7。
其中对 int 类型,给出了两个值:0, 4。这样做的意图是,为 int 类型提供两个 bit,
等价于 int 类型可以使用 30 个bits 位来表示值。而 int 类型是将整数值直接存放
在 val 位置,而不是作为指针(即不是作为指向 int 对象的指针)看待。这样扩大了
int 值的表示范围从 2^29 扩大到 2^30。更细节的稍后有机会再论。
但是由于最多有 8 种 tag,每个 tag 位置资源都很宝贵,所以这么做也有一点点缺点。
其它种类的 Lisp_Object 我们稍后一一研究。