NOTE: this project is much influx and should not be considered usabled
TinyVM is a minimalistic 64bit Virtual Machine. The aim of TinyVM is to make it easy to embed in other Go projects that require a Virtual Machine. TinyVM is not thread-safe, though this is subject to change.
To install the TinyVM binary please make sure you've got Go properly set up, then run
go get github.com/obscuren/tinyvm
Basic: tinyvm <flags> file
. TinyVM allows you to set the registers from the command line using the
-r#
. Where #
is the register number (0 to 15). Please take extra care when setting register 15.
This register is used for the program counter and allows you to control the flow of execution. Please
refer to the -help
option for more information.
TinyVM comes with a small set of assembler instructions to make it easy to use. The asm
package
contains an assembler language definition and a very simple compiler.
TinyVM comes with a small general purpose register (r0..r15
), unbounded memory ([addr]
)
and a general purpose stack mechanism (pop
, push
). r15
is a special register for the
program counter and can be set to jump to arbitrary position in code. TinyVM also has a very
simple calling mechanism (call
) and keeps an internal call stack to determine the positions
for returning (ret
).
Setting register r15
to anything other than the default (0
) means execution will start from
that position and onward. In the future we'll allow labels to be specified in the form of
v.Set(asm.Reg, asm.R15, "my_label")
, but this has to be implemented in both the vm as well as
the compiler who does not yet emit label information during the assembly stage of the "compiler".
See Appendix I for a list op assembly operations.
TinyVM supports (like ARM) conditional execution e.g. moveq
would only be executed if the
conditional value were to be set to zero. The conditional value can be set by appending s
to the mnemonic (e.g. movs
, which sets the 25th bit) or by using the comparison instructions
cmp
and tst
. By default data processing instructions do not set the condition code flag.
An conditional execution must always be preceeded by a comparison instruction or an instruction with the conditional code bit set.
The instruction scheme used by TinyVM is based on the ARM instruction scheme though with
some arrangement in the ops order. TinyVM uses 4-bit rotation value (9th to 12th bit) to
shift the 8-bit immediate value. This clever trick allows us to encode a lot of values in
the range of 2^0..32
in only 12-bits. When an immediate value is encoded in Ops2
the
25th bit is set to 1, indicating an immediate value is encoded in the lower 12 bits of the
instruction.
The last 4 bits will be used for conditional execution (like ARM). Any instruction can be
form of operation[condition]
e.g. addeq
for add if equal or movgt
for mov if
greater than.
Instructions can be in several modes stored in bit 27 and 26. TinyVM supports the following modes:
00
Data processing01
Data transfer10
Branching11
(reserved / unused)
+--------------+---------+----------+----------+----------+----------+---------+---------+---------+
| Bits |31 .. 28 | 27 .. 24 | 23 .. 20 | 19 .. 16 | 15 .. 12 | 11 .. 8 | 7 ... 4 | 3 ... 0 |
+--------------+---------+----------+----------+----------+----------+---------+---------+---------+
| Description | COND | DDSI | INS | Ds | Ops1 | Ops2 | | |
+--------------+---------+----------+----------+----------+----------+---------+---------+---------+
| mov r1 #260 | 0000 | 0001 | 1010 | 0001 | 0000 | 1111 | 0100 | 0001 |
| mov r1 r2 | 0000 | 0000 | 1010 | 0001 | 0002 | 0000 | 0000 | 0000 |
+--------------+---------+----------+----------+----------+----------+---------+---------+---------+
The following example is an integration example on how you could embed TinyVM in to your own project.
mov r15 main
add: ; add taket two arguments
add r0 r0 r1
ret
main: ; main must be called with r0 and r1 set
call add
stop
// parse the source code
code, err := asm.Assemble(sourceCode)
if err != nil {
panic(err)
}
v := vm.New(false) // pass "true" for debug info
// set the registers (as required for "main")
v.Set(asm.Reg, asm.R0, 3) // set r0 to 3
v.Set(asm.Reg, asm.R1, 2) // set r1 to 2
// execute the compiled code
if err := v.Exec(code); err != nil {
fmt.Println("err", err)
return
}
// exit: 5
fmt.Println("exit:", v.Get(asm.Reg, asm.R0))
Register r15
is used to keep track of the program counter (pc
). Setting register
r15
allows you to jump to another position during execution (using labels is permitted):
mov r15 main
mov r0 #1
main:
Instruction mov r0 #1
should be ignored and register 0
should be left empty.
The following code fragment is a loop which runs until the counter in r0
hits zero
When it hits zero the condition code ne
(not equal to zero) controling branch becomes
false and exits the loop.
mov r0 #10
loop:
subs r0 r0 #1
movne r15 loop
All operations take at least 2 argument. The first argument (dst
=destination) must be a register (r#
).
Opcode | Arg count | Example | Description |
---|---|---|---|
mov |
2 | mov r0 #1 |
Moves ops1 in to register dst |
add |
3 | add r0 r0 #1 |
ops1 + ops2 and sets the result to register dst |
sub |
3 | sub r0 r0 #1 |
ops1 - ops2 and sets the result to register dst |
rsb |
3 | rsb r0 r0 #1 |
ops2 - ops1 and sets the result to register dst |
mul |
3 | mul r0 r0 #1 |
ops2 * ops1 and sets the result to register dst |
div |
3 | mul r0 r0 #1 |
ops2 / ops1 and sets the result to register dst |
and |
3 | and r0 r0 #1 |
ops1 & ops2 and sets the result to register dst |
xor |
3 | xor r0 r0 #1 |
ops1 ^ ops2 and sets the result to register dst |
orr |
3 | orr r0 r0 #1 |
`ops1 |
cmp |
2 | cmp r0 r0 |
ops1 - ops2 and sets the result to the condition value |
ldr |
2 | ldr r0 r1 |
Load word addressed by ops1 from memory and store in dst |
str |
2 | str r0 r1 |
Store word indst at address ops1 |
call |
1 | call label |
sets r15 to dst and pushes pc to the pc stack |
ret |
0 | ret |
pops the pc of the pc stack and sets r15 . len(stack)==0 halt execution |
- Add conditional-execution tests
- Add assembler tests
- Rewrite memory implementation. Current memory model is temporarily.
- Implement a proper stack mechanism. Current call stack is temporarily.
- Add
pc
,lr
andsp
syntatic sugar (pc = r15, lr = r14, sp = r13)