Converting traditional assembly code to Forth assembly code
Converting traditional assembly code to Forth assembly code
Hi All,
(My first post, I think!)
I have a question that I thought might be best considered here, even though it's not strictly 6502 related *ahem*, but bear with me.
I'm using Brad R's excellent CamelForth/6809 that I have running on the Vectrex video game console, from 1982. My current project consists of salvaging a 6809 binary from a late 1970's FLEX disk and porting it onto the Vectrex. CamelForth/6809 comes with the 'Chromium' cross-compiler/assembler and I've started converting the FLEX binary, which I've disassembled, into CamelForth assembly.
First tests are good, the assembly works as expected, but then I hit a number of flag tests/branches and associated forward jumps, which the cross-assembler can't handle. Luckily, CamelForth assembler has IF, ELSE, THEN, etc. that in most cases I can substitute in.
But now I'm a bit further into the code, I'm hitting routines where the code flow jumps all over the place, e.g. out of BEGIN, UNTIL, loops, including using out of sequence IF, THEN,. Clearly, if the code was built using IF, THEN, etc. in the first place, I wouldn't be seeing this issue, but it wasn't and it looks like a laborious major restructuring to make it assemble with CamelForth.
My current thought is to abandon the attempt to convert to CamelForth assembly and use a regular assembler with the code as-is and attach the assembled binary to my Forth cross compiled binary and jump to the routines instead.
But before I do that, I thought I'd just ask around if anyone else had done something similar or had any further words of wisdom that might switch a light on.
Thanks for reading.
(And sorry it's not pure 6502. Actually, I have the idea to create a new cartridge for the Vectrex that would halt the 6809 and instead contain an '816 and a program ROM, which could then take over the system. People have already done it with an ARM, why not a '816?)
(My first post, I think!)
I have a question that I thought might be best considered here, even though it's not strictly 6502 related *ahem*, but bear with me.
I'm using Brad R's excellent CamelForth/6809 that I have running on the Vectrex video game console, from 1982. My current project consists of salvaging a 6809 binary from a late 1970's FLEX disk and porting it onto the Vectrex. CamelForth/6809 comes with the 'Chromium' cross-compiler/assembler and I've started converting the FLEX binary, which I've disassembled, into CamelForth assembly.
First tests are good, the assembly works as expected, but then I hit a number of flag tests/branches and associated forward jumps, which the cross-assembler can't handle. Luckily, CamelForth assembler has IF, ELSE, THEN, etc. that in most cases I can substitute in.
But now I'm a bit further into the code, I'm hitting routines where the code flow jumps all over the place, e.g. out of BEGIN, UNTIL, loops, including using out of sequence IF, THEN,. Clearly, if the code was built using IF, THEN, etc. in the first place, I wouldn't be seeing this issue, but it wasn't and it looks like a laborious major restructuring to make it assemble with CamelForth.
My current thought is to abandon the attempt to convert to CamelForth assembly and use a regular assembler with the code as-is and attach the assembled binary to my Forth cross compiled binary and jump to the routines instead.
But before I do that, I thought I'd just ask around if anyone else had done something similar or had any further words of wisdom that might switch a light on.
Thanks for reading.
(And sorry it's not pure 6502. Actually, I have the idea to create a new cartridge for the Vectrex that would halt the 6809 and instead contain an '816 and a program ROM, which could then take over the system. People have already done it with an ARM, why not a '816?)
P*h*i*l*l*i*p EEaattoon in real life
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: Converting traditional assembly code to Forth assembly c
pjeaton wrote:
Hi All,
(My first post, I think!)
(My first post, I think!)
Quote:
I have a question that I thought might be best considered here, even though it's not strictly 6502 related *ahem*, but bear with me. [...] (And sorry it's not pure 6502. Actually, I have the idea to create a new cartridge for the Vectrex that would halt the 6809 and instead contain an '816 and a program ROM, which could then take over the system. People have already done it with an ARM, why not a '816?)
The 65816 part is the 65 content, which is good, because this is a 65 forum, and the subtitle for this Forth section of the forum, as shown on the front page of the forum, is "Topics relating to various Forth models on the 6502, 65816, and related microprocessors and microcontrollers."
Quote:
My current thought is to abandon the attempt to convert to CamelForth assembly and use a regular assembler with the code as-is and attach the assembled binary to my Forth cross-compiled binary and jump to the routines instead.
But before I do that, I thought I'd just ask around if anyone else had done something similar
But before I do that, I thought I'd just ask around if anyone else had done something similar
Are you trying to come up with an assembly-language source code for a Forth kernel, to be assembled by a non-Forth assembler? This will have various pros and cons. I have to tried to disassemble anyone's Forth, but I've written my own '816 Forth source to assemble with the C32 cross-assembler. I've also assembled shorter pieces of code on the PC and had my already-running Forth (on the workbench computer) take it in as an Intel Hex file, then call it from the previously existing Forth. Can you be more specific about what problems you're having?
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
Re: Converting traditional assembly code to Forth assembly c
Oh wow, you're right, I'm not a newbie after all, although most of my posts were 16 years ago, I guess they slipped my mind!
I hesitated before posting that I was using a 6809 as this is really just a structured-assembly & Forth question and I knew structured-assembly was definitely on topic here, but I thought I'd just be transparent up front.
The '816 part is definitely real, though, as there are a lot of 6502 programmers out there and due to the 6809 not having the power to provide much in the way of on-the-fly animation, you need to pre-calculate animation vector lists and that quickly eats up the 48KiB of usable address space. The '816 expanded memory management could be a more elegant solution than is currently used.
So, my project...what I'm actually trying to do is port the first Sargon chess engine from the SWTPC 6809 to the Vectrex 6809. The custom user interface interface part I will strip out and initially I'll run the engine from the Forth interpreter via a terminal I have connected. Later, I'll create an interface for the vector monitor, using Forth.
I have an early non-working iteration of the 6809 code, but the actual working** binary code was updated quite a lot afterwards, but that later source code is lost, so I've disassembled the working binary to get something that resembles the source code and re-engineered it with the comments etc from the non-working early version.
**Actually it's mostly working, you can play a game on the SWTPC, but it doesn't make the same moves as the original Z80 real Sargon engine does, I need to fix that.
I do this with my CamelForth on the Vectrex, I call the BIOS ROM routines that allow me to draw on the screen, read the joystick, play music etc. via the 6522/AY38910/DAC, without having to write my own drivers.
One part I've already converted is this code below (almost assembles). The layout I'm not too sure about, but the problem is the MP15 branch half way down, it jumps out of a BEGIN, UNTIL, loop and that's going probably going to break an IF, THEN, compilation. The MP20 branch does the same thing again. I could probably fix this routine quite easily, either by restructuring the code or changing the Forth cross assembler to not use the same address variable for BEGIN, and IF, patching, but subsequent routines are much more complicated.
I guess I'm just batting round the pros and cons of Forth jumping to a separately assembled block of code as opposed to merging the assembly source with the Forth source, the latter being much neater and would mean I don't need to stitch the separate binary code together.
Quote:
...and related microprocessors and microcontrollers."
The '816 part is definitely real, though, as there are a lot of 6502 programmers out there and due to the 6809 not having the power to provide much in the way of on-the-fly animation, you need to pre-calculate animation vector lists and that quickly eats up the 48KiB of usable address space. The '816 expanded memory management could be a more elegant solution than is currently used.
So, my project...what I'm actually trying to do is port the first Sargon chess engine from the SWTPC 6809 to the Vectrex 6809. The custom user interface interface part I will strip out and initially I'll run the engine from the Forth interpreter via a terminal I have connected. Later, I'll create an interface for the vector monitor, using Forth.
I have an early non-working iteration of the 6809 code, but the actual working** binary code was updated quite a lot afterwards, but that later source code is lost, so I've disassembled the working binary to get something that resembles the source code and re-engineered it with the comments etc from the non-working early version.
**Actually it's mostly working, you can play a game on the SWTPC, but it doesn't make the same moves as the original Z80 real Sargon engine does, I need to fix that.
Quote:
I've also assembled shorter pieces of code on the PC and had my already-running Forth (on the workbench computer) take it in as an Intel Hex file, then call it from the previously existing Forth...
One part I've already converted is this code below (almost assembles). The layout I'm not too sure about, but the problem is the MP15 branch half way down, it jumps out of a BEGIN, UNTIL, loop and that's going probably going to break an IF, THEN, compilation. The MP20 branch does the same thing again. I could probably fix this routine quite easily, either by restructuring the code or changing the Forth cross assembler to not use the same address variable for BEGIN, and IF, patching, but subsequent routines are much more complicated.
I guess I'm just batting round the pros and cons of Forth jumping to a separately assembled block of code as opposed to merging the assembly source with the Forth source, the latter being much neater and would mean I don't need to stitch the separate binary code together.
Code: Select all
10 asm:
11 here equ MPIECE
12 COLOR EORA, \ Restore color of piece
13 $87 # ANDA, \ Clear all flags except color flag
14 BPAWN # CMPA, \ Is it a black Pawn?
15 EQ IF, \ Skip if not (BNE)
16 DECA, \ Decrement by one for black Pawn
17 THEN, \
18 7 # ANDA, \ Clear color flag
19 M1 STA, \ Save piece type (M1+1 ??????, then xfer to Y?)
20 M1 LDY, \ Load index to DCOUNT/DPOINT
21 Y DCOUNT , LDB, \ Get direction count
22 Y DPOINT , LDA, \ Get direction pointer
23 INDX2 STA, \ Save as index to direction table (INDX2+1 ????)
24 INDX2 LDY, \ Load index to direction table
25 BEGIN,
26 Y DIRECT , LDA, \ Get move direction and increment pointer
27 RC STA, \ Save it as parameter for PATH
28 M1 LDA, \ Get "from" position (M1+1 ?????)
29 M2 STA, \ Initialize "to" position (M1+2 ?????)
30 BEGIN,
31 PATH JSR, \ Calculate next position
32 #2 CMPA, \ Ready for new direction?
33 \ MP15 BCC, \ Branch if yes
34 RC STA, \ Check for empty square and save results
35 T1 LDA, \ Get moved piece
36 PAWN+1 # CMPA, \ Is it a Pawn? ( THIS ISN'T RIGHT)
37 MP20 BCS, \ Branch if yes
38 ADMOVE JSR, \ Add move to list
39 RC LDA, \ Restore previous test results
40 MP15 BNE, \ Branch if "to" square not empty
41 T1+1 LDA, \ Get piece type
42 #KING CMPA, \ Is it a King?
43 MP15 BEQ, \ Branch if yes
44 #BISHOP CMPA, \ Is it a Bishop, Rook, or Queen?
45 LO UNTIL, \ Branch if any of the above (BHS Branch if Higher or Same)
46 ( MP15 ) DECB, \ Decrement direction counter
47 EQ UNTIL, \ Do next direction, if any
48 T1+1 LDA, \ Get piece type
49 #KING CMPA, \ is it a King?
50 MERTN BNE,
51 EQ IF, \ Return if not (BNE)
52 CASTLE JMP, \ Consider castling
53 THEN,
54 RTS, \ Return
55 ;c
Last edited by pjeaton on Thu Jan 07, 2021 2:47 pm, edited 2 times in total.
P*h*i*l*l*i*p EEaattoon in real life
Re: Converting traditional assembly code to Forth assembly c
It sounds to as if the problem is that the 'Chromium' cross-compiler/assembler allows only structured control flow -- IOW, it will accept IF, ELSE, ENDIF, in the source code but not BNE BEQ and so on.
I have dealt with a similar situation when using Bill Ragsdale's RPN assembler for 6502. It sometimes happen that I want to use conditionals in an unstructured way, and I accomplish this by adding snippets in the source code, snippets which cause the necessary bytes to output. For example, ...
D0 C, DESIRED_DESTINATION HERE 1+ - C,
... will cause a BNE opcode then its 8-bit offset to be assembled.
This is somewhat messy, but it lets me "break the rules" and bypass the compiler security checking which otherwise insists that IF pair with ENDIF and so on... and the code Phillip is trying to reproduce breaks the rules in the same way.
If you search on this forum for "compiler security" then you'll find there's another (arguably less messy) way, and that involves juggling items the assembler has on stack instead. Here is one post in that vein.
In Brad R's documentation, does he talk about bypassing compiler security checking?
Returning to my example, it's a backward branch which I assemble. DESIRED_DESTINATION is a constant created previously, of course. But it'll save memory if the associated address can simply be left on the stack.
This is not a complete description; for example I haven't addressed the question of forward branches. If necessary I will, but I'm hoping you will instead find the necessary answer in Brad's doc or in other posts on this forum.
Edit: missed your latest post while I was typing, Phillip.
If you tell me what is the destination of the MP15 branch half way down then I'll try to illustrate a solution.
-- Jeff
I have dealt with a similar situation when using Bill Ragsdale's RPN assembler for 6502. It sometimes happen that I want to use conditionals in an unstructured way, and I accomplish this by adding snippets in the source code, snippets which cause the necessary bytes to output. For example, ...
D0 C, DESIRED_DESTINATION HERE 1+ - C,
... will cause a BNE opcode then its 8-bit offset to be assembled.
This is somewhat messy, but it lets me "break the rules" and bypass the compiler security checking which otherwise insists that IF pair with ENDIF and so on... and the code Phillip is trying to reproduce breaks the rules in the same way.
If you search on this forum for "compiler security" then you'll find there's another (arguably less messy) way, and that involves juggling items the assembler has on stack instead. Here is one post in that vein.
In Brad R's documentation, does he talk about bypassing compiler security checking?
Returning to my example, it's a backward branch which I assemble. DESIRED_DESTINATION is a constant created previously, of course. But it'll save memory if the associated address can simply be left on the stack.
This is not a complete description; for example I haven't addressed the question of forward branches. If necessary I will, but I'm hoping you will instead find the necessary answer in Brad's doc or in other posts on this forum.
Edit: missed your latest post while I was typing, Phillip.
If you tell me what is the destination of the MP15 branch half way down then I'll try to illustrate a solution.
-- Jeff
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html
https://laughtonelectronics.com/Arcana/ ... mmary.html
Re: Converting traditional assembly code to Forth assembly c
Jeff, thanks for your thoughts.
So you have a similar problem with an 'engineering' workaround, glad I'm not alone. The Forth assembler will handle BEQ, etc, but it can't handle a forward reference as the target. In the listing I gave (I now added line numbers) the BEQ,/BNE, on lines 33 and 40 jump out of the BEGIN, UNTIL, to line 46. Similarly, the BCS on line 37 jumps right past the code snippet I've gave.
I hadn't thought to review the documentation of the assembler, I guess I should have a look, but I'm not expecting it to be accommodated, I think it stated somewhere that it's not supposed to be a full-blown assembler, it's for putting together speed-up routines for Forth purposes. For new code design, I think it's control structures would be quite beneficial.
Perhaps I should look into changing the assembler to be able to handle forward references cleanly.
I just looked in the manual for MPEs VFX forth and it too offers both IF, THEN, etc. and labels, including forward referenced ones, it even has an example showing the same code written with each method. It can also work in prefix and postfix modes - oh the luxury!
I'll also checkout the links you provided - thanks!
Phil
So you have a similar problem with an 'engineering' workaround, glad I'm not alone. The Forth assembler will handle BEQ, etc, but it can't handle a forward reference as the target. In the listing I gave (I now added line numbers) the BEQ,/BNE, on lines 33 and 40 jump out of the BEGIN, UNTIL, to line 46. Similarly, the BCS on line 37 jumps right past the code snippet I've gave.
I hadn't thought to review the documentation of the assembler, I guess I should have a look, but I'm not expecting it to be accommodated, I think it stated somewhere that it's not supposed to be a full-blown assembler, it's for putting together speed-up routines for Forth purposes. For new code design, I think it's control structures would be quite beneficial.
Perhaps I should look into changing the assembler to be able to handle forward references cleanly.
I just looked in the manual for MPEs VFX forth and it too offers both IF, THEN, etc. and labels, including forward referenced ones, it even has an example showing the same code written with each method. It can also work in prefix and postfix modes - oh the luxury!
I'll also checkout the links you provided - thanks!
Phil
P*h*i*l*l*i*p EEaattoon in real life
Re: Converting traditional assembly code to Forth assembly c
pjeaton wrote:
I hadn't thought to review the documentation of the assembler, I guess I should have a look, but I'm not expecting it to be accommodated
-- Jeff
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html
https://laughtonelectronics.com/Arcana/ ... mmary.html
Re: Converting traditional assembly code to Forth assembly c
Quote:
The layout I'm not too sure about, but the problem is the MP15 branch half way down, it jumps out of a BEGIN, UNTIL, loop and that's going probably going to break an IF, THEN, compilation. The MP20 branch does the same thing again. I could probably fix this routine quite easily, either by restructuring the code or changing the Forth cross assembler to not use the same address variable for BEGIN, and IF, patching, but subsequent routines are much more complicated.
Code: Select all
25 BEGIN,
26 Y DIRECT , LDA, \ Get move direction and increment pointer
27 RC STA, \ Save it as parameter for PATH
28 M1 LDA, \ Get "from" position (M1+1 ?????)
29 M2 STA, \ Initialize "to" position (M1+2 ?????)
30 BEGIN,
31 PATH JSR, \ Calculate next position
32 #2 CMPA, \ Ready for new direction?
33 \ MP15 BCC, \ Branch if yes
34 RC STA, \ Check for empty square and save results
35 T1 LDA, \ Get moved piece
36 PAWN+1 # CMPA, \ Is it a Pawn? ( THIS ISN'T RIGHT)
37 MP20 BCS, \ Branch if yes
38 ADMOVE JSR, \ Add move to list
39 RC LDA, \ Restore previous test results
40 MP15 BNE, \ Branch if "to" square not empty
41 T1+1 LDA, \ Get piece type
42 #KING CMPA, \ Is it a King?
43 MP15 BEQ, \ Branch if yes
44 #BISHOP CMPA, \ Is it a Bishop, Rook, or Queen?
45 LO UNTIL, \ Branch if any of the above (BHS Branch if Higher or Same)
46 ( MP15 ) DECB, \ Decrement direction counter
...
60 ( MP20 )
Maybe something like this which allows for up to 3 forward branches that overlap:
: IF1 HERE 1- 0 ! ;
: IF2 HERE 1- 2 ! ;
: IF3 HERE 1- 4 ! ;
: THEN1 HERE 0 @ - 0 @ C! ;
: THEN2 HERE 2 @ - 2 @ C! ;
: THEN3 HERE 4 @ - 4 @ C! ;
The zero-page addresses 0,2,4 are locations that are free for use but can be made into variables instead if preferred.
Now the code becomes
Code: Select all
25 BEGIN,
26 Y DIRECT , LDA, \ Get move direction and increment pointer
27 RC STA, \ Save it as parameter for PATH
28 M1 LDA, \ Get "from" position (M1+1 ?????)
29 M2 STA, \ Initialize "to" position (M1+2 ?????)
30 BEGIN,
31 PATH JSR, \ Calculate next position
32 #2 CMPA, \ Ready for new direction?
***
HERE EQU MP15 \ calculates a backward branch just to reserve the BCC opcode
***
33 MP15 BCC, \ Branch if yes
***
IF1 \ IF1 backs up the DP one byte so THEN1 overwrites the branch value after BCC
***
34 RC STA, \ Check for empty square and save results
35 T1 LDA, \ Get moved piece
36 PAWN+1 # CMPA, \ Is it a Pawn? ( THIS ISN'T RIGHT)
***
HERE EQU MP20
***
37 MP20 BCS, \ reserve the BCS opcode
***
IF2 \ IF2 backs up the DP one byte so THEN2 overwrites the branch value after BCS
***
38 ADMOVE JSR, \ Add move to list
39 RC LDA, \ Restore previous test results
40 MP15 BNE, \ Branch if "to" square not empty
41 T1+1 LDA, \ Get piece type
42 #KING CMPA, \ Is it a King?
43 MP15 BEQ, \ Branch if yes
44 #BISHOP CMPA, \ Is it a Bishop, Rook, or Queen?
45 LO UNTIL, \ Branch if any of the above (BHS Branch if Higher or Same)
***
46 ( MP15 ) THEN1 \ stores the forward offset difference at IF1
***
DECB, \ Decrement direction counter
...
***
60 ( MP20 ) THEN2 \ stores the forward offset difference at IF2
***
Re: Converting traditional assembly code to Forth assembly c
Add these words to work with inline absolute code.
: IFABSL HERE 2- 0 ! ;
: IFABSH HERE 2- 2 ! ;
: THENABSL HERE 2- 0 @ ! ; \ absolute address points to lo-byte
: THENABSH HERE 1- 2 @ ! ; \ absolute address points to hi-byte
becomes
: IFABSL HERE 2- 0 ! ;
: IFABSH HERE 2- 2 ! ;
: THENABSL HERE 2- 0 @ ! ; \ absolute address points to lo-byte
: THENABSH HERE 1- 2 @ ! ; \ absolute address points to hi-byte
Code: Select all
CALL DW *+2
STX XSAVE ; ($F5)
LDA 0,X
STA CALL1+1
LDA 1,X
STA CALL1+2
LDA 0 ; Acc
LDX 1 ; X-reg
LDY 2 ; Y-reg
CALL1 JSR $FF58 ; place holder
STY 2
STX 1
STA 0
LDX XSAVE ; ($F5)
INX
INX
JMP NEXT
Code: Select all
CALL DW *+2
STX XSAVE ; ($F5)
LDA $0,X
STA $0000
***
IFABSL
***
LDA $1,X
STA $0000
***
IFABSH
***
LDA $6 ; Acc
LDX $7 ; X-reg
LDY $8 ; Y-reg
JSR $0000 ; place holder
***
THENABSL \ store absolute pointer for lo-byte
THENABSH \ store absolute pointer for hi-byte
***
STY $8
STX $7
STA $6
LDX XSAVE ; ($F5)
INX
INX
JMP NEXT
Re: Converting traditional assembly code to Forth assembly c
Thanks for your thoughts, guys.
I read the other threads mentioned and though Brad's assembler documentation and it's sparse, with nothing on forward references. In fact, the only reference I had seen was actually in the Camel Forth primitives declarations, where he uses HERE EQU <LABEL> to create backward references in few circumstances where structured programming might have been a bit verbose.
But I did come across this text below, regarding the Forth assembler, in one of Brad's papers:
So clearly, he wasn't a fan of labels and from what I can see of the source code, there's no specific functionality to accommodate them, but this does provide food for thought.
Now as I was thinking about this, I also came up with the idea of following a HERE EQU <LABEL> with a dummy C, address and then storing the target address using something like HERE <LABEL> C! (but with the subtraction to make it relative). I'm actually doing this a part of the regular CamelForth cross-compile, because the Vectrex has a hardcoded program start address, so I have to create a placeholder JMP address at that hardcoded address to then boot Forth and I backfill that placeholder when I know where it should JMP to.
And, now I see the suggested IF1, IF2, and IF3, as a less kludgy way of doing the same. It's not pretty, but it looks like it could do the job. I guess it would be something like the existing structured conditionals I found, but not using the stack for storage:
Now I need to do some more experiments - thanks again!
I read the other threads mentioned and though Brad's assembler documentation and it's sparse, with nothing on forward references. In fact, the only reference I had seen was actually in the Camel Forth primitives declarations, where he uses HERE EQU <LABEL> to create backward references in few circumstances where structured programming might have been a bit verbose.
But I did come across this text below, regarding the Forth assembler, in one of Brad's papers:
Quote:
4. Labels
Even in the era of structured programming, some programmers
will insist on labels in their assembler code.
The principal problem with named labels in a Forth assembler
definition is that the labels themselves are Forth words.
They are compiled into the dictionary -- usually at an
inconvenient point, such as inside the machine code. For
example:
CODE TEST ... machine code ...
HERE CONSTANT LABEL1
... machine code ...
LABEL1 NZ JP,
will cause the dictionary header for LABEL1 -- text, links,
and all -- to be inserted in the middle of CODE. Several
solutions have been proposed:
a) define labels only "outside" machine code.
Occasionally useful, but very restricted.
b) use some predefined storage locations (variables) to
provide "temporary," or local, labels.
c) use a separate dictionary space for the labels, e.g.,
as provided by the TRANSIENT scheme [3].
d) use a separate dictionary space for the machine code.
This is common practice for meta-compilation; most
Forth meta- compilers support labels with little
difficulty.
Even in the era of structured programming, some programmers
will insist on labels in their assembler code.
The principal problem with named labels in a Forth assembler
definition is that the labels themselves are Forth words.
They are compiled into the dictionary -- usually at an
inconvenient point, such as inside the machine code. For
example:
CODE TEST ... machine code ...
HERE CONSTANT LABEL1
... machine code ...
LABEL1 NZ JP,
will cause the dictionary header for LABEL1 -- text, links,
and all -- to be inserted in the middle of CODE. Several
solutions have been proposed:
a) define labels only "outside" machine code.
Occasionally useful, but very restricted.
b) use some predefined storage locations (variables) to
provide "temporary," or local, labels.
c) use a separate dictionary space for the labels, e.g.,
as provided by the TRANSIENT scheme [3].
d) use a separate dictionary space for the machine code.
This is common practice for meta-compilation; most
Forth meta- compilers support labels with little
difficulty.
Now as I was thinking about this, I also came up with the idea of following a HERE EQU <LABEL> with a dummy C, address and then storing the target address using something like HERE <LABEL> C! (but with the subtraction to make it relative). I'm actually doing this a part of the regular CamelForth cross-compile, because the Vectrex has a hardcoded program start address, so I have to create a placeholder JMP address at that hardcoded address to then boot Forth and I backfill that placeholder when I know where it should JMP to.
And, now I see the suggested IF1, IF2, and IF3, as a less kludgy way of doing the same. It's not pretty, but it looks like it could do the job. I guess it would be something like the existing structured conditionals I found, but not using the stack for storage:
Code: Select all
: IF, \ br.opcode -- adr.next.instr | reserve space
TC, 0 TC, THERE ;
: ENDIF, \ adr.instr.after.br -- | patch the forward ref.
THERE OVER - DUP 8BIT? 0= ?ADRERR SWAP 1- TC! ;
: ELSE, \ adr.after.br -- adr.after.this.br
NVR IF, SWAP ENDIF, ;
: BEGIN, \ -- dest.adr
THERE ;
: UNTIL, \ dest.adr br.opcode --
TC, THERE 1+ - DUP 8BIT? 0= ?ADRERR TC, ;
: WHILE, \ dest.adr br.opcode -- adr.after.this dest.adr
IF, SWAP ;
: REPEAT, \ adr.after.while dest.adr.of.begin --
NVR UNTIL, ENDIF, ;
: THEN, ENDIF, ; : END, UNTIL, ;P*h*i*l*l*i*p EEaattoon in real life
Re: Converting traditional assembly code to Forth assembly c
IamRob wrote:
Since IF/THEN already handles forward branching, just rewrite the definitions and add them to the assembler.
Maybe something like this which allows for up to 3 forward branches that overlap:
: IF1 HERE 1- 0 ! ;
: IF2 HERE 1- 2 ! ;
: IF3 HERE 1- 4 ! ;
: THEN1 HERE 0 @ - 0 @ C! ;
: THEN2 HERE 2 @ - 2 @ C! ;
: THEN3 HERE 4 @ - 4 @ C! ;
The zero-page addresses 0,2,4 are locations that are free for use but can be made into variables instead if preferred.
Now the code becomes
Maybe something like this which allows for up to 3 forward branches that overlap:
: IF1 HERE 1- 0 ! ;
: IF2 HERE 1- 2 ! ;
: IF3 HERE 1- 4 ! ;
: THEN1 HERE 0 @ - 0 @ C! ;
: THEN2 HERE 2 @ - 2 @ C! ;
: THEN3 HERE 4 @ - 4 @ C! ;
The zero-page addresses 0,2,4 are locations that are free for use but can be made into variables instead if preferred.
Now the code becomes
Code: Select all
25 BEGIN,
26 Y DIRECT , LDA, \ Get move direction and increment pointer
27 RC STA, \ Save it as parameter for PATH
28 M1 LDA, \ Get "from" position (M1+1 ?????)
29 M2 STA, \ Initialize "to" position (M1+2 ?????)
30 BEGIN,
31 PATH JSR, \ Calculate next position
32 #2 CMPA, \ Ready for new direction?
***
HERE EQU MP15 \ calculates a backward branch just to reserve the BCC opcode
***
33 MP15 BCC, \ Branch if yes
***
IF1 \ IF1 backs up the DP one byte so THEN1 overwrites the branch value after BCC
***
34 RC STA, \ Check for empty square and save results
35 T1 LDA, \ Get moved piece
36 PAWN+1 # CMPA, \ Is it a Pawn? ( THIS ISN'T RIGHT)
***
HERE EQU MP20
***
37 MP20 BCS, \ reserve the BCS opcode
***
IF2 \ IF2 backs up the DP one byte so THEN2 overwrites the branch value after BCS
***
38 ADMOVE JSR, \ Add move to list
39 RC LDA, \ Restore previous test results
40 MP15 BNE, \ Branch if "to" square not empty
41 T1+1 LDA, \ Get piece type
42 #KING CMPA, \ Is it a King?
43 MP15 BEQ, \ Branch if yes
44 #BISHOP CMPA, \ Is it a Bishop, Rook, or Queen?
45 LO UNTIL, \ Branch if any of the above (BHS Branch if Higher or Same)
***
46 ( MP15 ) THEN1 \ stores the forward offset difference at IF1
***
DECB, \ Decrement direction counter
...
***
60 ( MP20 ) THEN2 \ stores the forward offset difference at IF2
***
To me, this started looking like an array of addresses. I then came up with this to simplify things:
: ARRAY <BUILDS 2 * ALLOT DOES> 2 * + ;
6 ARRAY BRANCHES ( reserves 6 cells ) \ Branch #'s are 0-5 and this number can be easily adjusted to handle more cells
: IFBR ( n --- ) BRANCHES HERE 1- SWAP ! ; \ store address in array
: THENBR ( n --- ) BRANCHES @ DUP HERE SWAP - SWAP ! ; \ restore address, calculate branch offset and store offset
now the code becomes:
Code: Select all
[code]
25 BEGIN,
26 Y DIRECT , LDA, \ Get move direction and increment pointer
27 RC STA, \ Save it as parameter for PATH
28 M1 LDA, \ Get "from" position (M1+1 ?????)
29 M2 STA, \ Initialize "to" position (M1+2 ?????)
30 BEGIN,
31 PATH JSR, \ Calculate next position
32 #2 CMPA, \ Ready for new direction?
***
HERE EQU MP15 \ calculates a backward branch just to reserve the BCC opcode
***
33 MP15 BCC, \ Branch if yes
***
1 IFBR \ IF1 backs up the DP one byte so THEN1 overwrites the branch value after BCC
***
34 RC STA, \ Check for empty square and save results
35 T1 LDA, \ Get moved piece
36 PAWN+1 # CMPA, \ Is it a Pawn? ( THIS ISN'T RIGHT)
***
HERE EQU MP20
***
37 MP20 BCS, \ reserve the BCS opcode
***
2 IFBR \ IF2 backs up the DP one byte so THEN2 overwrites the branch value after BCS
***
38 ADMOVE JSR, \ Add move to list
39 RC LDA, \ Restore previous test results
40 MP15 BNE, \ Branch if "to" square not empty
***
3 IFBR
***
41 T1+1 LDA, \ Get piece type
42 #KING CMPA, \ Is it a King?
43 MP15 BEQ, \ Branch if yes
***
4 IFBR
***
44 #BISHOP CMPA, \ Is it a Bishop, Rook, or Queen?
45 LO UNTIL, \ Branch if any of the above (BHS Branch if Higher or Same)
***
46 ( MP15 ) 1 THENBR \ stores forward offset at IFBR #1
3 THENBR \ stores forward offset at IFBR #3
4 THENBR \ stores forward offset at IFBR #4
***
DECB, \ Decrement direction counter
...
***
60 ( MP20 ) 2 THENBR \ stores the forward offset at IFBR #2
***
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: Converting traditional assembly code to Forth assembly c
pjeaton wrote:
Thanks for your thoughts, guys.
I read the other threads mentioned and though Brad's assembler documentation and it's sparse, with nothing on forward references. In fact, the only reference I had seen was actually in the Camel Forth primitives declarations, where he uses HERE EQU <LABEL> to create backward references in few circumstances where structured programming might have been a bit verbose.
But I did come across this text below, regarding the Forth assembler, in one of Brad's papers:
So clearly, he wasn't a fan of labels and from what I can see of the source code, there's no specific functionality to accommodate them, but this does provide food for thought.
I read the other threads mentioned and though Brad's assembler documentation and it's sparse, with nothing on forward references. In fact, the only reference I had seen was actually in the Camel Forth primitives declarations, where he uses HERE EQU <LABEL> to create backward references in few circumstances where structured programming might have been a bit verbose.
But I did come across this text below, regarding the Forth assembler, in one of Brad's papers:
Quote:
Code: Select all
4. Labels
Even in the era of structured programming, some programmers
will insist on labels in their assembler code.
The principal problem with named labels in a Forth assembler
definition is that the labels themselves are Forth words.
They are compiled into the dictionary -- usually at an
inconvenient point, such as inside the machine code. For
example:
CODE TEST ... machine code ...
HERE CONSTANT LABEL1
... machine code ...
LABEL1 NZ JP,
will cause the dictionary header for LABEL1 -- text, links,
and all -- to be inserted in the middle of CODE. Several
solutions have been proposed:
a) define labels only "outside" machine code.
Occasionally useful, but very restricted.
b) use some predefined storage locations (variables) to
provide "temporary," or local, labels.
c) use a separate dictionary space for the labels, e.g.,
as provided by the TRANSIENT scheme [3].
d) use a separate dictionary space for the machine code.
This is common practice for meta-compilation; most
Forth meta- compilers support labels with little
difficulty.http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
Re: Converting traditional assembly code to Forth assembly c
Quote:
To me, this started looking like an array of addresses. I then came up with this to simplify things:
: ARRAY <BUILDS 2 * ALLOT DOES> 2 * + ;
: ARRAY <BUILDS 2 * ALLOT DOES> 2 * + ;
P*h*i*l*l*i*p EEaattoon in real life
Re: Converting traditional assembly code to Forth assembly c
I've done an experiment and, roughly, I have a working proof of concept.
The following code (lots of commented out lines, as they don't assemble yet):
assembles to: (copied from the Vectrex "VIDE" emulator debugger, <<< lines added manually)
I've also just read through Garth's links and it looks like he's already done this almost exactly, with his clean label version!
However, just doing this PoC has brought to my attention that the CamelForth assembler doesn't produce a list file, so the only way to look at the code is via the debugger and that's a bit clunky and there are still no labels brought across. (I actually have a fix for that, but it's more work.)
Given the code I have is actually disassembled from a mostly working programme, I'm leaning towards a relative simple reassembly with a standalone assembler and call the binary code subroutines from Forth for debugging. I have other improvements to make in CamelForth, it doesn't have a CASE or ?DO words for starters, which is already fiddly. The whole point of this exercise is to make working Vectrex programs, improving Forth for Forth's sake is a secondary priority. (Though a lot of fun and Garth's clean labels solution is tempting
).
Also, converting between postfix and prefix and changing all branches to structures is bound to introduce bugs that will need extra testing and will still be messy because the code wasn't originally written with structure in mind.
Nonetheless, whichever way I go, this has been a super learning exercise, I really appreciate your input to the conversation.
The following code (lots of commented out lines, as they don't assemble yet):
Code: Select all
RC STA, \ Save it as parameter for PATH
M1 LDA, \ Get "from" position (M1+1 ?????)
M2 STA, \ Initialize "to" position (M1+2 ?????)
BEGIN,
PATH JSR, \ Calculate next position
2 # CMPA, \ Ready for new direction?
\ MP15 BCC, \ Branch if yes
here BCC,
here 1 - equ MP15
RC STA, \ Check for empty square and save results
T1 LDA, \ Get moved piece
PAWN+1 # CMPA, \ Is it a Pawn? ( THIS ISN'T RIGHT)
\ MP20 BCS, \ Branch if yes
\ ADMOVE JSR, \ Add move to list
RC LDA, \ Restore previous test results
\ MP15 BNE, \ Branch if "to" square not empty
\ T1+1 LDA, \ Get piece type
\ #KING CMPA, \ Is it a King?
\ MP15 BEQ, \ Branch if yes
\ #BISHOP CMPA, \ Is it a Bishop, Rook, or Queen?
LO UNTIL, \ Branch if any of the above (BHS Branch if Higher or Same)
here MP15 - 1 - MP15 c!
DECB, \ Decrement direction counterCode: Select all
Address Label Content Mnemon Operand ~ ->Address
--------------------------------------------------------
$4C57 B7 4B 39 sta >_4B39 5 $4B39
$4C5A B6 4B 27 lda >_4B27 5 $4B27
$4C5D B7 4B 29 sta >_4B29 5 $4B29
<<<< BEGIN,
$4C60 _4C60: BD 4B F3 jsr >_4BF3 8 $4BF3
$4C63 81 02 cmpa #$02 2
$4C65 24 0D bcc _4C74 3 $0D <<<< BCC Patched OK
$4C67 B7 4B 39 sta >_4B39 5 $4B39
$4C6A B6 4B 2F lda >_4B2F 5 $4B2F
$4C6D 81 F4 cmpa #$F4 2
$4C6F B6 4B 39 lda >_4B39 5 $4B39
$4C72 24 EC bcc _4C60 3 -$14 <<<< UNTIL,
<<<< BCC Target
$4C74 _4C74: 5A decb 2 However, just doing this PoC has brought to my attention that the CamelForth assembler doesn't produce a list file, so the only way to look at the code is via the debugger and that's a bit clunky and there are still no labels brought across. (I actually have a fix for that, but it's more work.)
Given the code I have is actually disassembled from a mostly working programme, I'm leaning towards a relative simple reassembly with a standalone assembler and call the binary code subroutines from Forth for debugging. I have other improvements to make in CamelForth, it doesn't have a CASE or ?DO words for starters, which is already fiddly. The whole point of this exercise is to make working Vectrex programs, improving Forth for Forth's sake is a secondary priority. (Though a lot of fun and Garth's clean labels solution is tempting
Also, converting between postfix and prefix and changing all branches to structures is bound to introduce bugs that will need extra testing and will still be messy because the code wasn't originally written with structure in mind.
Nonetheless, whichever way I go, this has been a super learning exercise, I really appreciate your input to the conversation.
P*h*i*l*l*i*p EEaattoon in real life
Re: Converting traditional assembly code to Forth assembly c
pjeaton wrote:
I guess it would be something like the existing structured conditionals I found, but not using the stack for storage:
Now I need to do some more experiments - thanks again!
Code: Select all
: IF, \ br.opcode -- adr.next.instr | reserve space
TC, 0 TC, THERE ;
: ENDIF, \ adr.instr.after.br -- | patch the forward ref.
THERE OVER - DUP 8BIT? 0= ?ADRERR SWAP 1- TC! ;
: ELSE, \ adr.after.br -- adr.after.this.br
NVR IF, SWAP ENDIF, ;
: BEGIN, \ -- dest.adr
THERE ;
: UNTIL, \ dest.adr br.opcode --
TC, THERE 1+ - DUP 8BIT? 0= ?ADRERR TC, ;
: WHILE, \ dest.adr br.opcode -- adr.after.this dest.adr
IF, SWAP ;
: REPEAT, \ adr.after.while dest.adr.of.begin --
NVR UNTIL, ENDIF, ;
: THEN, ENDIF, ; : END, UNTIL, ;
That looks like source for a metacompiler's assembler.
Re: Converting traditional assembly code to Forth assembly c
pjeaton wrote:
So clearly, he wasn't a fan of labels and from what I can see of the source code, there's no specific functionality to accommodate them, but this does provide food for thought.
I don't see a bias against labels, just a statement of the difficulty with using them in Forth followed by four possible solutions.
My Forth is built with a metacompiler and the metacompiler's assembler is similar to the Forth assembler.
Although I don't use labels when writing CODE words in Forth, I do use labels in the source for some of the primitives in my Forth's kernel.