L1VM - go far beyond

Here I will show how to use inline assembly in combination with JIT-compiling. This makes it possible to run programs as fast as a C program or another compiled language. I will show the program double-test-jit it can be found here: benchmark 04.

Here are the variable definitions:

#include <intr.l1h>
(main func)
    (set int64 1 zero 0)
    (set double 1 x 23.0)
    (set double 1 y 42.0)
    (set double 1 z 7.0)
    (set double 1 a 1.0)
    (set int64 1 max 10000001Q)
    (set int64 1 one 1)
    (set int64 1 time 0)
    (set string s timerstr  "JIT-compiler compile time: ")

So they are set as in every other program. Nothing special there. Now the inline assembly starts with: (ASM)

 (ASM)
    loada zero, 0, I0
    loadd x, 0, F1
    loadd y, 0, F2
    loadd z, 0, F3
    loadd a, 0, F4
    loadd a, 0, F10
    loadd y, 0, F22
    loadd x, 0, F20
    loada one, 0, I4
    loada max, 0, I5
    loada one, 0, I6
    loadl :jit, I40
    loadl :jit_end, I41
    loada timerstraddr, 0, I50
    intr0 6, I50, 0, 0
    // start timer
    intr0 24, 0, 0, 0
    // run jit compiler
    intr0 253, I40, I41, 0
    // stop timer
    intr0 25, I50, 0, 0

With loada an int64 variable is load into the register as set by the third argument. It’s the same on loadd for double numbers. So: loadd x, 0, F1 loads the variable x into the F1 register. All other opcodes later use this registers we have initialized yet!

The loadl opcodes set the JIT-compiler start and end labels into the registers I40 and I41. And this intr0 253, I40, I41, 0 calls the JIT-compiler.

The next part is our main loop where the sum gets calculated by the add opcodes.

:loop
    // call jit code
    intr0 254, I0, 0, 0
    // jump to following non-jit code
    jmp :next
:jit
    addd F10, F20, F10
    addd F10, F20, F10
    addd F10, F20, F10
    addd F10, F20, F10
    addd F10, F20, F10
    addd F10, F20, F10
    addd F10, F20, F10
    addd F10, F20, F10
    addd F10, F20, F10
    addd F10, F20, F10
    addd F10, F22, F10
    addd F10, F22, F10
    addd F10, F22, F10
    addd F10, F22, F10
    addd F10, F22, F10
    addd F10, F20, F10
    addd F10, F20, F10
    addd F10, F20, F10
    addd F10, F20, F10
    addd F10, F20, F10
    addd F10, F20, F10
    addd F10, F20, F10
    addd F10, F20, F10
    addd F10, F20, F10
    addd F10, F20, F10
    addd F10, F22, F10
    addd F10, F22, F10
    addd F10, F22, F10
    addd F10, F22, F10
:jit_end
    addd F10, F22, F10
    // store
:next

Here we have the call to the JIT-code by: intr0 254, I0, 0, 0. And the next line is very important for us. The program jumps over the JIT-code to the label :next! As you may have already guessed the add opcodes do the double number adding.

Now the last part is to calculate the loop the program is running in:

    addi I4, I6, I4
    lseqi I4, I5, I30
    jmpi I30, :loop
    intr0 5, F10, 0, 0
    intr0 7, 0, 0, 0
    intr0 255, 0, 0, 0
    (ASM_END)
(funcend)

The lseqi I4, I5, I30 checks if the current loop is less or equal to the max loop setting. If this is true the program will do the jump in the next line to :loop. The loop exits if the max loop count is reached. The intr0 5, F10, 0, 0 prints the final result of the add loop. And the next line prints just a new line. The program exits by intr0 255, 0, 0, 0. And the (ASM_END) marks the inline assembler end there.