Hi Garth, I'm not sure I see what you mean there. When you have parameters on a stack in Forth or assembly treating the stack in the same way, you will still have to copy them eventually to do anything useful with them. They get copied from the stack to the stack with something like DUP or OVER, and in my example they are also copied from the stack to the stack. Here is a slightly modified example (The screen is 256x128 with one byte per pixel so the X,Y to address calculation is just SCREEN_ADDRESS+(y<<8)+x):
Code:
FUNC DrawPixel
BEGIN_ARGS
BYTE xpos, ypos, color
BEGIN_VARS
WORD gfx_ptr
END_VARS
MOV.W #SCREEN_ADDRESS, gfx_ptr
LDA xpos,X
STA gfx_ptr,X ;256x128 screen so low byte is x coord
LDA gfx_ptr+1,X
CLC
ADC ypos,X
STA gfx_ptr+1,X ;high byte is #>SCREEN_ADDRESS+ypos
MOV.B color,(gfx_ptr,X)
END_FUNC
CALL DrawPixel, #20, #30, #COLOR_BLUE
This disassembles into something like this:
Code:
001: FUNC DrawPixel
DrawPixel:
002: BEGIN_ARGS
003: BYTE xpos, ypos, color
xpos set 0
ypos set 1
color set 2
004: BEGIN_VARS
005: WORD gfx_ptr
gfx_ptr set 3
006: END_VARS
PHX ;save stack pointer
DEX ;room on stack for xpos
DEX ;room on stack for ypos
DEX ;room on stack for color
DEX ;room on stack for gfx_ptr (low byte)
DEX ;room on stack for gfx_ptr (high byte)
007: MOV.W #SCREEN_ADDRESS, gfx_ptr
LDA #<SCREEN_ADDRESS
STA 3,X ;3=gfx_ptr
LDA #>SCREEN_ADDRESS
STA 4,X ;4=gfx_ptr+1
008: LDA xpos,X
LDA 0,X
009: STA gfx_ptr,X
STA 3,X
010: LDA gfx_ptr+1,X
LDA 4,X
011: CLC
012: ADC ypos,X
ADC 1,X
013: STA gfx_ptr+1,X
STA 4,X
014: MOV.B color,(gfx_ptr,X)
LDA 2,X ;2=color
STA (3,X)
015: END_FUNC
PLX ;restore stack
RTS
016: CALL DrawPixel, #20, #30, #COLOR_BLUE
LDA #20
STA -5,X
LDA #30
STA -4,X
LDA #COLOR_BLUE
STA -3,X
;-1,X and -2,X left for gfx_ptr
JSR DrawPixel
In Forth it would be like this:
Code:
: DrawPixel ( x y color -- )
-ROT 8 LSHIFT SCREEN_ADDRESS + + c! ;
20 30 COLOR_BLUE DrawPixel
The code to copy the literals to the stack at 016 knows to copy the first value 5 bytes below the stack pointer since it knows that DrawPixel will adjust the stack pointer down by 5 at 006. This means the value of #20 will be at 0,X after the adjustment. 0,X in DrawPixel is xpos, which is where we want #20 to be.
Note that the arguments don't have to be immediates. If DrawPixel were called with arguments that were variables inside a function defined with BEGIN_VARS, then the LDA / STA pairs at 016 would be copying from zp,X to zp,X like you have with DUP and OVER.
I haven't figured out how many cycles the Forth version would take but just the overhead of DEX / INX would make it less efficient. I think the difference is much more noticeable in cases where you need to juggle many variables in something stack-based and those variables are accessed many times in a function/word. For example, keeping track of five counters like the following is unwieldy in Forth, whereas you get a big speedup by making room on the stack for them then not touching the stack pointer while you loop through possibly thousands of characters in the string you're searching.
Code:
FUNC CountPunctuation ;void CountPunctuation(char *str_ptr) {
BEGIN_ARGS
WORD str_ptr
BEGIN_VARS
BYTE commas ; char commas;
BYTE periods ; char periods;
BYTE semicolons ; char semicolons;
BYTE exclamations ; char exclamations;
BYTE questions ; char questions;
END_VARS
STZ commas,X ; commas=0;
STZ periods,X ; periods=0;
STZ semicolons,X ; semicolons=0;
STZ exclamations,X ; exclamations=0;
STZ questions,X ; questions=0;
while_loop: ; while(*str_ptr) {
LDA (str_ptr,X) ; A=*str_ptr;
BEQ .done
INC str_ptr,X ; str_ptr++;
BNE .no_carry
INC str_ptr+1,X
.no_carry:
CMP #',' ; if (A==',')
BNE .not_comma
INC commas,X ; commas++;
BRA while_loop
.not_comma:
CMP #'.' ; else if (A==',')
BNE .not_period
INC periods,X ; periods++;
BRA while_loop
.not_period:
CMP #';' ; else if (A==';')
BNE .not_semicolon
INC semicolons,X ; semicolons++;
BRA while_loop
.not_semicolon:
CMP #'!' ; else if (A=='!')
BNE .not_exclamation
INC exclamations,X ; exclamations++;
BRA while_loop
.not_exclamation:
CMP #'?' ; else if (A=='?')
BNE .not_question
INC questions,X ; questions++;
BRA while_loop
.not_question:
BRA while_loop ; }
.done:
CALL PrintPunctuation, commas, periods, semicolons, exclamations, questions
;PrintPunctuation(commas,periods,semicolons,exclamations,questions);
END_FUNC ;}
CALL CountPunctuation, test_str
JMP *
test_str:
FCB "Hi! Three commas, a period, a question mark, and two exlamations. Wow! Right?",0