188 lines
3.6 KiB
Text
188 lines
3.6 KiB
Text
; This file is intended to demonstrate how stack frames and function
|
|
; calls could be implemented.
|
|
;
|
|
; The stack starts at 0xfffff and grows towards 0x00000. The stack
|
|
; pointer always points to the next free address below the stack. This
|
|
; means that if the stack pointer is at 0xfffff, the stack is empty.
|
|
;
|
|
; The frame pointer points to the beginning of the stack frame. If the
|
|
; stack pointer points at the same position as the frame pointer, the
|
|
; current stack frame is empty.
|
|
;
|
|
; Functions take a fixed number of arguments. For this example, all
|
|
; values have a size of one word (24 bit).
|
|
|
|
IAR = main
|
|
SP = 0xfffff
|
|
FP = 0xfffff
|
|
|
|
; Temporary variables at a fixed memory location
|
|
;
|
|
; These are useful for commands like ADD, which need a fixed memory
|
|
; address. They are always under the complete control of the currently
|
|
; running function.
|
|
tmp1: LIT 0
|
|
tmp2: LIT 0
|
|
tmp3: LIT 0
|
|
tmp4: LIT 0
|
|
tmp5: LIT 0
|
|
|
|
100:
|
|
main:
|
|
CALL caller
|
|
HALT
|
|
|
|
200:
|
|
caller:
|
|
;; 1. Initialisation
|
|
|
|
; 1.1. Create own stack frame
|
|
LDFP
|
|
STRS 0
|
|
LDSP
|
|
ADC -1
|
|
STSP
|
|
STFP
|
|
|
|
; 2. Push RA onto current stack frame
|
|
LDRA
|
|
STRS 0
|
|
LDSP
|
|
ADC -1
|
|
STSP
|
|
|
|
; Do some work here
|
|
|
|
;; 2. Prepare call
|
|
|
|
; 2.1. Create a new shared stack frame
|
|
LDFP
|
|
STRS 0
|
|
LDSP
|
|
ADC -1
|
|
STSP
|
|
STFP
|
|
|
|
; 2.2. Write function parameters onto shared stack frame and keep
|
|
; space for return values
|
|
LDC 5
|
|
STRF 0
|
|
LDC 7
|
|
STRF -1
|
|
LDFP
|
|
ADC -4 ; 2 parameters and 2 return values
|
|
STSP
|
|
|
|
; Now, the shared stack frame looks like this:
|
|
;
|
|
; FP offset | Value
|
|
; 0 | argument 1
|
|
; -1 | argument 2
|
|
; -2 | return value 1
|
|
; -3 | return value 2
|
|
|
|
;; 3. Call callee
|
|
CALL callee
|
|
|
|
;; 4. Cleanup after call
|
|
|
|
; 4.1. Copy resulting values from the shared stack frame, so we can
|
|
; restore our own stack frame
|
|
LDRF -2
|
|
STV tmp1
|
|
LDRF -3
|
|
STV tmp2
|
|
|
|
; 4.2. Restore own stack frame
|
|
LDFP
|
|
ADC 1
|
|
STSP
|
|
LDRF 1
|
|
STFP
|
|
|
|
; Now, we can use the results stored in tmp1 and tmp2.
|
|
; Do some more work here
|
|
|
|
;; 5. Cleanup before RET
|
|
|
|
; 5.1. Pop and restore RA
|
|
LDRS 1
|
|
STRA
|
|
LDSP
|
|
ADC 1
|
|
STSP
|
|
|
|
RET
|
|
|
|
300:
|
|
callee:
|
|
; This callee doesn't really need its own stack since all
|
|
; calculations did fit into the temporary variables. I still created
|
|
; a stack frame for this function to demonstrate how it would work
|
|
; if a stack was required.
|
|
|
|
; 1. Create own stack frame
|
|
LDFP
|
|
STRS 0
|
|
LDSP
|
|
ADC -1
|
|
STSP
|
|
STFP
|
|
|
|
; Since we've pushed the old FP to the shared stack frame (which now
|
|
; has length 5) and moved the FP to after that, we've in effect
|
|
; added 5 to the FP offset. This means that the shared stack frame
|
|
; now looks like this:
|
|
;
|
|
; FP offset | Value
|
|
; 5 | argument 1
|
|
; 4 | argument 2
|
|
; 3 | return value 1
|
|
; 2 | return value 2
|
|
|
|
; 2. Load arguments into temporary variables
|
|
LDRF 5
|
|
STV tmp1
|
|
LDRF 4
|
|
STV tmp2
|
|
|
|
; 3. Add arguments and put result into return value 1
|
|
LDV tmp1
|
|
ADD tmp2
|
|
STRF 3
|
|
|
|
; 4. Multiply arguments and put result into return value 2
|
|
LDC 0
|
|
STV tmp3 ; For comparison in loop
|
|
STV tmp4 ; For accumulating result
|
|
|
|
callee-loop:
|
|
; Break if counter (tmp1) is zero
|
|
LDV tmp1
|
|
EQL tmp3
|
|
JMN callee-loop-end
|
|
; Decrement counter (tmp1) by 1
|
|
LDV tmp1
|
|
ADC -1
|
|
STV tmp1
|
|
; Increment result (tmp4) by the second argument (tmp2)
|
|
LDV tmp4
|
|
ADD tmp2
|
|
STV tmp4
|
|
; And repeat the loop
|
|
JMP callee-loop
|
|
|
|
callee-loop-end:
|
|
; Save the result in return value 2
|
|
LDV tmp4
|
|
STRF 2
|
|
|
|
; 5. Restore shared stack frame
|
|
LDFP
|
|
ADC 1
|
|
STSP
|
|
LDRF 1
|
|
STFP
|
|
|
|
; And this function is done :)
|
|
RET
|