|
- ; frames.s - generated-program software frames (E04-S02).
- ;
- ; All routines run with CR=$0E and IRQs masked. FRAME_SP/FRAME_FP live at
- ; $FB-$FE; the frame stack occupies $B800-$BFFF and grows downward from
- ; $C000. See src/common/frames.inc for the layout and calling convention.
- ;
- ; frame_enter is transactional on failure: if the stack bound would be
- ; crossed, SP and FP are restored to their entry values and C=1 with
- ; frame_error=FRAME_ERR_STACK. No partial frame remains.
-
- .setcpu "6502"
- .include "../common/frames.inc"
-
- .export frame_init, frame_enter, frame_leave, frame_discard
- .export frame_push_byte, frame_push_word, frame_error
-
- .segment "CODE"
-
- ; Empty software stack: SP=$C000, FP=0, no error.
- frame_init:
- lda #<FRAME_STACK_TOP
- sta FRAME_SP
- lda #>FRAME_STACK_TOP
- sta FRAME_SP+1
- lda #$00
- sta FRAME_FP
- sta FRAME_FP+1
- sta frame_error
- clc
- rts
-
- ; A/X = byte count (low/high). C=0 and SP updated on success; C=1 with
- ; FRAME_ERR_STACK and SP unchanged when the new SP would fall below the base.
- frame_reserve:
- sta frame_size
- stx frame_size+1
- lda FRAME_SP
- sec
- sbc frame_size
- sta frame_new_sp
- lda FRAME_SP+1
- sbc frame_size+1
- sta frame_new_sp+1
- lda frame_new_sp+1
- cmp #>FRAME_STACK_BASE
- bcc @fail
- bne @ok
- lda frame_new_sp
- cmp #<FRAME_STACK_BASE
- bcc @fail
- @ok: lda frame_new_sp
- sta FRAME_SP
- lda frame_new_sp+1
- sta FRAME_SP+1
- clc
- rts
- @fail: lda #FRAME_ERR_STACK
- sta frame_error
- sec
- rts
-
- ; Push one argument byte (A). C=1 on stack overflow.
- frame_push_byte:
- sta frame_byte
- lda #1
- ldx #0
- jsr frame_reserve
- bcs @out
- ldy #0
- lda frame_byte
- sta (FRAME_SP),y
- clc
- @out: rts
-
- ; Push one argument word: A=low, X=high. C=1 on stack overflow.
- frame_push_word:
- sta frame_word
- stx frame_word+1
- lda #2
- ldx #0
- jsr frame_reserve
- bcs @out
- ldy #1
- lda frame_word+1
- sta (FRAME_SP),y
- dey
- lda frame_word
- sta (FRAME_SP),y
- clc
- @out: rts
-
- ; Enter a callee frame. Arguments are already on the stack above FP+2.
- ; A/X = local byte count (low/high)
- ; C=0: old FP pushed, FP set, locals reserved
- ; C=1: no frame was created; SP/FP are unchanged
- frame_enter:
- sta frame_locals
- stx frame_locals+1
- tsx
- cpx #FRAME_HW_MIN
- bcs @hw_ok
- lda #FRAME_ERR_STACK
- sta frame_error
- sec
- rts
- @hw_ok: lda FRAME_SP
- sta frame_saved_sp
- lda FRAME_SP+1
- sta frame_saved_sp+1
- lda FRAME_FP
- sta frame_saved_fp
- lda FRAME_FP+1
- sta frame_saved_fp+1
-
- lda FRAME_FP
- ldx FRAME_FP+1
- jsr frame_push_word
- bcs @out ; old-FP push overflow: nothing changed
- lda FRAME_SP
- sta FRAME_FP
- lda FRAME_SP+1
- sta FRAME_FP+1
-
- lda frame_locals
- ldx frame_locals+1
- jsr frame_reserve
- bcc @ok
- lda frame_saved_sp ; locals overflow: undo the old FP push
- sta FRAME_SP
- lda frame_saved_sp+1
- sta FRAME_SP+1
- lda frame_saved_fp
- sta FRAME_FP
- lda frame_saved_fp+1
- sta FRAME_FP+1
- sec
- @out: rts
- @ok: clc
- rts
-
- ; Leave the current frame. SP=FP, pop the previous FP, and leave SP pointing
- ; at arg0. The caller still owns the argument bytes.
- frame_leave:
- lda FRAME_FP
- sta FRAME_SP
- lda FRAME_FP+1
- sta FRAME_SP+1
- ldy #0
- lda (FRAME_SP),y
- sta FRAME_FP
- iny
- lda (FRAME_SP),y
- sta FRAME_FP+1
- lda FRAME_SP
- clc
- adc #2
- sta FRAME_SP
- bcc @done
- inc FRAME_SP+1
- @done: rts
-
- ; Pop argument bytes after a return. A/X = byte count (low/high). A and X
- ; are preserved so the caller can still store the callee's result.
- frame_discard:
- sta frame_discard_n
- stx frame_discard_n+1
- clc
- lda FRAME_SP
- adc frame_discard_n
- sta FRAME_SP
- lda FRAME_SP+1
- adc frame_discard_n+1
- sta FRAME_SP+1
- lda frame_discard_n
- ldx frame_discard_n+1
- rts
-
- .segment "BSS"
- frame_size: .res 2
- frame_new_sp: .res 2
- frame_byte: .res 1
- frame_word: .res 2
- frame_locals: .res 2
- frame_saved_sp: .res 2
- frame_saved_fp: .res 2
- frame_discard_n: .res 2
- frame_error: .res 1
|