; E05-S04: fields, self, and concrete calls in x128. ; ; members.s emits method-body fragments; this fixture generates several and ; executes them against two objects of the same class layout (descriptor word ; at +0, byte field at +2, word field at +3..4): ; ; 1. a field writer storing the immediates in gen_count/gen_lo/gen_hi, ; run on both objects, then read back through emitted byte and word ; loaders, which proves offsets and independent state; ; 2. a caller that loads a receiver reference with mb_point, pushes arguments ; on the frame stack, and calls a real method with self in A/X, which ; proves the direct-JSR call ABI and argument discard; ; 3. the same caller shape with a word result, stored by the call site. ; ; Mailbox data: ; +0 field store/load offsets verified (1) ; +1 the two objects kept independent state (1) ; +2 concrete call ABI and argument discard verified (1) ; +3 word result preserved across the call path (1) ; +4 word result low byte ($78) ; +5 word result high byte ($56) ; Failure bits: 1 emitter, 2 store/offset, 4 independence, 8 call ABI, ; 16 result. .setcpu "6502" .include "../../src/common/c128.inc" .include "../../src/common/mailbox.inc" .include "../../src/common/print.inc" .include "../../src/common/codegen.inc" .include "../../src/common/flow.inc" .include "../../src/common/frames.inc" .include "../../src/common/members.inc" .import cg_init, cg_err, cg_code, cg_rts .import cg_lda_imm, cg_ldx_imm, cg_jsr_abs, cg_sta_abs, cg_stx_abs .import fl_err, fl_enter, fl_leave, fl_discard .import mb_init, mb_point, mb_self_load, mb_self_store .import mb_ld_field, mb_st_field, mb_call, mb_call_word .import mb_err .import frame_init, frame_enter, frame_leave, frame_discard, frame_push_byte, frame_push_word .import pr_char, pr_text, pr_cr COUNT_OFF = 2 ; byte field TOTAL_OFF = 3 ; word field OBJ_SIZE = 5 .macro TEXT src, len lda #src ldy #len jsr pr_text .endmacro ; Branch to a global label even when it is out of relative range. .macro ON_CARRY target bcc :+ jmp target : .endmacro .macro ON_NE target beq :+ 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 "ZEROPAGE" set_ptr: .res 2 ; receiver for the hand-written methods .segment "STARTUP" .export test_entry, test_end 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 MB_CODE sta MB_CODE+1 lda MMU_CR sta saved_cr lda #CR_RAM0_KERNAL sta MMU_CR lda #PR_CLEAR jsr pr_char jsr clear_state jsr frame_init jsr mb_init ; Runtime addresses the emitted prologue and call helpers need. lda #frame_enter sta fl_enter+1 lda #frame_leave sta fl_leave+1 lda #frame_discard sta fl_discard+1 ; ---------------------------------------- 1. field store on both objects lda #3 sta gen_count lda #$34 sta gen_lo lda #$12 sta gen_hi jsr cg_init jsr gen_store ON_CARRY emitter_error jsr run_a lda obj_a+COUNT_OFF cmp #3 ON_NE store_fail lda obj_a+TOTAL_OFF cmp #$34 ON_NE store_fail lda obj_a+TOTAL_OFF+1 cmp #$12 ON_NE store_fail lda #7 sta gen_count lda #$78 sta gen_lo lda #$56 sta gen_hi jsr cg_init jsr gen_store ON_CARRY emitter_error jsr run_b lda obj_b+COUNT_OFF cmp #7 ON_NE store_fail lda obj_b+TOTAL_OFF cmp #$78 ON_NE store_fail lda obj_b+TOTAL_OFF+1 cmp #$56 ON_NE store_fail ; A must be untouched by writing B. lda obj_a+COUNT_OFF cmp #3 ON_NE indep_fail lda obj_a+TOTAL_OFF cmp #$34 ON_NE indep_fail lda obj_a+TOTAL_OFF+1 cmp #$12 ON_NE indep_fail ; ------------------------------------- field loads through the emitter jsr cg_init jsr gen_read_count ON_CARRY emitter_error jsr run_a sta v_count_a jsr run_b sta v_count_b lda v_count_a cmp #3 ON_NE store_fail lda v_count_b cmp #7 ON_NE indep_fail jsr cg_init jsr gen_read_total ON_CARRY emitter_error jsr run_a sta v_lo_a stx v_hi_a jsr run_b sta v_lo_b stx v_hi_b lda v_lo_a cmp #$34 ON_NE store_fail lda v_hi_a cmp #$12 ON_NE store_fail lda v_lo_b cmp #$78 ON_NE indep_fail lda v_hi_b cmp #$56 ON_NE indep_fail lda #$01 sta MB_DATA+0 sta MB_DATA+1 ; ------------------------------- 2. concrete call with arguments lda FRAME_SP sta sp_before lda FRAME_SP+1 sta sp_before+1 lda #ref_a sta call_ref+1 lda #9 sta call_count lda #$CD sta call_lo lda #$AB sta call_hi jsr cg_init jsr gen_call_set ON_CARRY emitter_error lda #$00 jsr run_plain lda obj_a+COUNT_OFF cmp #9 ON_NE call_fail lda obj_a+TOTAL_OFF cmp #$CD ON_NE call_fail lda obj_a+TOTAL_OFF+1 cmp #$AB ON_NE call_fail lda FRAME_SP cmp sp_before ON_NE call_fail lda FRAME_SP+1 cmp sp_before+1 ON_NE call_fail lda obj_b+COUNT_OFF ; B was not the receiver cmp #7 ON_NE call_fail lda #$01 sta MB_DATA+2 ; ------------------------------------- 3. word result at the call site lda #ref_b sta call_ref+1 jsr cg_init jsr gen_call_total ON_CARRY emitter_error lda #$00 jsr run_plain lda res_lo sta MB_DATA+4 lda res_hi sta MB_DATA+5 lda res_lo cmp #$78 ON_NE result_fail lda res_hi cmp #$56 ON_NE result_fail lda #$01 sta MB_DATA+3 jmp report emitter_error: lda #$01 jmp fail store_fail: lda #$02 jmp fail indep_fail: lda #$04 jmp fail call_fail: lda #$08 jmp fail result_fail: lda #$10 jmp fail fail: ora fail_mask sta fail_mask report: lda saved_cr sta MMU_CR lda fail_mask sta MB_CODE lda #$00 sta MB_CODE+1 ldx fail_mask bne @fail lda #MB_ST_PASS sta MB_STATUS TEXT msg_ok, 9 jmp @end @fail: lda #MB_ST_FAIL sta MB_STATUS TEXT msg_fail, 11 @end: jsr pr_cr plp rts ; ------------------------------------------------------------- state clear_state: lda #$00 sta fail_mask sta res_lo sta res_hi sta v_count_a sta v_count_b ldx #OBJ_SIZE - 1 @a: sta obj_a,x dex bpl @a ldx #OBJ_SIZE - 1 @b: sta obj_b,x dex bpl @b sta obj_a+0 ; descriptor words stay zero here: these sta obj_b+0 ; fixtures exercise fields, not classes lda #obj_a sta ref_a+1 lda #obj_b sta ref_b+1 rts ; --------------------------------------------------------- generators ; store self.count = gen_count; self.total = gen_hi:gen_lo gen_store: jsr mb_self_store ON_CARRY gen_out lda gen_count jsr cg_lda_imm ON_CARRY gen_out lda #COUNT_OFF ldx #MB_BYTE jsr mb_st_field ON_CARRY gen_out lda gen_lo jsr cg_lda_imm ON_CARRY gen_out lda gen_hi jsr cg_ldx_imm ON_CARRY gen_out lda #TOTAL_OFF ldx #MB_WORD jsr mb_st_field ON_CARRY gen_out jmp cg_rts gen_out: sec rts ; return self.count gen_read_count: jsr mb_self_store ON_CARRY gen_out lda #COUNT_OFF ldx #MB_BYTE jsr mb_ld_field ON_CARRY gen_out jmp cg_rts ; return self.total gen_read_total: jsr mb_self_store ON_CARRY gen_out lda #TOTAL_OFF ldx #MB_WORD jsr mb_ld_field ON_CARRY gen_out jmp cg_rts ; receiver = *call_ref; push(word call_hi:call_lo); push(byte call_count); ; call setter(3 bytes). Demonstrates the caller ABI from generated code. gen_call_set: lda call_ref ldx call_ref+1 jsr mb_point ON_CARRY gen_out lda call_lo jsr cg_lda_imm ON_CARRY gen_out lda call_hi jsr cg_ldx_imm ON_CARRY gen_out lda #frame_push_word jsr cg_jsr_abs ON_CARRY gen_out lda call_count jsr cg_lda_imm ON_CARRY gen_out lda #frame_push_byte jsr cg_jsr_abs ON_CARRY gen_out lda #setter ldy #3 jsr mb_call ON_CARRY gen_out jmp cg_rts ; receiver = *call_ref; result = total(). The call site stores A/X, so the ; word result has to survive the call epilogue. gen_call_total: lda call_ref ldx call_ref+1 jsr mb_point ON_CARRY gen_out lda #total_of ldy #0 jsr mb_call_word ON_CARRY gen_out lda #res_lo jsr cg_sta_abs ON_CARRY gen_out lda #res_hi jsr cg_stx_abs ON_CARRY gen_out jmp cg_rts ; ------------------------------------------------------------ methods ; set(count as byte, total as word): self in A/X. setter: sta cur_self stx cur_self+1 lda #$00 ldx #$00 jsr frame_enter bcs @fault lda cur_self sta set_ptr lda cur_self+1 sta set_ptr+1 ldy #2 lda (FRAME_FP),y ldy #COUNT_OFF sta (set_ptr),y ldy #3 lda (FRAME_FP),y ldy #TOTAL_OFF sta (set_ptr),y ldy #4 lda (FRAME_FP),y ldy #TOTAL_OFF+1 sta (set_ptr),y jsr frame_leave clc rts @fault: sec rts ; total() as word: A = low, X = high. total_of: sta cur_self stx cur_self+1 lda #$00 ldx #$00 jsr frame_enter bcs @fault lda cur_self sta set_ptr lda cur_self+1 sta set_ptr+1 ldy #TOTAL_OFF lda (set_ptr),y pha ldy #TOTAL_OFF+1 lda (set_ptr),y tax pla pha txa pha jsr frame_leave pla tax pla clc rts @fault: sec rts ; ------------------------------------------------------------- runners ; A = code offset; A/X = self for the snippet. run_self: clc adc #cg_code adc #$00 sta @target+2 lda run_ptr ldx run_ptr+1 @target: jsr $FFFF rts ; A = code offset; no receiver. run_plain: clc adc #cg_code adc #$00 sta @target+2 @target: jsr $FFFF rts run_a: lda #obj_a sta run_ptr+1 lda #$00 jmp run_self run_b: lda #obj_b sta run_ptr+1 lda #$00 jmp run_self .segment "RODATA" msg_ok: .byte "MEMBER OK" msg_fail: .byte "MEMBER FAIL" .segment "BSS" saved_cr: .res 1 fail_mask: .res 1 gen_count: .res 1 gen_lo: .res 1 gen_hi: .res 1 call_ref: .res 2 call_count: .res 1 call_lo: .res 1 call_hi: .res 1 run_ptr: .res 2 sp_before: .res 2 res_lo: .res 1 res_hi: .res 1 v_count_a: .res 1 v_count_b: .res 1 v_lo_a: .res 1 v_hi_a: .res 1 v_lo_b: .res 1 v_hi_b: .res 1 obj_a: .res OBJ_SIZE obj_b: .res OBJ_SIZE ref_a: .res 2 ref_b: .res 2 cur_self: .res 2 test_end: