73493f92f4
FossilOrigin-Name: c9ea59275146e3ccddbcfc3c8e2aa12e0c1f51f6d54c9674cf7d9a3b0500bf7c
123 lines
3.1 KiB
Forth
123 lines
3.1 KiB
Forth
RETRO runs on a virtual machine called Nga. The instruction set is MISC
|
|
inspired, consisting of just 27 instructions:
|
|
|
|
0 nop 7 jump 14 gt 21 and
|
|
1 lit <v> 8 call 15 fetch 22 or
|
|
2 dup 9 ccall 16 store 23 xor
|
|
3 drop 10 return 17 add 24 shift
|
|
4 swap 11 eq 18 sub 25 zret
|
|
5 push 12 neq 19 mul 26 end
|
|
6 pop 13 lt 20 divmod
|
|
|
|
Four instructions are packed per 32-bit memory location. The Muri
|
|
assembler allows the instructions to be specified like:
|
|
|
|
'lica.... i
|
|
#100 d
|
|
|
|
I shorten the instructions to two letter abbreviations, with '..' for
|
|
'nop'.
|
|
|
|
The only special case here is that each 'li' instruction requires a
|
|
value to push in the following cell.
|
|
|
|
So to implement this, I first need to unpack a cell into the individual
|
|
instructions.
|
|
|
|
~~~
|
|
{{
|
|
:mask #255 and ;
|
|
:next #8 shift ;
|
|
---reveal---
|
|
:unpack (n-dcba)
|
|
dup mask swap next
|
|
dup mask swap next
|
|
dup mask swap next
|
|
'abcd 'dcba reorder ;
|
|
}}
|
|
~~~
|
|
|
|
With that out of the way, I make a variable to track the number of 'li'
|
|
instructions in the bundle.
|
|
|
|
~~~
|
|
'LitCount var
|
|
~~~
|
|
|
|
And then a string with the two letter identifiers for each instruction.
|
|
Here I append ?? at the end; I'll use this to identify unknown
|
|
instructions.
|
|
|
|
~~~
|
|
'..lidudrswpupojucaccreeqneltgtfestadsumudianorxoshzren?? 'INST s:const
|
|
~~~
|
|
|
|
And then a simple word to return a name for the current instruction. I
|
|
have this increment the `LitCount` when it finds a 'li' instruction.
|
|
|
|
~~~
|
|
:name-for (n-cc)
|
|
dup #1 eq? [ &LitCount v:inc ] if
|
|
#27 n:min #2 * &INST + fetch-next swap fetch swap ;
|
|
~~~
|
|
|
|
To actually display a bundle, I need to decide on what it is. So I have
|
|
a `validate` word to look at each instruction and make sure all are
|
|
actual instructions.
|
|
|
|
~~~
|
|
:valid? (c-f)
|
|
unpack [ #0 #26 n:between? ] bi@ and
|
|
[ [ #0 #26 n:between? ] bi@ and ] dip and ;
|
|
~~~
|
|
|
|
With this and the `LitCount`, I can determine how to render a bundle.
|
|
So the next step is a word to pad raw values so they're at least as
|
|
long as instruction bundle strings (8 characters).
|
|
|
|
~~~
|
|
:pad (s-)
|
|
s:length #30 swap - #0 n:max [ sp ] times ;
|
|
~~~
|
|
|
|
I split out each type (instruction, reference/raw, and data) into a
|
|
separate handler.
|
|
|
|
~~~
|
|
:render-inst (n-)
|
|
$' c:put unpack #4 [ name-for c:put c:put ] times sp '________ pad $i c:put ;
|
|
|
|
:render-data (n-)
|
|
$# c:put n:to-string dup s:put pad sp $d c:put ;
|
|
|
|
:render-ref (n-)
|
|
dup d:lookup-xt n:-zero?
|
|
[ dup render-data sp $( c:put d:lookup-xt d:name s:put ]
|
|
[ render-data ] choose ;
|
|
~~~
|
|
|
|
Then I use these and my `valid?` checker to implement a single word to
|
|
render the packed cell in a meaningful manner.
|
|
|
|
~~~
|
|
:render-packed (n-)
|
|
@LitCount n:zero?
|
|
[ dup valid?
|
|
[ render-inst ]
|
|
[ render-ref ] choose ]
|
|
[ render-ref &LitCount v:dec ] choose ;
|
|
~~~
|
|
|
|
And now to tie it all together:
|
|
|
|
~~~
|
|
:disassemble (an-)
|
|
[ fetch-next
|
|
over $( c:put n:put sp (address)
|
|
render-packed nl (inst_or_data)
|
|
] times drop ;
|
|
~~~
|
|
|
|
~~~
|
|
&disassemble here over - disassemble
|
|
~~~
|