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

走远了吗. 提交于 2019-12-23 07:11:28

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

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

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!