Specpdl机制

special binding & protection list 机制.

specpdl是一个手动管理的栈结构, 用于追踪三种信息:

  1. 动态的let binding

  2. 方案解绕保护 unwind-protects

  3. 回溯信息 backtrace

核心数据结构

specbind_tag

保护的类型

定义于 src/lisp.h.

大概分为: 函数 ,数组, 指针, int, point位置, 无参数保护函数, backtrace, 模块运行时, 模块环境, 动态let, 局部let, 全局let.

enum specbind_tag {
  SPECPDL_UNWIND,		/* An unwind_protect function on Lisp_Object.  */
  SPECPDL_UNWIND_ARRAY,		/* Likewise, on an array that needs freeing.
				   Its elements are potential Lisp_Objects.  */
  SPECPDL_UNWIND_PTR,		/* Likewise, on void *.  */
  SPECPDL_UNWIND_INT,		/* Likewise, on int.  */
  SPECPDL_UNWIND_INTMAX,	/* Likewise, on intmax_t.  */
  SPECPDL_UNWIND_EXCURSION,	/* Likewise, on an excursion.  */
  SPECPDL_UNWIND_VOID,		/* Likewise, with no arg.  */
  SPECPDL_BACKTRACE,		/* An element of the backtrace.  */
  SPECPDL_NOP,			/* A filler.  */
#ifdef HAVE_MODULES
  SPECPDL_MODULE_RUNTIME,       /* A live module runtime.  */
  SPECPDL_MODULE_ENVIRONMENT,   /* A live module environment.  */
#endif /* !HAVE_MODULES */
  SPECPDL_LET,			/* A plain and simple dynamic let-binding.  */
  /* Tags greater than SPECPDL_LET must be "subkinds" of LET.  */
  SPECPDL_LET_LOCAL,		/* A buffer-local let-binding.  */
  SPECPDL_LET_DEFAULT		/* A global binding for a localized var.  */
};

specbinding

栈上的条目

定义于src/lisp.h .

union specbinding
  {
    /* Aligning similar members consistently might help efficiency slightly
       (Bug#31996#25).  */
    ENUM_BF (specbind_tag) kind : CHAR_BIT;
    struct {
      ENUM_BF (specbind_tag) kind : CHAR_BIT;
      void (*func) (Lisp_Object);
      Lisp_Object arg;
      EMACS_INT eval_depth;
    } unwind;
    struct {
      ENUM_BF (specbind_tag) kind : CHAR_BIT;
      ptrdiff_t nelts;
      Lisp_Object *array;
    } unwind_array;
    struct {
      ENUM_BF (specbind_tag) kind : CHAR_BIT;
      void (*func) (void *);	/* Unwind function.  */
      void *arg;
      void (*mark) (void *);	/* GC mark function (if non-null).  */
    } unwind_ptr;
    struct {
      ENUM_BF (specbind_tag) kind : CHAR_BIT;
      void (*func) (int);
      int arg;
    } unwind_int;
    struct {
      ENUM_BF (specbind_tag) kind : CHAR_BIT;
      void (*func) (intmax_t);
      intmax_t arg;
    } unwind_intmax;
    struct {
      ENUM_BF (specbind_tag) kind : CHAR_BIT;
      Lisp_Object marker, window;
    } unwind_excursion;
    struct {
      ENUM_BF (specbind_tag) kind : CHAR_BIT;
      void (*func) (void);
    } unwind_void;
    struct {
      ENUM_BF (specbind_tag) kind : CHAR_BIT;
      /* `where' is not used in the case of SPECPDL_LET,
	 unless the symbol is forwarded to a KBOARD.  */
      Lisp_Object symbol, old_value;
      union {
	/* KBOARD object to which SYMBOL forwards, in the case of
	   SPECPDL_LET.  */
	KBOARD *kbd;

	/* Buffer otherwise.  */
	Lisp_Object buf;
      } where;
    } let;
    struct {
      ENUM_BF (specbind_tag) kind : CHAR_BIT;
      bool_bf debug_on_exit : 1;
      Lisp_Object function;
      Lisp_Object *args;
      ptrdiff_t nargs;
    } bt;
  };

specpdl_ref

指向specpdl栈中的某个条目的抽象引用,单位是字节而不是数组索引

定义于src/lisp.h

/* Abstract reference to a specpdl entry.
   The number is always a multiple of sizeof (union specbinding).  */
#ifdef WRAP_SPECPDL_REF
/* Use a proper type for specpdl_ref if it does not make the code slower,
   since the type checking is quite useful.  */
typedef struct { ptrdiff_t bytes; } specpdl_ref;
#else
typedef ptrdiff_t specpdl_ref;
#endif

接口

具体请看elisp.md,这里仅列出

  • specbind: 用于在specpdl栈上添加一个let绑定.

  • record_unwind_protect*系列: 在specpdl栈上注册反解绕处理器.

  • unbind_to: 从当前specpdl栈指针回退到之前保存的位置

  • SPECPDL_INDEX: 记录当前specpdl的栈位置 方便到时候unbind_to的时候直接恢复到这里.

  • unwind-protect: 保护BODY 最后无论如何执行清理.

GC

为什么需要 unwind_ptr 与record_unwind_protect_ptr_mark 这类GC机制的specpdl呢?

我们考虑这个场景:

struct context {
	Lisp_Object a_lisp_obj;
	int a;
	void *other_data;
};

我们想要用specpdl保护它:

struct context *ctx = xmalloc(sizeof(*ctx));
ctx->a_lisp_obj = Fcons (Qnil,Qnil);

record_unwind_protect_ptr(cleanup_func, (void *)ctx);

这里使用的是ptr 没有使用mark.

那么当GC扫描到specpdl栈 扫到了SPECPDL_UNWIND_PTR { func: cleanup_func , arg: (void *)ctx}

但是GC不会去分析这个ctx指向什么 也就是说: ctx中的a_lisp_obj是孤立的 没有被引用. 它们会被GC释放.

如果添加了mark:

static void context_mark (void *arg){
	struct context *ctx  = arg;
	
	make_objects (ctx->a_lisp_obj);
}
struct context *ctx = xmalloc(sizeof(*ctx));
ctx->a_lisp_obj = Fcons (Qnil,Qnil);

record_unwind_protect_ptr_mark(cleanup_func, (void *)ctx,context_mark);

那么这样 a_lisp_obj就是被标记的 有引用的 不会被GC识别清除.

示例

ert测试经常用

这个出自test/lisp/calc/clac-tests.el

(ert-deftest calc-latex-input ()
  ;; Check precedence of "/" in LaTeX input mode.
  (should (equal (math-read-exprs "a+b/c*d")
                 '((+ (var a var-a) (/ (var b var-b)
                                       (* (var c var-c) (var d var-d)))))))
  (unwind-protect
      (progn
        (calc-set-language 'latex)
        (should (equal (math-read-exprs "a+b/c*d")
                 '((+ (var a var-a) (/ (var b var-b)
                                       (* (var c var-c) (var d var-d)))))))
        (should (equal (math-read-exprs "a+b\\over c*d")
                       '((/ (+ (var a var-a) (var b var-b))
                            (* (var c var-c) (var d var-d))))))
        (should (equal (math-read-exprs "a/b/c")
                       '((/ (/ (var a var-a) (var b var-b))
                            (var c var-c))))))
    (calc-set-language nil)))

这里的(unwind-protect)保护了里面的calc-set-language所绑定的变量, 最后无论如何调用calc-set-language nil.

non-local exit恢复