Page 1 of 2

Converting traditional assembly code to Forth assembly code

Posted: Wed Jan 06, 2021 9:25 pm
by pjeaton
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?)

Re: Converting traditional assembly code to Forth assembly c

Posted: Wed Jan 06, 2021 11:39 pm
by GARTHWILSON
pjeaton wrote:
Hi All,

(My first post, I think!)
It's your 10th post, the first one in 27 months.

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

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?

Re: Converting traditional assembly code to Forth assembly c

Posted: Thu Jan 07, 2021 12:44 am
by pjeaton
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!
Quote:
...and related microprocessors and microcontrollers."
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.
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...
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.

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

Re: Converting traditional assembly code to Forth assembly c

Posted: Thu Jan 07, 2021 1:25 am
by Dr Jefyll
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

Re: Converting traditional assembly code to Forth assembly c

Posted: Thu Jan 07, 2021 3:36 pm
by pjeaton
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

Re: Converting traditional assembly code to Forth assembly c

Posted: Thu Jan 07, 2021 3:47 pm
by Dr Jefyll
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
Myself, I expect Brad will have something to say on the subject, so start your investigation here. The problem you're experiencing is fairly common, I suspect.

-- Jeff

Re: Converting traditional assembly code to Forth assembly c

Posted: Thu Jan 07, 2021 5:45 pm
by IamRob
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 )
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

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

Posted: Thu Jan 07, 2021 6:45 pm
by IamRob
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

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
becomes

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

Posted: Thu Jan 07, 2021 8:25 pm
by pjeaton
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:
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.
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:

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, ;
Now I need to do some more experiments - thanks again!

Re: Converting traditional assembly code to Forth assembly c

Posted: Thu Jan 07, 2021 9:36 pm
by IamRob
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

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
***
After studying this some more, I realize that one may have to create quite a few IFx/THENx statements. The (MP15) at lines 40 and 43, requires there to be a another IF3/THEN3 and IF4/THEN4 to be created. Line #46 would then have THEN1 THEN3 THEN4

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

Re: Converting traditional assembly code to Forth assembly c

Posted: Thu Jan 07, 2021 9:45 pm
by GARTHWILSON
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:
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.
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.
Regarding c) above, see my posts here and here, a little past the middle of each post, regarding compiling address constants in software buffers at the end of memory, which you can delete when you're done with them and they don't take any space in the final memory space.

Re: Converting traditional assembly code to Forth assembly c

Posted: Thu Jan 07, 2021 11:09 pm
by pjeaton
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 * + ;
Thanks - I will consider this also!

Re: Converting traditional assembly code to Forth assembly c

Posted: Thu Jan 07, 2021 11:57 pm
by pjeaton
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):

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 counter
assembles to: (copied from the Vectrex "VIDE" emulator debugger, <<< lines added manually)

Code: 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          
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.

Re: Converting traditional assembly code to Forth assembly c

Posted: Fri Jan 08, 2021 10:03 pm
by JimBoyd
pjeaton wrote:
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, ;
Now I need to do some more experiments - thanks again!

That looks like source for a metacompiler's assembler.

Re: Converting traditional assembly code to Forth assembly c

Posted: Fri Jan 08, 2021 10:45 pm
by JimBoyd
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.