我正在使用Common Lisp(64位Debian GNU / Linux中的SBCL 1.1.5)编写多体系结构汇编程序/反汇编程序,目前汇编程序为x86-64的子集生成正确的代码。为了组装x86-64汇编代码,我使用一个哈希表,其中汇编指令助记符(字符串),如"jc-rel8"
和"stosb"
是返回一个或多个编码函数列表的键,如下所示:
(defparameter *emit-function-hash-table-x64* (make-hash-table :test 'equalp)) (setf (gethash "jc-rel8" *emit-function-hash-table-x64*) (list #'jc-rel8-x86)) (setf (gethash "stosb" *emit-function-hash-table-x64*) (list #'stosb-x86))
编码函数就像这些(有些更复杂):
(defun jc-rel8-x86 (arg1 &rest args) (jcc-x64 #x72 arg1)) (defun stosb-x86 (&rest args) (list #xaa))
现在我试图通过使用转换为Common Lisp CLOS语法的NASM's (NASM 2.11.06)指令编码数据(文件insns.dat
)来合并完整的x86-64指令集。这意味着用自定义x86-asm-instruction
类的实例替换用于发出二进制代码的常规函数(如上面的函数)(到目前为止,这是一个非常基本的类,带有:initarg
的大约20个插槽,{{1} },:reader
等),其中带有参数的:initform
方法将用于发出给定指令(助记符)和参数的二进制代码。转换后的指令数据看起来像这样(但它超过40'000行,正好是7193 emit
和7193 make-instance
'。
;; first mnemonic + operand combination instances (:is-variant t). ;; there are 4928 such instances for x86-64 generated from NASM's insns.dat. (eval-when (:compile-toplevel :load-toplevel :execute) (setf Jcc-imm-near (make-instance 'x86-asm-instruction :name "Jcc" :operands "imm|near" :code-string "[i: odf 0f 80+c rel]" :arch-flags (list "386" "BND") :is-variant t)) (setf STOSB-void (make-instance 'x86-asm-instruction :name "STOSB" :operands "void" :code-string "[ aa]" :arch-flags (list "8086") :is-variant t)) ;; then, container instances which contain (or could be refer to instead) ;; the possible variants of each instruction. ;; there are 2265 such instances for x86-64 generated from NASM's insns.dat. (setf Jcc (make-instance 'x86-asm-instruction :name "Jcc" :is-container t :variants (list Jcc-imm-near Jcc-imm64-near Jcc-imm-short Jcc-imm Jcc-imm Jcc-imm Jcc-imm))) (setf STOSB (make-instance 'x86-asm-instruction :name "STOSB" :is-container t :variants (list STOSB-void))) ;; thousands of objects more here... ) ; this bracket closes (eval-when (:compile-toplevel :load-toplevel :execute)
我已经使用一个简单的Perl脚本将NASM的setf
转换为Common Lisp语法(如上所述)(下面进一步,但对脚本本身没有任何兴趣)并且原则上它可以工作。所以它可以工作,但编译这些7193对象真的很慢,通常会导致堆耗尽。在具有16G内存的Linux Core i7-2760QM笔记本电脑上,编译insns.dat
代码块与7193个对象(如上所述)需要7分钟以上,有时会导致堆耗尽,如下所示:
;; Swank started at port: 4005. * Heap exhausted during garbage collection: 0 bytes available, 32 requested. Gen StaPg UbSta LaSta LUbSt Boxed Unboxed LB LUB !move Alloc Waste Trig WP GCs Mem-age 0: 0 0 0 0 0 0 0 0 0 0 0 41943040 0 0 0.0000 1: 0 0 0 0 0 0 0 0 0 0 0 41943040 0 0 0.0000 2: 0 0 0 0 0 0 0 0 0 0 0 41943040 0 0 0.0000 3: 38805 38652 0 0 49474 15433 389 416 0 2144219760 9031056 1442579856 0 1 1.5255 4: 127998 127996 0 0 45870 14828 106 143 199 1971682720 25428576 2000000 0 0 0.0000 5: 0 0 0 0 0 0 0 0 0 0 0 2000000 0 0 0.0000 6: 0 0 0 0 1178 163 0 0 0 43941888 0 2000000 985 0 0.0000 Total bytes allocated = 4159844368 Dynamic-space-size bytes = 4194304000 GC control variables: *GC-INHIBIT* = true *GC-PENDING* = in progress *STOP-FOR-GC-PENDING* = false fatal error encountered in SBCL pid 9994(tid 46912556431104): Heap exhausted, game over. Welcome to LDB, a low-level debugger for the Lisp runtime environment. ldb>
我必须为SBCL添加(eval-when (:compile-toplevel :load-toplevel :execute)
参数才能完全编译,但是在分配4千兆字节的动态空间后,堆仍然会耗尽。即使堆耗尽了,只需在类中添加一个槽(用于这些实例的--dynamic-space-size 4000
类)之后编译7193个实例超过7分钟对于REPL中的交互式开发来说太过分了(我使用{ {3}},如果重要的话。)
这是'x86-asm-instruction
输出:
; caught 18636 WARNING conditions ; insns.fasl written ; compilation finished in 0:07:11.329 Evaluation took: 431.329 seconds of real time 238.317000 seconds of total run time (234.972000 user, 3.345000 system) [ Run times consist of 6.073 seconds GC time, and 232.244 seconds non-GC time. ] 55.25% CPU 50,367 forms interpreted 784,044 lambdas converted 1,031,842,900,608 processor cycles 19,402,921,376 bytes consed
使用OOP(CLOS)可以合并指令助记符(例如上面的(time (compile-file
或jc
,stosb
),指令的允许操作数(:name
),指令的二进制编码(例如:operands
的{{1}},#xaa
)和一个对象中指令的可能架构限制(stosb
)。但似乎至少我3岁的计算机效率不足以快速编译大约7000个CLOS对象实例。
我的问题是:是否有某种方法可以让SBCL的:code-string
更快,或者我应该在常规函数中保存汇编代码,如上面的示例所示?我也很高兴知道任何其他可能的解决方案。
以下是Perl脚本,以防万一:
:arch-flags
答案 0 :(得分:10)
18636警告看起来非常糟糕,首先要删除所有警告。
我首先要摆脱EVAL-WHEN
围绕这一切。对我来说没有多大意义。直接加载文件,或编译并加载文件。
另请注意,当变量未定义时,SBCL不喜欢(setf STOSB-void ...)
。 DEFVAR
或DEFPARAMETER
引入了新的顶级变量。 SETF
只是设置它们,但不定义它们。这应该有助于摆脱警告。
同样:is-container t
和:is-variant t
气味,这些属性应该转换为继承的类(例如作为mixin)。容器有变种。变体没有变体。