commit f2e6fdd29f00b842d40def5c0297e96cf9be58eb
parent 21cfece551c5285569588b624faba78018bd41dd
Author: Virgil Dupras <hsoft@hardcoded.net>
Date: Fri, 16 Sep 2022 10:21:01 -0400
cc/vm: consolidate and clarify code
Diffstat:
6 files changed, 162 insertions(+), 146 deletions(-)
diff --git a/fs/cc/vm/common.fs b/fs/cc/vm/common.fs
@@ -1,67 +0,0 @@
-\ Code common to all VM implementations
-
-: _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.
-
-\ Operands definition and selection
-\ Locations constant: where is the op located?
-$00 const VM_NONE \ nowhere
-$01 const VM_CONSTANT \ a constant of value "arg"
-$02 const VM_STACKFRAME \ on RS at RSP+arg
-$03 const VM_ARGSFRAME \ on PS at PSP+arg
-$04 const VM_REGISTER \ in an implementation-specific register of id "arg"
-$05 const VM_CONSTARRAY \ pointer to an array with the 1st elem being length
-\ Below, references to a location (points to X)
-$11 const VM_*CONSTANT
-$12 const VM_*STACKFRAME
-$13 const VM_*ARGSFRAME
-$14 const VM_*REGISTER
-
-struct[ VMOp
- sfield loc \ one of the VM_ constants
- sfield arg
- 4 &+ 'arg
- : :deinit SZ 0 fill ;
- : :loclo loc $f and ;
- : :deref? loc $10 and bool ;
- : :noop# loc VM_NONE = _assert ;
- : :keep ( self -- arg loc ) dup arg swap loc ;
- : :push ( self -- arg loc ) dup >r :keep VM_NONE to r> loc ;
- : :pop ( arg loc self -- ) dup >r :noop# r@ to loc r> to arg ;
-]struct
-
-create operands VMOp SZ 2 * allot
-operands structbind VMOp vmop
-operands VMOp SZ + structbind VMOp vmop^ \ the "other" op
-0 value opsunsigned \ when 1, ops are considered unsigned
-
-: selop1 ( -- ) operands ['] vmop rebind operands VMOp SZ + ['] vmop^ rebind ;
-: selop2 ( -- ) operands ['] vmop^ rebind operands VMOp SZ + ['] vmop rebind ;
-: selectedop ( -- n ) \ 0 == Op1 1 == Op2
- vmop :self operands <> ;
-: selop^ selectedop if selop1 else selop2 then ;
-: opsunsigned! 1 to opsunsigned ;
-
-: .ops selectedop .x1 spc> 4 >r operands begin @+ .x spc> next drop nl> ;
-: _ops$ selop2 vmop :deinit selop1 vmop :deinit 0 to opsunsigned ;
-
-\ Managing operands
-
-: hasop# vmop loc VM_NONE = not _assert ;
-: isconst? vmop loc VM_CONSTANT = ;
-: isconst# isconst? _assert ;
-: noop# vmop :noop# ;
-: const>op ( n -- ) noop# VM_CONSTANT to vmop loc to vmop arg ;
-: constarray>op ( a -- ) noop# VM_CONSTARRAY to vmop loc to vmop arg ;
-: sf+>op ( off -- ) noop# VM_*STACKFRAME to vmop loc to vmop arg ;
-: ps+>op ( off -- ) noop# VM_*ARGSFRAME to vmop loc to vmop arg ;
-: mem>op ( n -- ) noop# VM_*CONSTANT to vmop loc to vmop arg ;
-
-\ Swap op1 and op2 locs/args
-: op1<>op2
- vmop :push vmop^ :push vmop :pop vmop^ :pop ;
diff --git a/fs/cc/vm/commonhi.fs b/fs/cc/vm/commonhi.fs
@@ -0,0 +1,3 @@
+\ Code common to all VM implementations (high part)
+
+: ops$ selop2 vmop :init selop1 vmop :init 0 to opsunsigned neutral# ;
diff --git a/fs/cc/vm/commonlo.fs b/fs/cc/vm/commonlo.fs
@@ -0,0 +1,76 @@
+\ Code common to all VM implementations (low part)
+
+\ Note: there can only be one implementation of the VM loaded in memory at once.
+\ Loading another VM impl will break the previous one. In Dusk, it's not a
+\ problem because there is no CC cross-compiling. You'll always want to compile
+\ with one VM: your CPU's.
+
+: _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.
+
+\ Operands definition and selection
+\ Locations constant: where is the op located?
+$00 const VM_NONE \ nowhere
+$01 const VM_CONSTANT \ a constant of value "arg"
+$02 const VM_STACKFRAME \ on RS at RSP+arg
+$03 const VM_ARGSFRAME \ on PS at PSP+arg
+$04 const VM_REGISTER \ in an implementation-specific register of id "arg"
+$05 const VM_CONSTARRAY \ pointer to an array with the 1st elem being length
+\ Below, references to a location (points to X)
+$11 const VM_*CONSTANT
+$12 const VM_*STACKFRAME
+$13 const VM_*ARGSFRAME
+$14 const VM_*REGISTER
+
+struct[ VMOp
+ sfield loc \ one of the VM_ constants
+ sfield arg
+ sfield other \ link to the "other" op
+ 4 &+ 'arg
+ \ Initialize op to VM_NONE, "freeing" any resource it held.
+ : :init VM_NONE swap to loc ;
+ : :loclo loc $f and ;
+ : :deref? loc $10 and bool ;
+ : :noop# loc VM_NONE = _assert ;
+ : :keep ( self -- arg loc ) dup arg swap loc ;
+ : :push ( self -- arg loc ) dup >r :keep VM_NONE to r> loc ;
+ : :pop ( arg loc self -- ) dup >r :noop# r@ to loc r> to arg ;
+]struct
+
+create operands VMOp SZ 2 * allot
+operands structbind VMOp vmop
+operands VMOp SZ + structbind VMOp vmop^ \ the "other" op
+vmop^ :self to vmop other
+vmop :self to vmop^ other
+0 value opsunsigned \ when 1, ops are considered unsigned
+
+: _sel ['] vmop rebind vmop other ['] vmop^ rebind ;
+: selop1 ( -- ) operands _sel ;
+: selop2 ( -- ) operands VMOp SZ + _sel ;
+: selectedop ( -- n ) \ 0 == Op1 1 == Op2
+ vmop :self operands <> ;
+: selop^ vmop other _sel ;
+: opsunsigned! 1 to opsunsigned ;
+
+: .ops selectedop .x1 spc> 4 >r operands begin @+ .x spc> next drop nl> ;
+
+\ Managing operands
+
+: hasop# vmop loc VM_NONE = not _assert ;
+: isconst? vmop loc VM_CONSTANT = ;
+: isconst# isconst? _assert ;
+: noop# vmop :noop# ;
+: const>op ( n -- ) noop# VM_CONSTANT to vmop loc to vmop arg ;
+: constarray>op ( a -- ) noop# VM_CONSTARRAY to vmop loc to vmop arg ;
+: sf+>op ( off -- ) noop# VM_*STACKFRAME to vmop loc to vmop arg ;
+: ps+>op ( off -- ) noop# VM_*ARGSFRAME to vmop loc to vmop arg ;
+: mem>op ( n -- ) noop# VM_*CONSTANT to vmop loc to vmop arg ;
+
+\ Swap op1 and op2 locs/args
+: op1<>op2
+ vmop :push vmop^ :push vmop :pop vmop^ :pop ;
diff --git a/fs/cc/vm/forth.fs b/fs/cc/vm/forth.fs
@@ -2,7 +2,7 @@
\ This VM produces slower and bigger code than their native counterpart but has
\ the advantage of working under any architecture.
-?f<< /cc/vm/common.fs
+?f<< /cc/vm/commonlo.fs
\ How the VM works
\ Generally, CPUs have registers that we want to use for performance reasons.
@@ -20,43 +20,46 @@
$06 const VM_TOS
$16 const VM_*TOS
-: opdeinit VM_NONE to vmop loc ;
-: opfree vmop :loclo VM_TOS = if compile drop then opdeinit ;
-
-: ops$ selop2 opfree selop1 opfree _ops$ ;
-
-: TOS# vmop^ :loclo VM_TOS <> _assert ;
-: op>TOS VM_TOS to vmop loc ;
-: op>*TOS VM_*TOS to vmop loc ;
-
\ When accumulating call arguments, we need to keep track of how many we have
\ and apply the corresponding offset to VM_ARGSFRAME.
0 value psoff
+: PS+ 4 to+ psoff ;
+: PS- -4 to+ psoff ;
-: _ ( oploc -- )
- case
- VM_CONSTANT of = vmop arg litn endof
- VM_STACKFRAME of = vmop arg r', endof
- VM_ARGSFRAME of =
- vmop arg vmop^ :loclo VM_TOS = if CELLSZ + then psoff + p', endof
- VM_REGISTER of = vmop 'arg litn compile @ endof
- VM_TOS of = endof \ nothing to do
- _err endcase ;
-
-\ Resolve current operand and compile a push to PS as either VM_TOS or VM_*TOS.
-: opCompile ( -- )
- vmop :deref? if vmop :loclo _ op>*TOS else vmop loc _ op>TOS then ;
+struct+[ VMOp
+ : :TOS? :loclo VM_TOS = ;
+ \ Re-initialize op, "forgetting" the TOS, abandoning it to PS.
+ : :forgetTOS dup :TOS? _assert VMOp :init ;
+ : :init dup :TOS? if compile drop then VMOp :init ;
+ : :>TOS VM_TOS swap to loc ;
+ : :>*TOS VM_*TOS swap to loc ;
+ : _ ( loc self -- )
+ swap case
+ VM_CONSTANT of = arg litn endof
+ VM_STACKFRAME of = arg r', endof
+ VM_ARGSFRAME of =
+ dup arg swap other :loclo VM_TOS = if CELLSZ + then psoff + p', endof
+ VM_REGISTER of = 'arg litn compile @ endof
+ VM_TOS of = drop endof \ nothing to do
+ _err endcase ;
+ \ Resolve current operand and compile a push to PS as either VM_TOS or
+ \ VM_*TOS.
+ : :compile& >r
+ r@ :deref? if r@ :loclo r@ _ r> :>*TOS else r@ loc r@ _ r> :>TOS then ;
+ \ Resolve current operand and forces dereferencing. Always yields VM_TOS.
+ : :compile ( -- ) >r
+ r@ :compile& r@ loc VM_*TOS = if compile @ r@ :>TOS then rdrop ;
+]struct
-\ Resolve current operand and forces dereferencing. Always yields VM_TOS.
-: opCompile* ( -- )
- opCompile vmop loc VM_*TOS = if compile @ op>TOS then ;
+\ Verify that we're in "neutral" position with regards to PS
+: neutral# psoff if abort" unbalanced PS" then ;
\ transform current operand in its reference
: ?&op>op ( -- f ) vmop :deref? if vmop :loclo to vmop loc 1 else 0 then ;
: &op>op ?&op>op _assert ;
: op>reg vmop :loclo VM_REGISTER = if exit then
- hasop# ?&op>op ( f ) opCompile vmop 'arg litn compile ! ( f )
+ hasop# ?&op>op ( f ) vmop :compile& vmop 'arg litn compile ! ( f )
4 lshift VM_REGISTER or to vmop loc ;
\ If any of the op is VM_TOS, push it to a register.
@@ -65,7 +68,7 @@ $16 const VM_*TOS
\ dereference current operand
: *op>op vmop :deref? if
- opCompile* op>*TOS op>reg else
+ vmop :compile vmop :>*TOS op>reg else
vmop loc $10 or to vmop loc then .ops ;
\ We override the common :push/:pop mechanism to adjust to the weirness of
@@ -80,7 +83,7 @@ $16 const VM_*TOS
\ a TOS. If it is, change it into a REGISTER.
struct+[ VMOp
: :push ( self -- arg loc )
- dup :loclo VM_REGISTER = if opCompile then VMOp :push ;
+ dup :loclo VM_REGISTER = if vmop :compile& then VMOp :push ;
: :pop ( arg loc self -- )
over $f and VM_TOS = if ?tos>reg then VMOp :pop ;
]struct
@@ -95,7 +98,7 @@ struct+[ VMOp
: vmret,
selop2 noop# \ returning with a second operand? something's wrong
selop1 vmop loc if
- opCompile* opdeinit argsz ?dup if
+ vmop :compile vmop :forgetTOS argsz ?dup if
p', compile ! argsz CELLSZ - ?dup if p+, then then
else
argsz ?dup if p+, then then
@@ -103,20 +106,21 @@ struct+[ VMOp
exit, ;
\ Write op to args
-: vmcallarg, ( -- ) opCompile* opdeinit 4 to+ psoff ;
+: vmcallarg, ( -- ) vmop :compile vmop :forgetTOS PS+ ;
\ Call the address in current op. If the function has a result, you need to
\ pop it with vmpspop,
: vmcall, ( -- )
VM_*CONSTANT vmop loc = if
- vmop arg execute, else opCompile* compile execute then
- opdeinit 0 to psoff ;
+ vmop arg execute, vmop :init
+ else vmop :compile vmop :forgetTOS compile execute then
+ 0 to psoff ;
\ Allocate a new register for active op and pop 4b from PS into it.
-: vmpspop, noop# op>TOS op>reg ;
+: vmpspop, noop# vmop :>TOS op>reg ;
\ Push active op to PS.
-: vmpspush, opCompile* opdeinit ;
+: vmpspush, vmop :compile vmop :forgetTOS ;
\ Unary operations are performed on the selected op, which can be either op1 or
\ op2.
@@ -124,9 +128,9 @@ struct+[ VMOp
tuck @ swap execute ( a n ) swap ! ;
: unop doer ' , does> @ ( w )
isconst? if vmop arg swap execute to vmop arg else
- litn 4 to+ psoff \ the litn!
- vmop :keep &op>op opCompile* opdeinit vmop :pop compile apply
- -4 to+ psoff then ;
+ litn PS+ \ the litn!
+ vmop :keep &op>op vmop :compile vmop :forgetTOS vmop :pop compile apply
+ PS- then ;
unop vmneg, neg
unop vmnot, ^ \ ~
@@ -139,8 +143,8 @@ unop vm--op, 1-
\ We need to copy the old value to TOS and then inc or dec the reference.
: apply ( w a -- n ) \ Same as unop's apply, but yield old value
tuck @ ( a w old ) dup rot execute ( a old new ) rot ! ;
-: postop doer ' , does> @ ( w ) litn 4 to+ psoff \ the litn!
- &op>op opCompile* opdeinit compile apply op>TOS -4 to+ psoff ;
+: postop doer ' , does> @ ( w ) litn PS+ \ the litn!
+ &op>op vmop :compile vmop :forgetTOS compile apply vmop :>TOS PS- ;
postop vmop++, 1+
postop vmop--, 1-
@@ -151,9 +155,9 @@ postop vmop--, 1-
\ 2 fields: signed op, unsigned op
: binop doer ' , ' , does> opsunsigned if CELLSZ + then @ ( w )
- selop1 opCompile* \ op1 is TOS
+ selop1 vmop :compile \ op1 is TOS
selop2 hasop# swapifTOS
- opCompile* vmop :deinit \ op2 is "lost" on PS
+ vmop :compile vmop :forgetTOS
( w ) execute, selop1 ; \ result in op1 as VM_TOS
binop vmadd, + +
@@ -174,11 +178,11 @@ binop vm||, or and
\ with the participation of op2.
: binop= doer ' , does> @ ( w )
>r selop1 vmop :keep r> ( ... w )
- opCompile* \ op1 is TOS
- selop2 hasop# opCompile* \ op2 is TOS
+ vmop :compile \ op1 is TOS
+ selop2 hasop# vmop :compile \ op2 is TOS
( w ) execute, \ result on TOS
- selop1 vmop :deinit
- vmop :pop &op>op opCompile* vmop :deinit vmop^ :deinit compile ! ;
+ selop1 vmop :forgetTOS
+ vmop :pop &op>op vmop :compile vmop :forgetTOS vmop^ :forgetTOS compile ! ;
binop= vm<<=, lshift
binop= vm>>=, rshift
@@ -187,23 +191,23 @@ binop= vm>>=, rshift
\ op2. In other words, perform a AST_ASSIGN with the right part as op2
\ and the left part as op1.
: _movarray, \ special case, we have a {1, 2, 3} assign
- selop1 vmop loc VM_STACKFRAME = _assert opCompile* \ dst on compiled stack
- opdeinit selop2 vmop arg @+ ( a len ) >r begin ( a )
+ selop1 vmop loc VM_STACKFRAME = _assert vmop :compile \ dst on PS
+ vmop :forgetTOS selop2 vmop arg @+ ( a len ) >r begin ( a )
@+ litn compile swap compile !+ next compile drop ( a ) drop
- opdeinit ;
+ vmop :init ;
: vmmov,
selop2 hasop# vmop loc VM_CONSTARRAY = if _movarray, else
- opCompile* \ op2 is TOS
- selop1 swapifTOS &op>op opCompile*
- vmop :deinit vmop^ :deinit compile ! then ;
+ vmop :compile \ op2 is TOS
+ selop1 swapifTOS &op>op vmop :compile
+ vmop :forgetTOS vmop^ :forgetTOS compile ! then ;
\ Jumping
: ]vmjmp ( 'jump_addr -- ) here swap ! ;
: vmjmp, ( a -- ) [compile] again ;
: vmjmp[, ( -- a ) compile (br) here 4 allot ;
\ In conditional jumps below, the source of the test in in current op
-: _ opCompile* opdeinit ;
+: _ vmop :compile vmop :forgetTOS ;
: vmjz, ( a -- ) _ [compile] until ;
: vmjz[, ( -- a ) _ [compile] if ;
: vmjnz, ( a -- ) _ compile not [compile] until ;
diff --git a/fs/cc/vm/i386.fs b/fs/cc/vm/i386.fs
@@ -41,7 +41,7 @@
\ For usage example, see tests/cc/vm.fs
?f<< asm/i386.fs
-?f<< /cc/vm/common.fs
+?f<< /cc/vm/commonlo.fs
\ Register management
\ When an operand needs to go to a register, we allocate one for it. when it
@@ -65,14 +65,13 @@ create registers AX c, BX c, CX c, DX c, SI c, DI c,
reglvl not if abort" too many regfree" then
-1 to+ reglvl reglvl REGCNT >= if ax pop, then ;
-\ reinitialize selected op to VM_NONE and dealloc registers if needed
-: opdeinit vmop :loclo VM_REGISTER = if regfree then VM_NONE to vmop loc ;
+struct+[ VMOp
+ \ reinitialize selected op to VM_NONE and dealloc registers if needed
+ : :init dup :loclo VM_REGISTER = if regfree then VMOp :init ;
+]struct
-\ Deinit both ops and select Op1
-: ops$
- selop2 opdeinit selop1 opdeinit
- reglvl if abort" unbalanced reg allot/free" then
- _ops$ ;
+\ Verify that we're in "neutral" position with regards to registers
+: neutral# reglvl if abort" unbalanced reg allot/free" then ;
\ Resolve current operand as an assembler "src" argument.
: opAsm ( -- )
@@ -160,14 +159,14 @@ create registers AX c, BX c, CX c, DX c, SI c, DI c,
\ Write op to args
: vmcallarg, ( -- )
- opderef 4 to+ callsz callsz neg bp d) opAsm mov, opdeinit ;
+ opderef 4 to+ callsz callsz neg bp d) opAsm mov, vmop :init ;
\ Call the address in current op. If the function has a result, you need to
\ pop it with vmpspop,
: vmcall, ( -- )
callsz ?dup if bp i) sub, 0 to callsz then
VM_*CONSTANT vmop loc = if vmop arg VM_NONE to vmop loc else opAsm then
- abs>rel call, opdeinit ;
+ abs>rel call, vmop :init ;
\ TODO: copy forth VM's TOS argtype and logic to all VMs. This could save quite
\ a few back-and-forth operations.
@@ -178,20 +177,20 @@ create registers AX c, BX c, CX c, DX c, SI c, DI c,
bp CELLSZ i) add, ;
\ Push active op to PS.
-: vmpspush, opderef bp CELLSZ i) sub, bp 0 d) opAsm mov, opdeinit ;
+: vmpspush, opderef bp CELLSZ i) sub, bp 0 d) opAsm mov, vmop :init ;
\ 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 ;
-: vm&, binopprep and, opdeinit ;
-: vm|, binopprep or, opdeinit ;
-: vm^, binopprep xor, opdeinit ;
+: vmadd, binopprep add, vmop :init ;
+: vmsub, binopprep sub, vmop :init ;
+: vm&, binopprep and, vmop :init ;
+: vm|, binopprep or, vmop :init ;
+: vm^, binopprep xor, vmop :init ;
\ TODO: allow non-const shift right-operand
-: vm<<, binopprep isconst# shl, opdeinit ;
-: vm>>, binopprep isconst# shr, opdeinit ;
+: vm<<, binopprep isconst# shl, vmop :init ;
+: vm>>, binopprep isconst# shr, vmop :init ;
\ 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
@@ -201,7 +200,7 @@ create registers AX c, BX c, CX c, DX c, SI c, DI c,
\ 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 vmop arg AX = not if ax push, ax opAsm mov, then
- selop2 op>reg hasop# opAsm mul, opdeinit
+ selop2 op>reg hasop# opAsm mul, vmop :init
selop1 vmop arg AX = not if opAsm ax mov, ax pop, then
reglvl 4 >= if dx pop, then ;
\ Copy the contents of op2 in the memory address pointed out by op1 and deinit
@@ -215,12 +214,12 @@ create registers AX c, BX c, CX c, DX c, SI c, DI c,
drop selop2
else
maybederef selop1 opAsm selop2 opAsm mov, then
- opdeinit ;
+ vmop :init ;
: binop=prep ( -- ) \ prepare ops a binop of the "assign" loc
selop1 opAsm selop2 hasop# opAsm ;
-: vm<<=, binop=prep isconst# shl, opdeinit ;
-: vm>>=, binop=prep isconst# shr, opdeinit ;
+: vm<<=, binop=prep isconst# shl, vmop :init ;
+: vm>>=, binop=prep isconst# shr, vmop :init ;
\ Code generation - Unary ops
\ Unary operations are performed on the selected op, which can be either op1 or
@@ -250,18 +249,18 @@ create registers AX c, BX c, CX c, DX c, SI c, DI c,
: _ ( 'w -- )
selop1 vmop loc VM_*STACKFRAME = vmop loc VM_*ARGSFRAME = or _assert
selop2 noop# selop1 vmop loc vmop arg selop2 to vmop arg to vmop loc
- selop1 op>reg selop2 opAsm execute opdeinit selop1 ;
+ selop1 op>reg selop2 opAsm execute vmop :init selop1 ;
: vmop++, ['] inc, _ ;
: vmop--, ['] dec, _ ;
\ Code generation - Logic
: _
- selop1 op>reg opAsm selop2 opAsm cmp, opdeinit
+ selop1 op>reg opAsm selop2 opAsm cmp, vmop :init
selop1 opAsm 0 i) mov, ;
: vm<, _ opAsm opsunsigned if setb, else setl, then ;
: vm==, _ opAsm setz, ;
-: _ ( 'w -- ) selop1 opAsm selop2 opAsm execute opdeinit selop1 vmboolify, ;
+: _ ( 'w -- ) selop1 opAsm selop2 opAsm execute vmop :init selop1 vmboolify, ;
: vm&&, ['] and, _ ;
: vm||, ['] or, _ ;
@@ -288,7 +287,7 @@ create registers AX c, BX c, CX c, DX c, SI c, DI c,
\ test ..., -1.
: vmtest,
opAsm vmop loc $f and VM_REGISTER = if opAsm else -1 i) then test, ( sets Z )
- opdeinit ;
+ vmop :init ;
: vmjz, ( a -- ) selop1 vmtest, jz, ;
: vmjz[, ( -- a ) 0 vmjz, _ ;
: vmjnz, ( a -- ) selop1 vmtest, jnz, ;
diff --git a/fs/cc/vm/vm.fs b/fs/cc/vm/vm.fs
@@ -3,3 +3,4 @@ require sys/scratch.fs
S" /cc/vm" curpath :find# ( path )
syspad :[ ARCH c@+ dup 3 + c, move, ," .fs" syspad :] ( path fname )
swap Path :child Path :fload
+f<< /cc/vm/commonhi.fs