5 Interpreting
| (require a86/interp) | package: a86 |
5.1 Running assembly programs
It is possible to run a86 Programs from within Racket using asm-interp.
If you have code written in a86 that you would like to execute directly, you should instead use the printing facilities to save the program to a file and then use an external assembler (e.g. clang) and linker to produce either object files or executables. It’s possible to use The Racket Foreign Interface to interact with those files from within Racket.
The simplest form of interpreting an a86 program is to use asm-interp.
procedure
(asm-interp is ...) → integer?
is : (or/c instruction? (listof instruction?))
Examples
> (asm-interp (prog (Global 'entry) (Label 'entry) (Mov 'rax 42) (Ret))) 42
Programs do not have to start with a label named 'entry. The interpreter will jump to whatever the first label in the program is (which must be declared Global):
Examples
> (asm-interp (prog (Global 'f) (Label 'f) (Mov 'rax 42) (Ret))) 42
As a convenience, asm-interp accepts any number of arguments that are either instructions or lists of instructions and it will splice them together to form a program, like seq:
Examples
> (asm-interp (Global 'f) (Label 'f) (Mov 'rax 42) (Ret)) 42
As another convenience, if the first defined label of the instructions given to asm-interp is not declared Global or there is no first defined label, asm-interp will generate a globally defined label at the beginning of the instructions and start executing there:
Examples
> (asm-interp (Mov 'rax 42) (Ret)) 42
With the exception of these conveniences, the argument of asm-interp should form a complete, well-formed a86 program in the sense of prog.
While this library tries to make assembly syntax errors
impossible, it is possible—
Examples
> (asm-interp (Mov rax 0) (Jmp rax)) invalid memory reference. Some debugging context lost
5.2 Resolving external labels
It is often the case that we want our assembly programs to interact with the oustide or to use functionality implemented in other programming languages. For that reason, it is possible to link in object files to the running of an a86 program.
The mechanism for controlling which objects should be linked in is a parameter called current-objects, which contains a list of paths to object files which are linked to the assembly code when it is interpreted.
parameter
(current-objects objs) → void? objs : (listof path-string?)
= '()
For example, suppose there’s a GCD function in C:
int gcd(int n1, int n2) { return (n2 == 0) ? n1 : gcd(n2, n1 % n2); }
First, compile the program to an object file:
shell
> gcc -fPIC -c gcd.c -o gcd.o
The option -fPIC is important; it causes the C compiler to emit “position independent code,” which is what enables Racket to dynamically load and run the code.
Once the object file exists, using the current-objects parameter, we can run code that uses things defined in the C code:
Examples
> (parameterize ((current-objects (list "gcd.o"))) (asm-interp (Extern 'gcd) (Mov 'rdi 11571) (Mov 'rsi 1767) (Sub 'rsp 8) (Call 'gcd) (Add 'rsp 8) (Ret))) 57
Note that if you forget to set current-objects, you will get a linking error saying a symbol is undefined:
Examples
> (asm-interp (Extern 'gcd) (Mov 'rdi 11571) (Mov 'rsi 1767) (Sub 'rsp 8) (Call 'gcd) (Add 'rsp 8) (Ret)) jit-call: Symbols not found: [ gcd ]
lookup of label 'label_init7177_5b7e' failed: Failed to
materialize symbols: { (a86_prog_71, { label_init7177_5b7e
}) }
Sometimes that other programming language we want our assembly programs to interact with is Racket. In this case, we can actually resolve external symbols in the assembly code to Racket values.
For example, suppose there’s a GCD function in Racket:
Examples
> (define (gcd n1 n2) (if (zero? n2) n1 (gcd n2 (modulo n1 n2))))
We can define a host-gcd function, which essentially attaches a C-style type declaration (using bindings from the FFI) to this function and the external symbol 'gcd:
Examples
> (require ffi/unsafe)
> (define host-gcd (extern 'gcd gcd (_fun _int64 _int64 -> _int64)))
Then we can inform the interpreter to resolve the 'gcd external label to host-gcd by using the current-externs parameter.
parameter
(current-externs) → (listof extern?)
(current-externs externs) → void? externs : (listof extern?)
= '()
Examples
> (parameterize ([current-externs (list host-gcd)]) (asm-interp (Extern 'gcd) (Mov 'rdi 11571) (Mov 'rsi 1767) (Sub 'rsp 8) (Call 'gcd) (Add 'rsp 8) (Ret))) 57
procedure
(asm-interp/io is in) → (cons integer? string?)
is : (listof instruction?) in : string?