uLisp – ARM Assembler in Lisp

栏目: IT技术 · 发布时间: 5年前

内容简介:This article describes an in-line assembler written in Lisp, as an add-on for uLisp. The project is currently a work in progress, but I thought it would be interesting to describe where I've got to, and I'd be interested to hear suggestions for extensions

This article describes an in-line assembler written in Lisp, as an add-on for uLisp. The project is currently a work in progress, but I thought it would be interesting to describe where I've got to, and I'd be interested to hear suggestions for extensions or improvements.

The assembler generates ARM Thumb code, and I've tested it on ARM M0 platforms, such as the Adafruit ItsyBitsy M0, and ARM M4 platforms, such as the Adafruit Metro M4. It is currently restricted to generating one assembler routine with up to three integer parameters, and returning an integer result.

uLisp extensions

The assembler uses a special version of uLisp to which I've added two functions:

The assemble function takes a series of 16-bit integer arguments comprising the machine code of the routine being assembled. These are written into RAM. Any argument can also return nil , in which case it is ignored.

The call function will then call the assembler routine in RAM, and return the result.

The actual instruction generation is handled by an assembler written in Lisp. This makes it easy to check the generated code, and extend the assembler to handle additional instructions and addressing modes.

Using the assembler

To use the assembler you need to run the version of uLisp ARM 3.0x from here: uLisp ARM Version 3.0x

Then load in the Lisp source of the assembler from here:ARM Assembler

Assembler syntax

Where possible the syntax is very similar to ARM assembler syntax, with the following differences:

  • The mnemonics are prefixed by '$' (because some mnemonics such as  push  and  pop  are already in use as Lisp functions).
  • Registers are represented as symbols, prefixed with a quote. Constants are just numbers.
  • Lists of registers, as used in the  $push  and  $pop  mnemonics, are represented as a Lisp list.

Assembler instructions are just Lisp functions, so you can see the code they generate:

