; codegen.s - documented scalar instruction emitter (E04-S03). ; ; The emitter writes into cg_code, a 256-byte RAM buffer. cg_len is the byte ; count (0-254); every failure sets cg_err and returns C=1. It does not emit a ; whole function yet: parser/codegen integration is E04-S04. ; ; API: ; cg_init ; cg_put A = byte ; cg_lda_imm A = value ; cg_ldx_imm A = value ; cg_ora_imm A = value ; cg_ldy_imm A = value ; cg_lda_zp A = zero-page address ; cg_ldx_zp A = zero-page address ; cg_sta_zp A = zero-page address ; cg_stx_zp A = zero-page address ; cg_lda_zpy A = zero-page pointer address, indexed by Y ; cg_sta_zpy A = zero-page pointer address, indexed by Y ; cg_adc_imm A = value (ADC #value) ; cg_adc_zp A = zero-page address (CLC/ADC (zp)) ; cg_sbc_zp A = zero-page address (SEC/SBC (zp)) ; (there is no LDX (zp),Y: use LDA/TAX for a high byte) ; cg_lda_abs A/X = address ; cg_ldx_abs A/X = address ; cg_sta_abs A/X = address ; cg_stx_abs A/X = address ; cg_adc_abs A/X = address ; cg_sbc_abs A/X = address ; cg_cmp_abs A/X = address ; cg_cmp_zp A = zero-page address (CMP zp) ; cg_jsr_abs A/X = address ; cg_clc, cg_sec, cg_rts ; cg_branch_fwd A = branch opcode; emits opcode + placeholder, returns ; C=0 and A = fixup index ; cg_patch_here A = fixup index; target is the current end of buffer ; cg_here returns A = current length ; cg_set_count A != 0: count bytes only (no stores); 0: emit bytes ; ; Only forward relative branches within +0..+127 bytes are accepted. This is ; enough for the scalar shapes and gives E04-S04 the checked primitive it ; needs for a later long-branch expansion. ; ; Count mode (E04-S04) lets a sizing pass measure a routine without writing ; the code buffer. cg_len advances exactly as in emit mode, so label offsets ; and lengths match by construction. .setcpu "6502" .include "../common/codegen.inc" .export cg_init, cg_put, cg_here, cg_branch_fwd, cg_patch_here .export cg_set_count, cg_count .export cg_lda_imm, cg_ldx_imm, cg_ora_imm, cg_ldy_imm, cg_lda_abs, cg_ldx_abs .export cg_lda_zp, cg_ldx_zp, cg_sta_zp, cg_stx_zp, cg_lda_zpy, cg_sta_zpy .export cg_adc_zp, cg_sbc_zp, cg_adc_imm .export cg_sta_abs, cg_stx_abs, cg_adc_abs, cg_sbc_abs, cg_cmp_abs, cg_cmp_zp .export cg_jsr_abs, cg_clc, cg_sec, cg_rts .export cg_code, cg_len, cg_err .segment "CODE" cg_init: lda #$00 sta cg_len sta cg_err sta cg_fix_count sta cg_count clc rts cg_here: lda cg_len rts ; A != 0: count bytes only. A = 0: emit them (the default). cg_set_count: sta cg_count rts ; A = byte to append. C=1 on overflow. cg_put: ldy cg_len cpy #CG_MAX bcs @full pha lda cg_count bne @count pla sta cg_code,y jmp @bump @count: pla @bump: inc cg_len clc rts @full: lda #CG_ERR_FULL sta cg_err sec rts ; Immediate forms. cg_lda_imm: pha lda #$A9 jsr cg_put bcs @out pla jmp cg_put @out: pla sec rts cg_ldx_imm: pha lda #$A2 jsr cg_put bcs @out pla jmp cg_put @out: pla sec rts cg_ora_imm: pha lda #$09 jsr cg_put bcs @out pla jmp cg_put @out: pla sec rts cg_ldy_imm: pha lda #$A0 jsr cg_put bcs @out pla jmp cg_put @out: pla sec rts ; Zero-page forms (E05-S04). Each wrapper supplies the opcode in Y; the ; shared routine stores the address before Y is reused by cg_put. cg_lda_zp: ldy #$A5 jmp cg_emit_zp cg_ldx_zp: ldy #$A6 jmp cg_emit_zp cg_sta_zp: ldy #$85 jmp cg_emit_zp cg_stx_zp: ldy #$86 jmp cg_emit_zp cg_adc_zp: ldy #$65 jmp cg_emit_zp cg_sbc_zp: ldy #$E5 jmp cg_emit_zp cg_cmp_zp: ldy #$C5 jmp cg_emit_zp cg_lda_zpy: ldy #$B1 jmp cg_emit_zp cg_sta_zpy: ldy #$91 jmp cg_emit_zp cg_emit_zp: sta cg_addr tya jsr cg_put bcs @out lda cg_addr jmp cg_put @out: sec rts ; Absolute-address forms. Each wrapper supplies the opcode in Y; the shared ; routine stores the address before Y is reused by cg_put. cg_lda_abs: ldy #$AD jmp cg_emit_addr cg_ldx_abs: ldy #$AE jmp cg_emit_addr cg_sta_abs: ldy #$8D jmp cg_emit_addr cg_stx_abs: ldy #$8E jmp cg_emit_addr cg_adc_imm: pha lda #$69 jsr cg_put bcs @out pla jmp cg_put @out: pla sec rts cg_adc_abs: ldy #$6D jmp cg_emit_addr cg_sbc_abs: ldy #$ED jmp cg_emit_addr cg_cmp_abs: ldy #$CD jmp cg_emit_addr cg_jsr_abs: ldy #$20 jmp cg_emit_addr cg_emit_addr: sta cg_addr stx cg_addr+1 tya jsr cg_put bcs @out lda cg_addr jsr cg_put bcs @out lda cg_addr+1 jmp cg_put @out: sec rts cg_clc: lda #$18 jmp cg_put cg_sec: lda #$38 jmp cg_put cg_rts: lda #$60 jmp cg_put ; A = branch opcode; forward target must be patched later. cg_branch_fwd: sta cg_op lda cg_fix_count cmp #CG_FIX_MAX bcs @fix_full sta cg_idx lda cg_op jsr cg_put bcs @out lda cg_len sta cg_pos ldy cg_idx sta cg_fixpos,y inc cg_fix_count lda #$00 jsr cg_put bcs @out lda cg_idx clc rts @fix_full: lda #CG_ERR_FIX sta cg_err sec rts @out: sec rts ; A = fixup index. Patch its placeholder so the branch targets cg_len. cg_patch_here: cmp cg_fix_count bcs @bad tax lda cg_len sec sbc cg_fixpos,x sec sbc #1 cmp #128 bcs @range ldy cg_fixpos,x sta cg_code,y clc rts @bad: lda #CG_ERR_FIX sta cg_err sec rts @range: lda #CG_ERR_RANGE sta cg_err sec rts .segment "BSS" cg_code: .res 256 cg_len: .res 1 cg_err: .res 1 cg_count: .res 1 cg_fix_count: .res 1 cg_fixpos: .res CG_FIX_MAX cg_addr: .res 2 cg_op: .res 1 cg_idx: .res 1 cg_pos: .res 1