emacs lisp 研究 lisp.h (几何画板开发笔记 四)

由于想为所做的几何画板(类)和几何推理引入一种驱动语言,近期研究了 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 我们稍后一一研究。

 

你可能感兴趣的:(lisp)