Skip to content

Instantly share code, notes, and snippets.

@duangsuse
Last active October 3, 2018 14:40
Show Gist options
  • Save duangsuse/7dba2f4b41e45c3533158226091438e3 to your computer and use it in GitHub Desktop.
Save duangsuse/7dba2f4b41e45c3533158226091438e3 to your computer and use it in GitHub Desktop.
🖥 Intermediate Language and VM for Scheme Implementations

Intermediate Language for Scheme

为 Scheme 实现准备的 IL(中间语言) 和执行模式 原文档

BiwaScheme 是一个基于虚拟机的字节码解释型语言,Scheme 代码通过 BiwaScheme.Compiler 翻译为中间语言,并随后在 BiwaScheme.Interpreter 中解释执行

实现的大部分字节码设计基于 R. Kent Dybvig 1987 年发布的论文 《三种 Scheme 实现模型》

显然地,这里描述的 不是 BiwaScheme VM,但与之类似

寄存器(Register)

typedef struct SchemeVMSt {
    Value *stack;           // SchemeVM 栈

    unsigned freeIndex;     // 指示栈上空闲位置起始(空栈模式)
    unsigned stackSize;     // 指示栈大小

    Value value;            // 临时变量
    OpCode *operations;     // 接下来执行的字节码数组
    Closure func;           // 当前正在执行的闭包对象
} SchemeVM;

执行栈(Stack)

执行栈的实现非常简单,在 Java 等应用层高级程序设计语言中,你可以使用 ArrayListLinkedList 等实现它

空栈是指堆栈将栈顶指针指向下一个为空的位置,压入栈帧时递增栈指针,相对的满栈则将栈顶指针指向当前位置

typedef struct StackStruct {
    Value *items;

    unsigned freeIndex;
    unsigned stackSize;
    unsigned maxSize;
} Stack;

Value Stack_push(Stack &stack, Value value);
Value Stack_pop(Stack &stack, Value value);
Value Stack_peek(Stack &stack); // 只取栈顶的值,不移除它

bool Stack_empty(Stack &stack);

闭包寄存器的使用方法

闭包对象包含关于 自由变量(外部变量) 的信息

当我们需要一个外部变量的值的时候,就会用到 func 寄存器了

字节码 refer-freeassign-free 需要这个寄存器

虚拟机指令(OpCodes)

执行栈管理(Stack Manipulation)

constant 指令 - 设置 value 寄存器

val 置入 value 寄存器

vm.value = op1
(display 'Hello)
frame
constant "Hello"
# value = "Hello"
argument
# stack = ["Hello"]
constant 1
# value = 1
argument
# stack = [1, "Hello"]
refer-global "display"
apply
halt

argument 指令 - 将 value 压入执行栈

value 压入执行者栈,一般作为函数参数

这意味着它要读取旧的栈大小并写入新栈大小(维护栈指针)

vm.stack << vm.value
(print "Hi")
constant "Hi"
# value = "Hi"
argument
# stack = ["Hi"]

程序流程控制(Control Structure)

test 指令 - 进行流程分支

测试 value 寄存器的值,如果为真,跳转到 then 分支执行,否则跳转到 else

if vm.value then
  vm.ip = op1
else
  vm.ip = op2
end
(define val (if 1 2 3))
constant 1
test 2 3 # branch_then, brance_else
constant 2 # if 1 then value = 2
constant 3 # else 3

argument # [2 or 3]
constant "val"
argument # ["val", 2 or 3]
constant 2
argument # [2, "val", 2 or 3]
refer-global "define"
apply # apply global "define" argsize=2 "val", 2 or 3

close 指令 - 创建 Closure

创建可以用数组表示的 Scheme 闭包对象到 value 寄存器

close freevar_num, dotpos, body, body_size, next

freevar_num: 自由变量,也即引用的外部变量个数

自由变量应该使用 box 封装

dotpos: 可变参数起始索引

(lambda vararg ...) : 0 ; vararg
(lambda (a . rest) ...) : 1
(lambda (a b . rest) ...) : 2
(lambda (a b c) ...) : -1 ; no vararg
(lambda () ...) : 0 ; no argument
vm.value = make_closure(op1, op2, op3, op4)
vm.ip = op5 # next
(lambda () 1)

(define (f a b)
  (lambda () a b))
(f 11 22)

(define (f a b)
  (set! a 99)
  (lambda () a b))
(f 11 22)
close 0, -1, 1, 2, 3
constant 1
return
halt

;; insns omitted ;;
; (lambda () a b))
refer-free 0
refer-free 1
return

; (lambda () a b))
refer-free 0
indirect
refer-free 1
return

conti 指令 - 捕获程序执行状态

捕获当前程序执行状态,这将会创建一个 Closure,其中包含执行栈的复制和还原程序上下文使用的 nuate 指令

conti n next

n 是要移除的外部 lambda 参数个数

nuate 指令 - 恢复程序执行状态

nuate saved_stack next

当执行这条指令时,解释器栈会被保存栈 saved_stack 替换

这条指令只会在运行时由字节码解释器创建,因为编译器不知道关于运行时栈的信息

