retroforth/example/Disassembler.forth
crc 73493f92f4 complete renaming of words in the examples
FossilOrigin-Name: c9ea59275146e3ccddbcfc3c8e2aa12e0c1f51f6d54c9674cf7d9a3b0500bf7c
2018-05-07 16:36:37 +00:00

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
~~~