; 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+1 lda #frame_leave sta fl_leave+1 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 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 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