> (x ($mov 'r1 13))
#x210d

The function x is a convenient addition that prints a 16-bit value in hexadecimal.

The following table shows typical ARM assembler formats, and the equivalent in this Lisp assembler:

Examples ARM assembler uLisp assembler
Push and pop push  {r4, r5, r6, lr} ($push '(r4 r5 r6 lr))
Registers mov  r1, r4 ($mov 'r1 'r4)
Immediate mov r2, #3 ($mov 'r2 3)
Load relative ldr  r0, [r3, #0] ($ldr 'r0 '(r3 0))
Branch bne label ($bne 'label)
Constant .short  0x0b0b #x0b0b

Simple example

Here's a trivial example consisting of four ARM Thumb instructions that multiplies its parameter by 13 and returns the result:

(assemble
 ($push '(lr))
 ($mov 'r1 13)
 ($mul 'r0 'r1)
 ($pop '(pc)))

After executing the assemble command we can then call the function as follows:

> (call 11)
143

The result is the number returned in the r0 register.

If you prefer you could wrap up the call in a Lisp function:

(defun times13 (x) (call x))

Labels

The previous example assembled a function with no branches, in which case you can simply give the mnemonics as arguments to the assemble function.

However, to assemble branches to labels you have to do a bit of extra work as described here:

Enclose the whole program in a let to declare the labels you are going to use as local variables.

Then enclose the assemble call in a

(dotimes (p 2 ass)
    (setq *pc* 0)
    ....
    )

to do a two-pass assembly which will resolve forward references.

Where you want to insert a label such as loop in the program use a statement such as:

($label 'loop)

This assigns the current program counter to the label variable, but returns nil so that no additional code is generated.

For example, here's a simple routine to calculate the Greatest Common Divisor, which uses three labels:

; Greatest Common Divisor
(let ((swap 0) (again 0) (minus 0))
  (dotimes (p 2)
    (setq *pc* 0)
    (assemble
     ($push '(r4 lr))
     ($label 'swap)
     ($mov 'r4 'r1)
     ($mov 'r1 'r0)
     ($label 'again)
     ($mov 'r0 'r4)
     ($label 'minus)
     ($sub 'r4 'r4 'r1)
     ($blt 'swap)
     ($bne 'again)
     ($pop '(r4 pc)))))

For example, to find the GCD of 3287 and 3460:

> (call 3287 3460)
173

For some more examples seebelow.

The assembler

Here's a description of some of the key assembler routines.

The program counter *pc*

To handle labels and branches the assembler uses a global variable *pc* that needs to be defined as follows:

(defvar *pc* 0)

The other routines will give errors if you don't do this.

Packing arguments into bit fields

The emit function takes a list of bit field widths, and a series of arguments, packs the arguments into a 16-bit number according to the bit widths, and returns the result:

(defun emit (bits &rest args)
  (let ((word 0))
    (unless (= (apply #'+ bits) 16) (error "Incorrect number of bits"))
    (mapc #'(lambda (width value)
              (when (> value (1- (expt 2 width))) (error "Won't fit"))
              (setq word (logior (ash word width) value)))
          bits args)
    (incf *pc* 2)
    word))

The bit field widths should add up to 16. For example:

> (x (emit '(4 4 4 4) 12 13 14 15))
#xcdef

As a side effect the emit function also increments the global variable *pc* .

Generating the assembler instructions

A set of helper routines are provided that handle groups of mnemonics with similar parameters; for example mov-cmp-2 handles the  $mov and $cmp mnemonics with an 8-bit immediate argument:

(defun mov-cmp-2 (op argd immed8)
  (emit '(4 1 3 8) 2 op (regno argd) immed8))

These are called in turn by the functions for each individual mnemonic such as $cmp :

(defun $cmp (argd argm)
  (cond
   ((numberp argm)
    (mov-cmp-2 1 argd argm))
   (t
    (and-mvn-4 10 argd argm))))

The number in the name, 2, refers to the value in the top four bits of the instruction to help classify the routines.

A separate helper routine, and-mvn-4 , handles the case of $cmp with two register arguments.

I found Appendix B2 from the ARM System Developer's Guide by Andrew Sloss, Dominic Symes, and Chris Wright useful.

Examples

Here are some examples.

Fibonacci sequence

The Fibonacci sequence is:

1, 1, 2, 3, 5, 8, 13, 21 ...

where the first two terms are 1, and each subsequent term is the sum of the two previous terms. The following recursive function finds the nth term, counting from 0:

(defun fib (n)
  (if (< n 3) 1
    (+ (fib (- n 1)) (fib (- n 2)))))

Running the Lisp version:

> (for-millis () (print (fib 27)))

196418
24609

The first number is the result and the second number is the time, in milliseconds.

Here's the assembler version:

; Fibonacci sequence
(let ((fib 0) (loop 0) (add 0))
  (dotimes (p 2)
    (setq *pc* 0)
    (assemble
     ($label 'fib)
     ($push '(r4 r5 r6 lr))
     ($mov 'r5 'r0)
     ($mov 'r4 0)
     ($label 'loop)
     ($cmp 'r5 2)
     ($ble 'add)
     ($sub 'r0 'r5 1)
     ($bl 'fib)
     ($sub 'r5 2)
     ($add 'r4 'r4 'r0)
     ($b 'loop)
     ($label 'add)
     ($add 'r0 'r4 1)
     ($pop '(r4 r5 r6 pc)))))

Running the assembler version:

> (for-millis () (print (call 27)))

196418
61

On a Adafruit Metro M4 the assembler version is approximately a factor of 400 faster.

Takeuchi function

The Takeuchi function is a classic benchmark for comparing implementations of Lisp, originally used by Ikuo Takeuchi of Japan. Here's the Lisp version:

(defun tak (x y z)
  (if (not (< y x))
      z
    (tak
     (tak (1- x) y z)
     (tak (1- y) z x)
     (tak (1- z) x y))))

For example:

> (for-millis () (print (tak 18 12 6)))

7 
5230

Here's the assembler version:

(let ((tak 0) (compare 0) (exit 0))
  (dotimes (p 2)
    (setq *pc* 0)
    (assemble
     ($label 'tak)
     ($push '(r0 r1 r2 r4 r5 r6 r7 lr))
     ($mov 'r5 'r0)
     ($mov 'r6 'r1)
     ($mov 'r4 'r2)
     ($label 'compare)
     ($cmp 'r6 'r5)
     ($bge 'exit)
     ($mov 'r2 'r4)
     ($mov 'r1 'r6)
     ($sub 'r0 'r5 1)
     ($bl 'tak)
     ($mov 'r2 'r5)
     ($mov 'r1 'r4)
     ($str 'r0 '(sp 4))
     ($sub 'r0 'r6 1)
     ($bl 'tak)
     ($mov 'r2 'r6)
     ($mov 'r7 'r0)
     ($mov 'r1 'r5)
     ($sub 'r0 'r4 1)
     ($bl 'tak)
     ($mov 'r6 'r7)
     ($mov 'r4 'r0)
     ($ldr 'r5 '(sp 4))
     ($b 'compare)
     ($label 'exit)
     ($mov 'r0 'r4)
     ($pop '(r1 r2 r3 r4 r5 r6 r7 pc)))))

Run it as follows:

> (for-millis () (print (call 18 12 6)))

7 
18

On a Adafruit Metro M4 the assembler version is a factor of 290 faster.

Hofstadter Q sequence

This is one of several recursive sequences described in Douglas Hofstadter's book "Gödel, Escher, Bach: an Eternal Golden Braid". It is related to the Fibonacci sequence, except that in this case the two preceding terms specify how far to go back in the sequence to find the two terms to be summed:

(defun q (n)
  (if (<= n 2) 1
    (+
     (q (- n (q (- n 1))))
     (q (- n (q (- n 2)))))))

Running the Lisp version:

> (for-millis () (print (q 21)))

12 
6798

Here's the assembler version:

; Hofstadter Q sequence
(let ((q 0) (compare 0) (add 0))
  (dotimes (p 2)
    (setq *pc* 0)
    (assemble
     ($label 'q)
     ($push '(r4 r5 r6 lr))
     ($mov 'r4 'r0)
     ($mov 'r5 0)
     ($label 'compare)
     ($cmp 'r4 2)
     ($ble 'add)
     ($sub 'r0 'r4 1)
     ($bl 'q)
     ($sub 'r0 'r4 'r0)
     ($bl 'q)
     ($mov 'r6 'r0)
     ($sub 'r0 'r4 2)
     ($bl 'q)
     ($add 'r5 'r5 'r6)
     ($sub 'r4 'r4 'r0)
     ($b 'compare)
     ($label 'add)
     ($add 'r0 'r5 1)
     ($pop '(r4 r5 r6 pc))))))

Running the assembler version:

> (for-millis () (print (call 21)))

12 
25

In other words, about 270 times faster.

  1. It's available as a PDF here:  http://engold.ui.ac.ir/~nikmehr/Appendix_B2.pdf .

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

密码朋克

密码朋克

[澳] 朱利安·阿桑奇 / Gavroche / 中信出版社 / 2017-10 / 42.00元

互联网已经在世界各地掀起了革命,然而全面的打击也正在展开。随着整个社会向互联网迁移,大规模监控计划也正在向全球部署。我们的文明已经来到一个十字路口。道路的一边通往一个承诺“弱者要隐私,强 者要透明”的未来,而另一边则通往一个极权的互联网,在那里,全人类的权力被转移给不受问责的间谍机构综合体及其跨国公司盟友。 密码朋克是一群倡导大规模使用强密码术以保护我们的基本自由免遭攻击的活动家。维基解密的......一起来看看 《密码朋克》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换