On this page:
asm-interp
current-objs
asm-interp/  io

5 Interpreting🔗

 (require a86/interp) package: a86

It is possible to run a86 Programs from within Racket using asm-interp.

Using asm-interp comes with significant overhead, so it’s unlikely you’ll want to implement Racket functionality in assembly code via asm-interp. Rather this is a utility for interactively exploring the behavior of assembly code and writing tests for functions that generate assembly code.

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. nasm) 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?))
Assemble, link, and execute an a86 program.

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—quite easy, in fact—to write well-formed, but erroneous assembly programs. For example, this program tries to jump to null, which causes a segmentation fault:

Examples

> (asm-interp (Mov rax 0)
              (Jmp rax))

invalid memory reference.  Some debugging context lost

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-objs, which contains a list of paths to object files which are linked to the assembly code when it is interpreted.

parameter

(current-objs)  (listof path-string?)

(current-objs objs)  void?
  objs : (listof path-string?)
 = '()
Parameter that controls object files that will be linked in to assembly code when running asm-interp.

For example, let’s implement a GCD function in C:

gcd.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-objs parameter, we can run code that uses things defined in the C code:

Examples

> (parameterize ((current-objs '("gcd.o")))
    (asm-interp (Extern 'gcd)
                (Mov 'rdi 11571)
                (Mov 'rsi 1767)
                (Sub 'rsp 8)
                (Call 'gcd)
                (Add 'rsp 8)
                (Ret)))

57

This will be particularly relevant for writing a compiler where emitted code will make use of functionality defined in a runtime system.

Note that if you forget to set current-objs, 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))

link error: symbol gcd not defined in linked objects: ()

use `current-objs` to link in object containing symbol

definition.

procedure

(asm-interp/io is in)  (cons integer? string?)

  is : (listof instruction?)
  in : string?
Like asm-interp, but uses in for input and produce the result along with any output as a string.