

如何编译setq?
source link: https://www.tuicool.com/articles/UnmA3eJ
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Common Lisp中的 setq
类似于其它语言中的赋值语句,它可以给一个符号对象设定一个值,类似于将一个值赋值给一个变量一样。简单起见,在 jjcc2
中,我会将所有的符号都作为全局的一个label来实现。也就是说,如果代码中出现了
(setq a 1)
这样的代码,那么在最后生成的代码中,就会相应的在 .data
段中有一个同名的label,其中存放着数值1。
既然都是全局变量,那么只需要准备一个容器来盛这些变量名即可。现阶段,暂时认为所有的变量都是数值类型即可。简单起见,这个容器直接用Common Lisp内置的 HASH-TABLE
来表示。
当在 jjcc2
函数中遭遇到 setq
这个符号时,整个表的形态是这样的
(setq var form)
这时候,首先要将 var
放入到记录全局变量的哈希表中。然后,递归地调用 jjcc2
函数,先编译 form
,得到一系列的汇编代码。然后,生成一条 mov
语句,将 eax
寄存器中的内容放到 var
所指的内存位置中。最终的 jjcc2
的代码如下
(defun jjcc2 (expr globals) "支持两个数的四则运算的编译器" (check-type globals hash-table) (cond ((eq (first expr) '+) `((movl ,(second expr) %eax) (movl ,(third expr) %ebx) (addl %ebx %eax))) ((eq (first expr) '-) `((movl ,(second expr) %eax) (movl ,(third expr) %ebx) (subl %ebx %eax))) ((eq (first expr) '*) ;; 将两个数字相乘的结果放到第二个操作数所在的寄存器中 ;; 因为约定了用EAX寄存器作为存放最终结果给continuation用的寄存器,所以第二个操作数应当为EAX `((movl ,(second expr) %eax) (movl ,(third expr) %ebx) (imull %ebx %eax))) ((eq (first expr) '/) `((movl ,(second expr) %eax) (cltd) (movl ,(third expr) %ebx) (idivl %ebx))) ((eq (first expr) 'progn) (let ((result '())) (dolist (expr (rest expr)) (setf result (append result (jjcc2 expr globals)))) result)) ((eq (first expr) 'setq) ;; 编译赋值语句的方式比较简单,就是将被赋值的符号视为一个全局变量,然后将eax寄存器中的内容移动到这里面去 ;; TODO: 这里expr的second的结果必须是一个符号才行 ;; FIXME: 不知道应该赋值什么比较好,先随便写个0吧 (setf (gethash (second expr) globals) 0) (values (append (jjcc2 (third expr) globals) ;; 为了方便stringify函数的实现,这里直接构造出RIP-relative形式的字符串 `((movl %eax ,(format nil "~A(%RIP)" (second expr))))) globals))))
然后还需要修改 stringify
函数,现在它需要处理传给 jjcc2
的全局变量的哈希表,将其转化为对应的 .data
段的声明。代码如下
(defun stringify (asm globals) "根据jjcc2产生的S表达式生成汇编代码字符串" (check-type globals hash-table) ;; 输出globals中的所有变量 ;; FIXME: 暂时只支持输出数字 (format t " .data~%") (maphash (lambda (k v) (format t "~A: .long ~D~%" k v)) globals) (format t " .section __TEXT,__text,regular,pure_instructions~%") (format t " .globl _main~%") (format t "_main:~%") (dolist (ins asm) (cond ((= (length ins) 3) (format t " ~A ~A, ~A~%" (first ins) (if (numberp (second ins)) (format nil "$~A" (second ins)) (second ins)) (if (numberp (third ins)) (format nil "$~A" (third ins)) (third ins)))) ((= (length ins) 2) (format t " ~A ~A~%" (first ins) (if (numberp (second ins)) (format nil "$~A" (second ins)) (second ins)))) ((= (length ins) 1) (format t " ~A~%" (first ins))))) (format t " movl %eax, %edi~%") (format t " movl $0x2000001, %eax~%") (format t " syscall~%"))
弄了一个辅助的函数来方便将 jjcc2
和 stringify
串起来
(defun test (expr) (let ((ht (make-hash-table))) (multiple-value-bind (asm globals) (jjcc2 expr ht) (stringify asm globals))))
尝试在SLIME中运行
(test '(setq a (+ 1 2)))
最后得到如下的汇编代码
.data A: .long 0 .section __TEXT,__text,regular,pure_instructions .globl _main _main: MOVL $1, %EAX MOVL $2, %EBX ADDL %EBX, %EAX MOVL %EAX, A(%RIP) movl %eax, %edi movl $0x2000001, %eax syscall
全文完
Recommend
-
59
-
58
-
65
前言 在了解 Babel 是如何编译 class 前,我们先看看 ES6 的 class 和 ES5 的构造函数是如何对应的。毕竟,ES6 的 class 可以看作一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰...
-
65
-
37
golang交叉编译 问题 golang如何在一个平台编译另外一个平台可以执行的文件。比如在mac上编译Windows和linux可以执行的文件。那么我们的问题就设定成:如何在mac上编译64位linux的可执行文件。 解决方案
-
67
这篇的东西比较多。 首先要处理一下 inside-out/aux 和 inside-out 这两个函数。之前的 inside-out/aux 其实一直不支持对 progn 的处理,需要先补充;而 inside-...
-
27
编译安装的mysql如何更改文件路径 场景:小张在巡检时发现公司一台数据库经常写不入数据,于是登录数据库所在的主机一看,原来是数据库对应的50G磁盘分区满了,而主机上还有另外一个500G的磁盘分区没在用,于是小张请示完领导后...
-
22
iOS编译速度如何稳定提高10倍以上Mr_Coder_12020.05.26 16:39:17字数 4,686阅读 2,502
-
4
PHP Mailing Lists 上这两天有个好玩儿的问题:Introduction to the PHP source code,大概就是有人想知道如何学习 PHP 源码,可是这种事情不是应该自己去发掘的吗? 上面是玩笑话,现在我也说说如何学习...
-
10
如何优雅的实现C++编译期静态反射netcanC++程序猿, 公众号:高级开发者原文链接:
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK