commit fe384925332a6b205fb5648450d5502c2b7335c1
parent 8fb909c8bdd8783e55e9f37474fd8ca9bc26d677
Author: Virgil Dupras <hsoft@hardcoded.net>
Date: Sat, 18 Jun 2022 14:44:47 -0400
cc: completely overhaul cc/vm
My first draft turned out to be a bit more complex than it should and didn't
generate very good code. This new design generated much, much better code. It's
more sophisticated, but I think it's simpler as a whole with its 2 ops approach
instead of op+result.
Diffstat:
M | fs/asm.fs | | | 15 | +++++++++++---- |
M | fs/cc/gen.fs | | | 60 | +++++++++++++++++++++++++++--------------------------------- |
M | fs/cc/vm.fs | | | 349 | +++++++++++++++++++++++++++++++++++++++++++++++++------------------------------ |
M | fs/tests/cc/vm.fs | | | 114 | +++++++++++++++++++++++++++++++++++++------------------------------------------ |
4 files changed, 306 insertions(+), 232 deletions(-)
diff --git a/fs/asm.fs b/fs/asm.fs
@@ -38,12 +38,14 @@
: isimm? ( -- f ) src _id IMM = ;
: tgt-or-src! tgt 0< if to tgt else to src then ;
-: r! ( reg -- ) $300 or ( mod 3 ) tgt-or-src! ;
+: r! ( reg -- ) $300 or ( mod 3 ) tgt-or-src! ;
: [r]! ( reg -- ) ( mod 0 ) tgt-or-src! ;
: [r]+8b! ( reg -- ) $100 or ( mod 1 ) tgt-or-src! ;
: eax AX r! ; alias eax ax alias eax al
: ebx BX r! ; alias ebx bx alias ebx bl
+: ecx CX r! ; alias ecx cx alias ecx cl
+: edx DX r! ; alias edx dx alias edx dl
: ebp BP r! ;
: edi DI r! ;
: [eax] AX [r]! ;
@@ -63,7 +65,7 @@
prefix, op, ( reg ) 3 lshift tgtid tgt mod or or ( modrm ) c,
disp? if disp c, then asm$ ;
: modrm<imm, ( imm immreg op -- ) \ immediate src, modrm tgt
- op, 3 lshift tgtid or tgt mod or ( modrm ) c, , disp, asm$ ;
+ op, 3 lshift tgtid or tgt mod or ( modrm ) c, disp, , asm$ ;
: modrm2, ( imm? reg op -- ) \ modrm op with 2 arguments
src mod $c0 = not if
\ surprise! it's not tgt that is the mod rm, it's src. We're in "alternate"
@@ -104,5 +106,10 @@ $58 op pop, $50 op push,
\ Special
: mov,
prefix, isimm? if
- $b8 tgtid or c, , asm$ else
- src $89 modrm2, then ;
+ tgt mod $c0 = if \ mod 3, simple imm->reg move
+ $b8 tgtid or c, , asm$ else \ imm->modrm move
+ 0 $c7 modrm<imm, then
+ else src $89 modrm2, then ;
+
+: shln, ( n -- ) \ SHL n times
+ 4 $c1 modrm1, c, ;
diff --git a/fs/cc/gen.fs b/fs/cc/gen.fs
@@ -6,17 +6,17 @@
: _err ( node -- ) printast abort" unexpected node" ;
UOPSCNT wordtbl uopgentbl ( -- )
-:w ( - ) operand?>result vmneg, ;
-:w ( ~ ) operand?>result vmnot, ;
-:w ( ! ) operand?>result vmboolnot, ;
-'w operand>&operand ( & )
-'w operand>[operand] ( * )
-:w ( ++ ) operand?>result vminc, ;
-:w ( -- ) operand?>result vmdec, ;
+:w ( - ) vmneg, ;
+:w ( ~ ) vmnot, ;
+:w ( ! ) vmboolnot, ;
+'w &op>op ( & )
+'w *op>op ( * )
+:w ( ++ ) vm++op, ;
+:w ( -- ) vm--op, ;
POPSCNT wordtbl popgentbl ( -- )
-:w ( ++ ) vminc, ;
-:w ( -- ) vmdec, ;
+:w ( ++ ) vmop++, ;
+:w ( -- ) vmop--, ;
BOPSCNT wordtbl bopgentblmiddle ( node -- node )
'w noop ( + )
@@ -55,25 +55,21 @@ alias noop gennode ( node -- ) \ forward declaration
: spit ( a u -- ) A>r >r >A begin Ac@+ .x1 next r>A ;
: getfuncmap ( node -- funcentry ) AST_FUNCTION parentnodeid data2 ;
-\ get SF offset of AST_LVALUE node
-: lvsfoff ( lvnode -- off )
- dup data1 swap getfuncmap ( name funcentry ) findvarinmap ( varentry )
- vmap.sfoff ;
+: lvvar ( lvnode -- varentry )
+ dup data1 swap getfuncmap ( name funcentry ) findvarinmap ;
\ special binop case
: _assign ( node -- )
firstchild ?dup not if _err then ( lvnode )
dup nextsibling ?dup not if _err then ( lvnode exprnode )
- gennode operand?>result \ result=set
- gennode \ operand=set
- result>operand ;
+ selop1 gennode selop2 gennode op1<>op2 op2>*op1 ;
ASTIDCNT wordtbl gentbl ( node -- )
'w drop ( Declare )
'w genchildren ( Unit )
:w ( Function )
_debug if ." debugging: " dup data1 stype nl> then
- vm$
+ ops$
dup data1 entry
dup data2 ( astfunc mapfunc )
here over fmap.address! \ set address
@@ -81,13 +77,13 @@ ASTIDCNT wordtbl gentbl ( node -- )
genchildren
_debug if current here current - spit nl> then ;
:w ( Return )
- genchildren operand?>result vmret, ;
-:w ( Constant ) data1 const>operand ;
+ genchildren vmret, ;
+:w ( Constant ) data1 const>op ;
:w ( Statements )
- \ we run vm$ between each statement to discard any unused Result
- firstchild ?dup if begin dup gennode vm$ nextsibling ?dup not until then ;
+ \ we run ops$ between each statement to discard any unused Result
+ firstchild ?dup if begin dup gennode ops$ nextsibling ?dup not until then ;
'w genchildren ( ArgSpecs )
-:w ( LValue ) lvsfoff sf+>operand ;
+:w ( LValue ) lvvar vmap.sfoff sf+>op ;
:w ( UnaryOp )
dup genchildren
data1 uopgentbl swap wexec ;
@@ -96,32 +92,30 @@ ASTIDCNT wordtbl gentbl ( node -- )
data1 popgentbl swap wexec ;
:w ( BinaryOp ) dup data1 12 = if _assign exit then
( node ) >r
+ selop2 noop# selop1 optype if oppush 1 else 0 then ( reg? f )
r@ childcount 2 = not if abort" binop node with more than 2 children!" then
r@ firstchild dup nextsibling swap ( n2 n1 )
- gennode bopgentblmiddle r@ data1 wexec
- operand?>result
- resultset? if
- pushresult, gennode operand?>result popresult, else
- gennode operand?>result then
- bopgentblpost r> data1 wexec ;
+ selop1 gennode bopgentblmiddle r@ data1 wexec
+ selop2 gennode bopgentblpost r> data1 wexec
+ ( reg? f ) if op1<>op2 selop1 oppop then ;
'w _err ( unused )
:w ( If )
- firstchild ?dup not if _err then dup gennode ( exprnode )
- operand?>result vmjz, swap ( jump_addr exprnode )
+ firstchild ?dup not if _err then dup selop1 gennode ( exprnode )
+ vmjz, swap ( jump_addr exprnode ) ops$
nextsibling ?dup not if _err then dup gennode ( jump_addr condnode )
nextsibling ?dup if ( jump_addr elsenode )
- vmjnz, ( ja1 enode ja2 ) rot vmjmp! ( enode ja2 )
+ vmjmp, ( ja1 enode ja2 ) rot vmjmp! ( enode ja2 )
swap gennode ( ja2 ) then ( jump_addr ) vmjmp! ;
'w _err ( unused )
:w ( FunCall )
\ pass arguments
dup childcount 4 * callargallot,
dup firstchild ?dup if -4 swap begin ( cursf+ argnode )
- dup gennode operand?>result swap dup sf+>operand result>operand
+ dup selop1 gennode swap dup selop2 sf+>op op1<>op2 op2>*op1 ops$
4 - swap nextsibling ?dup not until drop then
\ find and call
( node ) data1 ( name ) findfuncinmap ( mapfunc )
- fmap.address vmcall>result, ;
+ fmap.address vmcall>op1, ;
: _ ( node -- ) gentbl over astid wexec ;
current to gennode
diff --git a/fs/cc/vm.fs b/fs/cc/vm.fs
@@ -4,123 +4,179 @@
\ The goal of this VM is to provide a unified API for code generation of a C
\ AST across CPU architecture.
-\ Computation done by this generated code is centered around the "Result".
-\ concept. The goal is to make the CPU move bits around towards that Result.
-\ The Result always lives on a particular register reserved for this role. For
-\ example, on x86, it's EAX.
+\ Computation done by this generated code is centered around two operands, Op1
+\ and Op2. Those operands can "live" in different places depending on the
+\ context: in a register, in memory, or as a constant.
-\ The VM very often interacts with the Result through another concept: the
-\ Operand. Moving from the operand to the result, from the result to the
-\ operand, running an operation on both the Result and the operand, etc.
+\ Each of the two operands can be of either of those types:
-\ The Operand doesn't live in a particular register, it comes from multiple
-\ types of sources. Its possible values are:
-
-\ None: no operand specified
+\ None: operand not specified
\ Constant: a constant value
\ Stack Frame: an address on the Stack Frame
-\ Register: value currently being held in an "alternate" register (EBX on x86)
+\ Register: value currently being held a register
+
+\ Besides the type, each operand has an accompanying "argument", whose meaning
+\ depend on the type:
+
+\ Constant: the value of the constant
+\ Stack Frame: the offset relative to the SF pointer
+\ Register: the ID of the register
-\ We operate the VM by first specifying an operand, moving the operand toward
-\ the result, performing ops, moving the result out. If we need to keep an
-\ intermediate result for later, we can push it to a stack (the mechanism for
-\ this is arch-specific). The pushed result can be later pulled backed into the
-\ Operand.
+\ On those operands, the VM generates code that perform operations on them.
+\ Although some operations are special, there are basically 2 types of
+\ operations: unary op and binary op.
+
+\ When performing an unary op we:
+\ 1. Assert that op2 is unset
+\ 2. if op1 is not in a register, move the value to a register
+\ 3. Perform the operation on the register
+
+\ When performing an binary op we:
+\ 1. Assert that op2 is set
+\ 2. if op1 is not in a register, move the value to a register
+\ 3. Perform the binary operation with op1 as the target, op2 as the source.
\ To avoid errors, moving an operand to a non-empty and non-pushed Result is an
\ error. To set the operand when it's not None is also an error.
\ For usage example, see tests/cc/vm.fs
+: _err abort" vm error" ;
+: _assert not if _err then ;
+
+\ Execution context (function)
+
+0 value argsz \ size of the argument portion of the SF.
+0 value locsz \ size of the "local vars" portion of the SF.
+0 value callsz \ size of the args portion of a Function Call
+
+\ Register management
+\ When an operand needs to go to a register, we allocate one for it. when it
+\ doesn't need it anymore, we deallocate it. Registers have to be deallocated
+\ in the reverse order they were allocated.
+\ Allocation works by having a list of register to allocate, and a pointer,
+\ "reglvl" which indicate which register can be used next. When we run out of
+\ register, allocating a register pushes the first register to the stack and
+\ then allocates it.
+
+6 const REGCNT
+create registers AX c, BX c, CX c, DX c, SI c, DI c,
+0 value reglvl
+
+: curreg ( -- regid )
+ reglvl REGCNT < if
+ registers reglvl + c@ ( regid ) else
+ eax push, AX ( regid ) then ;
+: regallot ( -- regid ) curreg 1 to+ reglvl ;
+: regfree ( -- )
+ reglvl not if abort" too many regfree" then
+ -1 to+ reglvl reglvl REGCNT >= if eax pop, then ;
+
+\ Operands definition and selection
$00 const VM_NONE
$01 const VM_CONSTANT
$02 const VM_STACKFRAME
$03 const VM_REGISTER
+$13 const VM_*REGISTER \ dereferenced value pointed by register
-0 value resultset? \ 0 = no result, 1=result set
-VM_NONE value operand
-\ For VM_CONSTANT, this contains the actual value
-\ For VM_STACKFRAME, this contains the SF offset
-0 value operandarg
-0 value operandlvl \ -1 means "&", 1 means "*", 2 means "**" etc.
-0 value argsz \ size of the argument portion of the SF.
-0 value locsz \ size of the "local vars" portion of the SF.
-0 value callsz \ size of the args portion of a Function Call
+\ 2 operands, 2 fields each (type, arg), 4b per field
+create operands 16 allot0
-: vm$ 0 to resultset? VM_NONE to operand 0 to operandlvl 0 to callsz ;
+operands value 'curop
+: selop1 ( -- ) operands to 'curop ;
+: selop2 ( -- ) operands 8 + to 'curop ;
+: optype ( -- type ) 'curop @ ;
+: optype! ( type -- ) 'curop ! ;
+: oparg ( -- arg ) 'curop 4 + @ ;
+: oparg! ( arg -- ) 'curop 4 + ! ;
+: opdeinit optype $f and VM_REGISTER = if regfree then VM_NONE optype! ;
+\ reinitialize both ops to VM_NONE and dealloc registers if needed
+: ops$
+ selop2 opdeinit selop1 opdeinit
+ reglvl if abort" unbalanced reg allot/free" then
+ operands 16 0 fill ;
-: _err abort" vm error" ;
-: _assert not if _err then ;
+\ Managing operands
+
+: hasop# optype VM_NONE = not _assert ;
+: noop# optype VM_NONE = _assert ;
+: const>op ( n -- ) noop# VM_CONSTANT optype! oparg! ;
+: sf+>op ( off -- ) noop# VM_STACKFRAME optype! oparg! ;
\ get current operand SF offset, adjusted with callsz
-: opersf+ ( -- off ) operandarg callsz + ;
+: opsf+ ( -- off ) oparg callsz + ;
\ Resolve current operand as an assembler "src" argument.
-: operandAsmKeep ( -- )
- operand VM_CONSTANT = if
- operandarg i32
- else operand VM_REGISTER = if
- ebx
- else operand VM_STACKFRAME = if
- opersf+ [ebp]+
- else _err then then then ;
-
-: operandAsm ( -- ) operandAsmKeep VM_NONE to operand ;
-
-: result! 1 to resultset? ;
-
-\ Force current operand to be copied to the "alternate" register
-: operand>reg ( -- )
- operand VM_REGISTER = not if
- ebx operandAsm mov, VM_REGISTER to operand then ;
-
-\ emit, if necessary, the code necessary to resolve "positive" operandlvl
-: resolvederef
- operandlvl if
- operandlvl 0< if
- 0 to operandlvl
- VM_REGISTER to operand
- ebx ebp mov,
- opersf+ ?dup if ebx i32 add, then
- else operand>reg begin
- ebx [ebx] mov, -1 to+ operandlvl operandlvl not until then then ;
-
-: const>operand ( n -- )
- VM_NONE operand = _assert
- VM_CONSTANT to operand to operandarg ;
-
-: sf+>operand ( offset -- )
- VM_NONE operand = _assert
- VM_STACKFRAME to operand to operandarg ;
-
-: operand>result ( -- )
- resultset? not _assert
- resolvederef
- eax operandAsm mov, result! ;
-
-: operand?>result operand VM_NONE = not if operand>result then ;
-
-: operand>&operand
- operand VM_STACKFRAME = _assert
- operandlvl 0>= _assert
- -1 to operandlvl ;
-
-: operand>[operand]
- operand VM_STACKFRAME = operand VM_REGISTER = or _assert
- 1 to+ operandlvl ;
-
-: result>operand
- resultset? _assert
- operand VM_STACKFRAME = if
- operandlvl if
- -1 to+ operandlvl operand>reg resolvederef
- [ebx] eax mov,
- else operandAsm eax mov, then
- else operand VM_REGISTER = if
- -1 to+ operandlvl resolvederef
- [ebx] eax mov,
- else _err then then
- 0 to resultset? VM_NONE to operand ;
+: opAsm ( -- )
+ optype case
+ VM_CONSTANT of = oparg i32 endof
+ VM_REGISTER of = oparg r! endof
+ VM_STACKFRAME of = opsf+ [ebp]+ endof
+ VM_*REGISTER of = oparg [r]! endof
+ _err endcase ;
+
+\ Force current operand to be copied to a register
+: op>reg
+ hasop# optype VM_REGISTER = not if
+ regallot dup r! ( regid ) opAsm mov, oparg! VM_REGISTER optype! then ;
+
+\ Resolve any referencing into a "simple" result. A VM_STACKFRAME goes into a
+\ register, a VM_*REGISTER is resolved into a VM_REGISTER.
+: opderef
+ optype case
+ VM_STACKFRAME of = op>reg endof
+ VM_*REGISTER of = oparg r! oparg [r]! mov, VM_REGISTER optype! endof
+ endcase ;
+
+\ Before doing an operation on two operands, we verify that they are compatible.
+\ For example, we can't have two VM_*REGISTER ops. one of them has to be
+\ dereferenced (it has to be op2).
+: maybederef
+ selop1 optype VM_*REGISTER = optype VM_STACKFRAME = or if
+ selop2 opderef then ;
+
+\ Resolve current operand as an assembler "tgt" argument.
+: *opAsm ( -- )
+ optype case
+ VM_STACKFRAME of = opsf+ [ebp]+ endof
+ VM_*REGISTER of = oparg [r]! endof
+ _err endcase ;
+
+\ if possible, transform current operand in its reference
+: &op>op optype case
+ VM_STACKFRAME of =
+ regallot dup r! ebp mov, dup r! opsf+ i32 add,
+ oparg! VM_REGISTER optype! endof
+ VM_*REGISTER of = VM_REGISTER optype! endof
+ _err endcase ;
+
+\ if possible, dereference current operand
+: *op>op optype case
+ VM_STACKFRAME of = op>reg *op>op endof
+ VM_REGISTER of = VM_*REGISTER optype! endof
+ VM_*REGISTER of = opderef VM_*REGISTER optype! endof
+ _err endcase ;
+
+\ Force the op into a register and then reset the op to VM_NONE
+: oppush ( -- regid ) op>reg VM_NONE optype! oparg ;
+
+\ Assuming that current op is VM_NONE, set it back to VM_REGISTER, its arg to
+\ the current register at reglvl
+: oppop ( regid -- ) noop# VM_REGISTER optype! oparg! ;
+
+\ Copy the contents of op2 in the memory address pointed out by op1 and deinit
+\ op2. In other words, perform a AST_ASSIGN with the right part as op2
+\ and the left part as op1.
+: op2>*op1
+ maybederef selop1 *opAsm selop2 opAsm mov, opdeinit ;
+
+\ Swap op1 and op2 types/args
+: op1<>op2
+ selop1 optype oparg
+ selop2 oparg swap oparg! optype rot optype!
+ selop1 optype! oparg! ;
+
+\ Code generation - Functions, calls, ret
\ generate function prelude code by allocating "locsz" bytes on PS.
: vmprelude, ( argsz locsz -- )
@@ -129,54 +185,79 @@ VM_NONE value operand
\ deallocate locsz and argsz. If result is set, keep a 4b in here and push the
\ result there.
: vmret,
- locsz argsz + resultset? if 4 - then
+ selop2 noop# \ returning with a second operand? something's wrong
+ locsz argsz + selop1 optype if 4 - then
+ opderef \ for [ebp] src mov, to work, "src" has to be "simple"
?dup if ebp i32 add, then
- resultset? if [ebp] eax mov, then
+ optype if [ebp] opAsm mov, then
ret, ;
-: pushresult, resultset? _assert eax push, 0 to resultset? ;
-: popresult,
- VM_NONE operand = _assert
- VM_REGISTER to operand
- ebx pop, ;
-: callargallot, ( bytes -- )
- dup to callsz ebp i32 sub, ;
+
+: callargallot, ( bytes -- ) dup to callsz ebp i32 sub, ;
\ TODO: call function with no return value.
\ call addr and assume that it left a 4b area in the SF with its result in it.
\ move this data in Result.
-: vmcall>result, ( addr -- )
- resultset? not _assert
+: vmcall>op1, ( addr -- )
+ selop1 noop#
call,
- eax [ebp] mov, 1 to resultset?
+ VM_REGISTER optype! regallot r! [ebp] mov,
ebp 4 i32 add, 0 to callsz ;
-: vmadd, eax operandAsm add, result! ;
-: vmsub, eax operandAsm sub, result! ;
-: vmmul, operand>reg operandAsm mul, result! ;
-: vmneg, eax neg, ;
-: vmnot, ( ~ ) eax not, ;
-: vmboolnot,
- eax eax test,
- eax 0 i32 mov,
- al setz, ;
-\ inc/dec have 2 operation modes. If there's no operand, it inc/dec the result.
-\ if there's an operand, it *post* inc/dec, that is, it moves the operand to
-\ the result and then it inc/dec the operand.
-: _ ( 'w -- ) operand VM_NONE = if
- eax execute else
- eax operandAsmKeep result! mov, operandAsm execute then ;
-: vminc, ['] inc, _ ;
-: vmdec, ['] dec, _ ;
-: vm<,
- eax operandAsm cmp,
- eax 0 i32 mov,
- al setg, ;
-: vm==,
- eax operandAsm cmp,
- eax 0 i32 mov,
- al setz, ;
+
+
+\ Code generation - Binary ops
+: binopprep ( -- ) \ prepare ops for the binop
+ selop1 op>reg opAsm
+ selop2 hasop# opAsm ;
+: vmadd, binopprep add, opdeinit ;
+: vmsub, binopprep sub, opdeinit ;
+\ mul is special and cannot use binopprep for two reasons: its target operand
+\ is hardcoded to EAX and also, EDX gets written by the op, so we need to save
+\ EDX if in use.
+: vmmul,
+ reglvl 4 >= if edx push, then
+ \ if op1 is not EAX, we need to push EAX, perform the mul, copy EAX to op1's
+ \ reg, then pop eax back.
+ selop1 op>reg oparg AX = not if eax push, eax opAsm mov, then
+ selop2 op>reg hasop# opAsm mul, opdeinit
+ selop1 oparg AX = not if opAsm eax mov, eax pop, then
+ reglvl 4 >= if edx pop, then ;
+
+\ Code generation - Unary ops
+: unaryopprep selop2 noop# selop1 op>reg opAsm ;
+: vmneg, unaryopprep neg, ;
+: vmnot, ( ~ ) unaryopprep not, ;
+: vmboolnot, unaryopprep
+ opAsm test,
+ opAsm 0 i32 mov,
+ opAsm setz, ;
+
+\ pre-inc/dec op1
+\ TODO: *opAsm goes below, not opAsm. We must inc the reference, not the result.
+: vm++op, selop2 noop# selop1 opAsm inc, ;
+: vm--op, selop2 noop# selop1 opAsm dec, ;
+
+\ post-inc/dec op1
+\ It's a bit complicated here. Before we inc/dec, we need a copy of the current
+\ value in a new register, which will be our result.
+\ For now, we only support op1=VM_STACKFRAME
+: _ ( 'w -- )
+ selop1 optype VM_STACKFRAME = _assert
+ selop2 noop# selop1 optype oparg selop2 oparg! optype!
+ selop1 op>reg selop2 *opAsm execute opdeinit selop1 ;
+: vmop++, ['] inc, _ ;
+: vmop--, ['] dec, _ ;
+
+\ Code generation - Logic
+
+: _
+ selop1 op>reg opAsm selop2 opAsm cmp, opdeinit
+ selop1 opAsm 0 i32 mov, ;
+: vm<, _ opAsm setl, ;
+: vm==, _ opAsm setz, ;
: vmjmp! ( 'jump_addr -- ) here over - 4 - swap ! ;
+: vmjmp, 0 jmp, here 4 - ;
: vmjz, ( -- addr )
- eax eax test, 0 to resultset?
+ selop1 opAsm opAsm test, opdeinit
0 jz, here 4 - ;
: vmjnz, ( -- addr )
- eax eax test, 0 to resultset?
+ selop1 opAsm opAsm test, opdeinit
0 jnz, here 4 - ;
diff --git a/fs/tests/cc/vm.fs b/fs/tests/cc/vm.fs
@@ -3,117 +3,109 @@ f<< asm.fs
f<< cc/vm.fs
testbegin
\ Tests for the C compiler VM module
-
\ binop[+](binop[*](const[2],const[3]),const[1])
-vm$
+ops$
code test1
0 0 vmprelude,
- 2 const>operand
- operand>result
- 3 const>operand
+ selop1 2 const>op
+ selop2 3 const>op
vmmul,
- 1 const>operand
+ selop2 1 const>op
vmadd,
vmret,
test1 7 #eq
\ binop[+](binop[-](const[3], const[1]),binop[*](const[2],const[3]))
-vm$
+ops$
code test2
0 0 vmprelude,
- 3 const>operand
- operand>result
- 1 const>operand
+ selop1 3 const>op
+ selop2 1 const>op
vmsub,
- pushresult,
- 2 const>operand
- operand>result
- 3 const>operand
+ selop1 oppush
+ selop1 2 const>op
+ selop2 3 const>op
vmmul,
- popresult,
+ selop2 oppop
vmadd,
vmret,
test2 8 #eq
\ sub 2 args
-vm$
+ops$
code test3
8 0 vmprelude,
- 4 sf+>operand
- operand>result
- 0 sf+>operand
+ selop1 4 sf+>op
+ selop2 0 sf+>op
vmsub,
vmret,
54 12 test3 42 #eq
\ assign 2 local vars
-vm$
+ops$
code test4
0 8 vmprelude,
\ foo = 42
- 42 const>operand
- operand>result
- 4 sf+>operand
- result>operand
+ selop2 42 const>op
+ selop1 4 sf+>op
+ op2>*op1
+ ops$
\ bar = 5
- 5 const>operand
- operand>result
- 0 sf+>operand
- result>operand
+ selop2 5 const>op
+ selop1 0 sf+>op
+ op2>*op1
+ ops$
\ return foo + bar
- 4 sf+>operand
- operand>result
- 0 sf+>operand
+ selop1 4 sf+>op
+ selop2 0 sf+>op
vmadd,
vmret,
test4 47 #eq
\ variable reference and dereference
-vm$
+ops$
code test5
0 8 vmprelude,
\ foo = 42
- 42 const>operand
- operand>result
- 4 sf+>operand
- result>operand
+ selop2 42 const>op
+ selop1 4 sf+>op
+ op2>*op1
+ ops$
\ bar = &foo
- 4 sf+>operand
- operand>&operand
- operand>result
- 0 sf+>operand
- result>operand
+ selop2 4 sf+>op
+ &op>op
+ selop1 0 sf+>op
+ op2>*op1
+ ops$
\ return *bar
- 0 sf+>operand
- operand>[operand]
- operand>result
+ selop1 0 sf+>op
+ *op>op
vmret,
test5 42 #eq
\ assign and dereference
-vm$
+ops$
code test6
0 8 vmprelude,
\ foo = 42
- 42 const>operand
- operand>result
- 4 sf+>operand
- result>operand
+ selop2 42 const>op
+ selop1 4 sf+>op
+ op2>*op1
+ ops$
\ bar = &foo
- 4 sf+>operand
- operand>&operand
- operand>result
- 0 sf+>operand
- result>operand
+ selop2 4 sf+>op
+ &op>op
+ selop1 0 sf+>op
+ op2>*op1
+ ops$
\ *bar = 54
- 54 const>operand
- operand>result
- 0 sf+>operand
- operand>[operand]
- result>operand
+ selop2 54 const>op
+ selop1 0 sf+>op
+ *op>op
+ op2>*op1
+ ops$
\ return foo
- 4 sf+>operand
- operand>result
+ selop1 4 sf+>op
vmret,
test6 54 #eq
testend