|
- ; E04-S04: control-flow emitter fixture in x128.
- ;
- ; The fixture generates the same routine twice with flow.s: first in counting
- ; mode (a sizing pass that writes no bytes), then in emitting mode. Both
- ; passes must produce identical label offsets and total lengths. The emitted
- ; routine is then executed and must compute:
- ;
- ; a = 0; while a < 5: a = a + 1 -> a = 5
- ; if a = 5 then path = 10 else path = 20 -> path = 10
- ; for i = 1 to 3: sum = sum + i -> sum = 16, i = 4
- ; sum = add(sum) -> sum = 17 (call + return)
- ; return sum -> A = 17
- ;
- ; A separate routine takes a long conditional branch (distance >= 128) over
- ; 125 bytes of filler and must reach its target. The fixture also checks the
- ; five-byte branch encoding: short form has the distance byte plus three NOPs,
- ; long form has +3 and a live JMP.
- ;
- ; Mailbox data:
- ; +0 program result (17)
- ; +1 a (5)
- ; +2 if/else path marker (10)
- ; +3 i after the for loop (4)
- ; +4 long-branch result (5A)
- ; +5 long-branch wrong-path marker (0)
- ; +6 label/length mismatches between the sizing and emission passes (0)
- ; +7 branch-form bits: 1 short form verified, 2 long form verified (3)
- ; Failure bits: 1 sizing, 2 program result, 4 if/else, 8 for loop,
- ; 16 call/return, 32 long branch, 64 branch bytes, 128 emitter error.
-
- .setcpu "6502"
- .include "../../src/common/c128.inc"
- .include "../../src/common/mailbox.inc"
- .include "../../src/common/codegen.inc"
- .include "../../src/common/flow.inc"
- .include "../../src/common/frames.inc"
-
- .import cg_init, cg_len, cg_err, cg_code, cg_here
- .import cg_lda_imm, cg_ldx_imm, cg_lda_abs, cg_sta_abs
- .import cg_adc_abs, cg_cmp_abs, cg_jsr_abs
- .import cg_clc, cg_sec, cg_rts
- .import fl_init, fl_label, fl_jmp, fl_branch, fl_finish, fl_get_label
- .import fl_branch_true, fl_call_byte, fl_prologue, fl_ret_byte
- .import fl_err, fl_enter, fl_leave, fl_discard
- .import frame_init, frame_enter, frame_leave, frame_discard, frame_push_word
-
- ; Label ids (reset by every fl_init).
- L_WHILE = 0
- L_ENDWHILE = 1
- L_ELSE = 2
- L_ENDIF = 3
- L_FOR = 4
- L_ENDFOR = 5
- L_ERR = 6
- L_LONG = 7
- LABEL_IDS = 7 ; ids 0..6 belong to the main routine
-
- .macro EMIT_LDA addr
- lda #<(addr)
- ldx #>(addr)
- jsr cg_lda_abs
- .endmacro
-
- .macro EMIT_STA addr
- lda #<(addr)
- ldx #>(addr)
- jsr cg_sta_abs
- .endmacro
-
- .macro EMIT_ADC addr
- lda #<(addr)
- ldx #>(addr)
- jsr cg_adc_abs
- .endmacro
-
- .macro EMIT_CMP addr
- lda #<(addr)
- ldx #>(addr)
- jsr cg_cmp_abs
- .endmacro
-
- .macro EMIT_LDA_IMM value
- lda #value
- jsr cg_lda_imm
- .endmacro
-
- .macro EMIT_LDX_IMM value
- lda #value
- jsr cg_ldx_imm
- .endmacro
-
- .macro EMIT_JSR addr
- lda #<(addr)
- ldx #>(addr)
- jsr cg_jsr_abs
- .endmacro
-
- ; Branch to a global error label even when it is out of relative range.
- .macro ON_CARRY target
- bcc :+
- jmp target
- :
- .endmacro
-
- .segment "LOADADDR"
- .word $1C01
-
- .segment "EXEHDR"
- .word basic_end
- .word 10
- .byte $9E
- .byte "7181"
- .byte $00
- basic_end:
- .word $0000
-
- .segment "MAILBOX"
- .res MB_SIZE
-
- .segment "STARTUP"
- .export test_entry
- test_entry:
- php
- sei
- ldx #MB_SIZE - 1
- lda #$00
- @clr: sta MB_BASE,x
- dex
- bpl @clr
- lda #MB_MAGIC0_VALUE
- sta MB_MAGIC0
- lda #MB_MAGIC1_VALUE
- sta MB_MAGIC1
- lda #MB_PROD_PROGRAM
- sta MB_PRODUCER
- lda #MB_ST_RUNNING
- sta MB_STATUS
- lda #$00
- sta fail_mask
- sta mismatch
- sta form_mask
- sta MB_CODE
- sta MB_CODE+1
- jsr init_vars
-
- ; Runtime addresses for the emitted prologue/call/epilogue.
- lda #<frame_enter
- sta fl_enter
- lda #>frame_enter
- sta fl_enter+1
- lda #<frame_leave
- sta fl_leave
- lda #>frame_leave
- sta fl_leave+1
- lda #<frame_discard
- sta fl_discard
- lda #>frame_discard
- sta fl_discard+1
-
- lda MMU_CR
- sta saved_cr
- lda #CR_RAM0_KERNAL
- sta MMU_CR
-
- ; ------------------------------------------- main routine: sizing pass
- lda #FL_MODE_COUNT
- jsr fl_init
- jsr gen_main
- ON_CARRY emitter_error
- jsr fl_finish
- ON_CARRY emitter_error
- lda cg_len
- sta main_size
- jsr save_labels
-
- ; ------------------------------------------ main routine: emit pass 2
- jsr frame_init
- lda #FL_MODE_EMIT
- jsr fl_init
- jsr gen_main
- ON_CARRY emitter_error
- jsr fl_finish
- ON_CARRY emitter_error
- lda cg_len
- cmp main_size
- beq @len_ok
- inc mismatch
- lda #$01
- jsr fail
- @len_ok:
- jsr compare_labels
-
- ; short branch: distance byte plus three NOPs
- ldy pos_while
- lda cg_code,y
- cmp #128
- bcs @short_bad
- iny
- lda cg_code,y
- cmp #$EA
- bne @short_bad
- iny
- lda cg_code,y
- cmp #$EA
- bne @short_bad
- iny
- lda cg_code,y
- cmp #$EA
- bne @short_bad
- lda #$01
- sta form_mask
- jmp @run_main
- @short_bad:
- lda #$40
- jsr fail
-
- ; ------------------------------------------------ execute the routine
- @run_main:
- jsr frame_init
- lda #$00
- jsr run_code
- sta prog_result
-
- lda prog_result
- cmp #17
- beq @check_a
- lda #$02
- jsr fail
- @check_a:
- lda v_a
- cmp #5
- beq @check_if
- lda #$04
- jsr fail
- @check_if:
- lda v_ifpath
- cmp #10
- beq @check_for
- lda #$04
- jsr fail
- @check_for:
- lda v_i
- cmp #4
- beq @check_call
- lda #$08
- jsr fail
- @check_call:
- lda v_sum
- cmp #17
- beq @long_begin
- lda #$10
- jsr fail
-
- ; ------------------------------- long branch routine: sizing + emit
- @long_begin:
- lda #FL_MODE_COUNT
- jsr fl_init
- jsr gen_long
- ON_CARRY emitter_error
- jsr fl_finish
- ON_CARRY emitter_error
- lda cg_len
- sta long_size
-
- lda #FL_MODE_EMIT
- jsr fl_init
- jsr gen_long
- ON_CARRY emitter_error
- jsr fl_finish
- ON_CARRY emitter_error
- lda cg_len
- cmp long_size
- beq @long_len_ok
- inc mismatch
- lda #$01
- jsr fail
- @long_len_ok:
- ; long form: +3 then JMP, and the target is at least 128 bytes away
- ldy pos_long
- lda cg_code,y
- cmp #$03
- bne @long_bad
- iny
- lda cg_code,y
- cmp #$4C
- bne @long_bad
- ldy pos_long
- dey
- lda cg_code,y
- cmp #$F0 ; BNE was inverted to BEQ
- bne @long_bad
- lda #L_LONG
- jsr fl_get_label
- bcs @long_bad
- txa
- sec
- sbc pos_long
- sec
- sbc #$01
- cmp #128
- bcc @long_bad
- lda form_mask
- ora #$02
- sta form_mask
- jmp @run_long
- @long_bad:
- lda #$40
- jsr fail
-
- @run_long:
- lda #$00
- jsr run_code
- lda v_long
- cmp #$5A
- beq @check_bad
- lda #$20
- jsr fail
- @check_bad:
- lda v_bad
- beq report
- lda #$20
- jsr fail
-
- jmp report
-
- ; ---------------------------------------------------------- report
- report:
- lda saved_cr
- sta MMU_CR
- lda prog_result
- sta MB_DATA+0
- lda v_a
- sta MB_DATA+1
- lda v_ifpath
- sta MB_DATA+2
- lda v_i
- sta MB_DATA+3
- lda v_long
- sta MB_DATA+4
- lda v_bad
- sta MB_DATA+5
- lda mismatch
- sta MB_DATA+6
- lda form_mask
- sta MB_DATA+7
- lda fail_mask
- sta MB_CODE
- lda #$00
- sta MB_CODE+1
- lda #MB_ST_FAIL
- ldx fail_mask
- bne @status
- lda #MB_ST_PASS
- @status:
- sta MB_STATUS
- ldy #$00
- @print: lda message,y
- beq @end
- sty print_y
- jsr CHROUT
- ldy print_y
- iny
- bne @print
- @end: plp
- rts
-
- emitter_error:
- lda #$80
- jsr fail
- jmp report
-
- ; ------------------------------------------------------------- generators
- ; if/else/while/for, one call, and a value return.
- gen_main:
- lda #$00 ; prologue: no locals
- ldx #$00
- ldy #L_ERR
- jsr fl_prologue
- ON_CARRY gen_out
-
- EMIT_LDA_IMM $00 ; a = 0
- EMIT_STA v_a
- EMIT_LDA_IMM $00 ; sum = 0
- EMIT_STA v_sum
-
- lda #L_WHILE ; while a < 5
- jsr fl_label
- EMIT_LDA v_a
- EMIT_CMP v_five
- jsr cg_here ; the exit branch's distance byte
- clc
- adc #$01
- sta pos_while
- lda #$B0 ; BCS: a >= 5 leaves the loop
- ldx #L_ENDWHILE
- jsr fl_branch
- ON_CARRY gen_out
- EMIT_LDA v_a ; a = a + 1
- jsr cg_clc
- EMIT_ADC v_one
- EMIT_STA v_a
- lda #L_WHILE ; back edge: absolute jump
- jsr fl_jmp
- ON_CARRY gen_out
- lda #L_ENDWHILE
- jsr fl_label
-
- EMIT_LDA v_a ; if a = 5
- EMIT_CMP v_five
- lda #$D0 ; BNE: else
- ldx #L_ELSE
- jsr fl_branch
- ON_CARRY gen_out
- EMIT_LDA_IMM 10
- EMIT_STA v_ifpath
- EMIT_STA v_sum
- lda #L_ENDIF
- jsr fl_jmp
- ON_CARRY gen_out
- lda #L_ELSE
- jsr fl_label
- EMIT_LDA_IMM 20
- EMIT_STA v_ifpath
- EMIT_STA v_sum
- lda #L_ENDIF
- jsr fl_label
-
- EMIT_LDA_IMM 1 ; for i = 1 to 3
- EMIT_STA v_i
- lda #L_FOR
- jsr fl_label
- EMIT_LDA v_i
- EMIT_CMP v_four
- lda #$B0 ; BCS: i >= 4 leaves the loop
- ldx #L_ENDFOR
- jsr fl_branch
- ON_CARRY gen_out
- EMIT_LDA v_sum ; sum = sum + i
- jsr cg_clc
- EMIT_ADC v_i
- EMIT_STA v_sum
- EMIT_LDA v_i ; i = i + 1
- jsr cg_clc
- EMIT_ADC v_one
- EMIT_STA v_i
- lda #L_FOR
- jsr fl_jmp
- ON_CARRY gen_out
- lda #L_ENDFOR
- jsr fl_label
-
- EMIT_LDA v_sum ; push(sum) for add()
- EMIT_LDX_IMM 0
- EMIT_JSR frame_push_word
- ON_CARRY gen_out
- lda #$B0 ; push refused a frame
- ldx #L_ERR
- jsr fl_branch
- ON_CARRY gen_out
- lda #<add ; sum = add(sum)
- ldx #>add
- ldy #2
- jsr fl_call_byte
- ON_CARRY gen_out
- EMIT_STA v_sum
-
- EMIT_LDA v_sum ; return sum
- jsr fl_ret_byte
- ON_CARRY gen_out
-
- lda #L_ERR ; frame or argument-push failure
- jsr fl_label
- lda #$00
- jsr cg_sec
- jmp cg_rts
-
- ; A long taken branch over 125 bytes of filler.
- gen_long:
- EMIT_LDA_IMM $00
- EMIT_STA v_bad
- EMIT_LDA_IMM $01
- jsr cg_here ; remember the branch's distance byte
- clc
- adc #$01
- sta pos_long
- lda #L_LONG
- jsr fl_branch_true
- ON_CARRY gen_out
- EMIT_LDA_IMM $AA ; wrong path if the branch fails
- EMIT_STA v_bad
- lda #25 ; X is clobbered by the emit macros
- sta fill_count
- @fill: EMIT_LDA_IMM $11
- EMIT_STA v_filler
- dec fill_count
- bne @fill
- lda #L_LONG
- jsr fl_label
- EMIT_LDA_IMM $5A
- EMIT_STA v_long
- jmp cg_rts
-
- ; Shared early exit for both generators: C=1 from the failing call.
- gen_out:
- rts
-
- ; ---------------------------------------------------------------- helpers
- ; A = code-buffer offset. Run the snippet and return its A.
- run_code:
- clc
- adc #<cg_code
- sta @target+1
- lda #>cg_code
- adc #$00
- sta @target+2
- @target:
- jsr $FFFF
- rts
-
- save_labels:
- lda #$00
- sta label_id
- @loop: lda label_id
- cmp #LABEL_IDS
- beq @done
- jsr fl_get_label
- bcs @missing
- ldy label_id
- txa
- sta saved_labels,y
- inc label_id
- jmp @loop
- @missing:
- inc mismatch
- lda #$01
- jsr fail
- inc label_id
- jmp @loop
- @done: rts
-
- compare_labels:
- lda #$00
- sta label_id
- @loop: lda label_id
- cmp #LABEL_IDS
- beq @done
- jsr fl_get_label
- bcs @bad
- ldy label_id
- txa
- cmp saved_labels,y
- beq @next
- @bad: inc mismatch
- lda #$01
- jsr fail
- @next: inc label_id
- jmp @loop
- @done: rts
-
- init_vars:
- lda #$01
- sta v_one
- lda #$03
- sta v_three
- lda #$04
- sta v_four
- lda #$05
- sta v_five
- lda #$00
- sta v_a
- sta v_sum
- sta v_i
- sta v_ifpath
- sta v_bad
- sta v_long
- sta v_filler
- sta prog_result
- rts
-
- ; add(value as word) as byte: returns value + 1.
- add:
- lda #$00
- ldx #$00
- jsr frame_enter
- bcs @fault
- ldy #2
- lda (FRAME_FP),y
- clc
- adc #$01
- sta add_result
- jsr frame_leave
- lda add_result
- clc
- rts
- @fault: lda #$00
- sec
- rts
-
- .segment "CODE"
- fail:
- ora fail_mask
- sta fail_mask
- rts
-
- .segment "RODATA"
- message: .byte "NOVA128 CONTROL TEST DONE", $0D, $00
-
- .segment "BSS"
- fail_mask: .res 1
- print_y: .res 1
- saved_cr: .res 1
- label_id: .res 1
- mismatch: .res 1
- form_mask: .res 1
- main_size: .res 1
- long_size: .res 1
- pos_while: .res 1
- pos_long: .res 1
- prog_result: .res 1
- v_one: .res 1
- v_three: .res 1
- v_four: .res 1
- v_five: .res 1
- v_a: .res 1
- v_sum: .res 1
- v_i: .res 1
- v_ifpath: .res 1
- v_bad: .res 1
- v_long: .res 1
- v_filler: .res 1
- add_result: .res 1
- saved_labels: .res LABEL_IDS
- fill_count: .res 1
|