(define cc (call/cc identity))
(cc)
refer-local 0
nuate <saved stack> 2
return

halt 指令 - 终止程序执行

终止虚拟机指令循环。告诉虚拟机程序已经结束执行

函数调用(Function Call)

frame 指令 - 压入执行栈帧

apply 函数前必须调用 frame,除非是在尾递归优化中

frame next, after

一般,栈帧包含这些信息:

  • 返回地址,即 after 的位置
  • freeIndex,即上层栈帧地址
  • closure,即当前执行的闭包对象

return 一般用于提前从调用中返回

(print 11)
frame 1, 7 ; halt
constant 11
argument
constant 1
argument
refer-global "print"
apply
halt

frame 自己一般不更新上层栈指针,而由 apply 来更新,因为函数参数必须在这一层执行

apply 指令 - 执行函数

apply 没有参数,可以用来执行原生函数和 Closure

执行时,它先 get value 出需要 applyapplicable 对象,再 pop 出参数个数,最后是参数 arg1, arg2...

(+ 11 12)
constant 12
argument ; arg2: 12
constant 11
argument ; arg1: 11
constant 2
argument ; argn: 2
refer-global "+" ; fun: +
apply ; +(11, 12)

tco_hinted_apply 指令 - 尾调用优化的 apply (记录递归堆栈)

类似 apply,不过在允许基本调用栈调试的情况下进行尾递归优化

我们使用这些变量来保存调用调试信息:

var lastRefer: String
val callStack: Stack<String>
val tcoCounter: Stack<Integer>

return 指令 - 从函数中返回

return 会将栈顶的栈帧弹出,一般用于从函数返回

shift 指令 - 抛弃栈帧的尾调用优化

shift n_args

shift 把栈帧顶的 n 个参数移除,以便使用这层栈帧尾调用函数

变量引用和赋值(Variable Reference/Assignment)

refer-localrefer-freerefer-global

  • local

refer-local pos

  • free

refer-free pos

用于索引闭包的自由变量

  • global

refer-global name-symbol

assign-localassign-freeassign-global

格式皆为 assign-* id

value 寄存器的值赋予 id

boxindirect 指令 - 创建 set! 可赋值的变量

为一个本地变量创建一重指针 box local-slot

indirect 可以从 value 寄存器里解包(unwrap)出包装的值

实际上的 C 代码

#include <stdint.h>
#include <stddef.h>

// nil, boolean, reference, number
// integer, C function, string, mapping
// closure, vm state
#define TNIL 0
#define TBOL 1
#define TREF 2
#define TNUM 3
#define TINT 4
#define TFUN 5

#define TSTR 6
#define TMAP 7
#define TLBA 8
#define TSVM 9

#define ttype(o) (o.info.type)
#define trefc(o) (o.info.refc)

// 字节码为 64 位整形
typedef int64_t OpCode;

// 元类型
struct ValueBase {
  unsigned short type; // 值类型
  unsigned short refc; // 引用计数
};

// 字符串
struct StringStruct {
  struct ValueBase info;

  unsigned long hash;
  size_t length;
  char buffer[1];
};

// 浮点数值
struct NumberStruct {
  struct ValueBase info;

  double number;
};

// 整形数值
struct IntegerStruct {
  struct ValueBase info;

  long integer;
};

// 引用、C 函数
struct ReferenceStruct {
  struct ValueBase info;

  void *pointer;
};

// 实际上都是 Value 类型,但我太菜就算了
typedef struct KvPairStruct {
  void *key;
  void *value;
} KvPair;

// 映射
struct MapStruct {
  struct ValueBase info;

  size_t kvlength;
  KvPair *pairarray;
};


// 闭包
struct ClosureStruct {
  struct ValueBase info;

  OpCode *insns;
  KvPair *binding;
  unsigned dotpos;
  // rest args num, -1 means not vararg, 0 means no argument
};

typedef struct ClosureStruct Closure;

// 执行状态
struct StateStruct {
  struct ValueBase info;

  void *vm; // 不会互相引用,就无类型了
};

// 值引用
struct ValueStruct {
  unsigned short type, refc;

  union {
    struct StringStruct str;
    struct NumberStruct num;
    struct IntegerStruct integer;
    struct ReferenceStruct ref;
    struct MapStruct map;
    struct ClosureStruct lba;
    struct StateStruct vm;
  };
};

typedef struct ValueStruct Value;

typedef struct VMStackFrameStruct {
  Value *stack;           // SchemeVM 栈

  unsigned freeIndex;     // 指示栈上空闲位置起始(空栈模式)
  unsigned stackSize;     // 指示栈大小
} VMStackFrame;

// 虚拟机
typedef struct SchemeVMStruct {
  VMStackFrame *stack;    // SchemeVM 栈帧数组

  unsigned freeIndex;     // 指示栈上空闲位置起始(空栈模式)
  unsigned stackSize;     // 指示栈大小

  Value value;            // 临时变量
  OpCode *operations;     // 接下来执行的字节码数组
  Closure func;           // 当前正在执行的闭包对象
} SchemeVM;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment