; Minimal libgcc-equivalent runtime for the W65816 / Apple IIgs. ; Provides the helpers that the LLVM backend lowers integer multiply, ; shift, divide, and modulo operations to. Implementations are ; correct-but-unoptimised; they exist to unblock end-to-end testing, ; not to compete with hand-tuned 65816 math libraries. The exception is ; memcpy / memmove / memset at the end of this file (MVN / MVP block ; moves, in their own section): the backend lowers struct copies and ; clears to them too, so they live here rather than in libc.c. ; ; Calling convention (matches W65816ISelLowering::LowerCall): ; - Arg 0 in A (16-bit M). ; - Arg 1 pushed via PHA before the JSL. Reads as (4,S) inside the ; callee (3-byte JSL return address sits at 1..3,S). ; - Return value in A. Caller releases pushed args. ; - Routines run in 16-bit M, 16-bit X (REP #$30 by convention). ; ; Direct-page scratch lives at DP+$E0..DP+$EF (16 bytes). Programs ; that use this runtime must keep DP=0 or remap accordingly. ; ; Assembled with: tools/llvm-mos-build/bin/llvm-mc -arch=w65816 \ ; -filetype=obj ; runtime/src/libgcc.s ; -o runtime/libgcc.o .text ; -------------------------------------------------------------------- ; Indirect-call trampoline (REENTRANT). An indirect call (function ; pointer) stores its 24-bit target -- 16-bit offset to __indirTarget ; ($B8/$B9) and bank byte to $BA -- then JSLs here. __jsl_indir reads ; that target DP-RELATIVE and RTL-jumps to it with the original caller's ; JSL frame still on the stack, so the target's own RTL returns straight ; back to the caller (one frame, no double-RTL). The instruction-level ; idiom is documented at the __jsl_indir label below. ; ; Caller emit sequence in W65816ISelLowering::LowerCall: ; sta __indirTarget ; store ptr offset (must precede any A clobber for args) ; ...store target bank to $BA, arg pushes... ; jsl __jsl_indir ; ; No self-modified operand: the target is read from the caller's OWN ; direct page ($B8/$B9 offset + $BA bank), and DP is per-thread -- GNO ; context-switches it every task swap, and both the GS/OS Loader and GNO ; keep DP at a non-zero, process-private page (never bank-0 zero page). ; So each thread dispatches through ITS OWN target, and a fork()ed child's ; function pointer no longer wild-jumps through the parent thread's page. ; (An earlier form JMP'd through a single global operand that crt0 self- ; modified to the main thread's DP+$B8; it aliased under preemptive multi- ; tasking and is gone -- there is no __jsl_indir_op now, and the crt0 ; variants no longer patch anything.) ; ; Keeping DP non-zero also parks every codegen scratch slot (indirect-call ; anchor $B8, PBR stash $BE, libcall scratch $E0..$FF) in reserved / ; process-private bank-0 memory instead of on system zero page that ROM ; IRQ handlers and GS/OS dispatcher state freely scribble. Forcing DP=0 ; used to crash clang-built S16 apps with C++ ctors in the init_array ; indirect-dispatch loop for exactly that reason. DP is always in bank 0 ; on the 65816, so the $B8/$BA reads resolve to bank 0 regardless of DP -- ; bare-metal (crt0.s) keeps DP=0 and the same reads land at $00B8/$00BA, ; needing no patch. ; ; The fetched pointer is honored BANK-EXPLICIT via the $BA bank byte, so a ; cross-bank function pointer -- e.g. &func the linker placed in a ; different segment/bank and redirected through a seg1 JML trampoline -- ; lands in the correct bank. ; -------------------------------------------------------------------- .globl __indirTarget __indirTarget = 0xb8 .text .globl __jsl_indir __jsl_indir: ; REENTRANT dispatch: read the 24-bit target DP-RELATIVE (offset $B8-$B9, ; bank $BA -- both stored by clang at the call site) and RTL to it, with NO ; self-modified global operand. DP is per-thread (context-switched by GNO ; every task swap; process-private under GS/OS), so each caller reads ITS ; OWN target -- a fork()ed child dispatching a function pointer no longer ; wild-jumps through the parent thread's page. Preserves A/X/Y (C-ABI arg ; regs) and the caller's JSL frame (the target's RTL returns to the JSL ; site). $BC is a per-thread DP temp to free A; assumes the 16-bit native ; call state (M=0/X=0) every clang indirect call + the ctor loop use. RTL ; adds 1 to the pulled 16-bit PC, hence offset-1. sta 0xbc ; save arg0 (A) to a per-thread DP temp sep #0x20 ; 8-bit A for the 1-byte bank push lda 0xba ; target bank pha ; push PBR rep #0x20 ; back to 16-bit A lda 0xb8 ; target offset (16-bit) dec a ; offset-1 (RTL adds 1 to the pulled PC) pha ; push PCH:PCL lda 0xbc ; restore arg0 (A) rtl ; -> bank:offset; caller's RTL frame remains below ; -------------------------------------------------------------------- ; __run_cxa_atexit — weak no-op fallback. ; ; Each crt0 variant calls `jsl __run_cxa_atexit` after main() returns, ; so pure-C programs that don't link libcxxabi must still resolve the ; symbol. libcxxabi.c overrides this with a strong implementation that ; walks the registered dtor table. ; -------------------------------------------------------------------- .weak __run_cxa_atexit __run_cxa_atexit: rtl ; -------------------------------------------------------------------- ; exit / _exit -- weak fallbacks. ; ; crt0Gsos.s and crt0Gno.s end a program by calling exit() with main()'s ; value in A. libc.c has the real exit (atexit handlers, then C++ dtors, ; then _exit); this weak one serves programs that link no libc: dtors, ; then _exit. _exit is the platform's way out: crt0Gsos.s (GS/OS QUIT) ; and libcGno.c (GNO QUIT) override the weak halt below, which is bare ; metal's -- BRK so MAME's debugger catches it, then spin. ; -------------------------------------------------------------------- .weak exit exit: pha ; status jsl __run_cxa_atexit pla jsl _exit ; does not return .weak _exit _exit: .byte 0x00, 0x00 ; BRK .L_exitSpin: bra .L_exitSpin ; -------------------------------------------------------------------- ; __heapLock / __heapUnlock -- weak no-op heap-serialization hooks. ; ; malloc()/free() (libc.c) bracket their free-list critical sections with these. ; Single-threaded runtimes (bare-metal crt0.s, GS/OS crt0Gsos.s) keep the no-op: ; there are no concurrent threads to serialize. libcGno.c overrides both with a ; GNO binary-semaphore mutex so malloc/free are safe across preemptive threads. ; -------------------------------------------------------------------- .weak __heapLock __heapLock: rtl .weak __heapUnlock __heapUnlock: rtl ; -------------------------------------------------------------------- ; __srandInitFromTime — weak no-op fallback. ; ; crt0Gsos / crt0Gno call `jsl __srandInitFromTime` after .init_array to ; seed rand() from ReadTimeHex. Programs that don't link extras.o (smoke ; harness link-tests) must still resolve the symbol; the no-op fallback ; leaves rand() at its deterministic seed-1 starting state. ; -------------------------------------------------------------------- .weak __srandInitFromTime __srandInitFromTime: rtl ; -------------------------------------------------------------------- ; __heapInitMM — weak no-op fallback. ; ; crt0Gsos calls `jsl __heapInitMM` (Memory-Manager user ID in A) before ; .init_array to back the malloc heap with a locked MM handle. GS/OS ; programs that don't link libc.o (bare probes / link tests) must still ; resolve the symbol; the no-op leaves malloc on its link-time ; [__heap_start,__heap_end) window. libc.o overrides with the strong ; version. A is ignored. ; -------------------------------------------------------------------- .weak __heapInitMM __heapInitMM: rtl ; -------------------------------------------------------------------- ; 16x16 multiplies. One method, inlined in __mulhi3 and __umulhisi3 and ; a JSR subroutine (__umul16_core) for __mulhisi3 and __mulsi3: the ; multiplier in $e0, the multiplicand in $e2, the product's low word ; returned in A and its high word in X. Clobbers Y and $e0..$e5 (never ; the IMG words $c0..$df: W65816StackRelToImg keeps values there across ; these calls). ; Both operands below 256: quarter squares, a*b = T[a+b] - T[|a-b|] ; with T[k] = k*k/4 (__umulhisi3_qsq), ~60 cycles. ; One below 256: eight steps with it as the multiplier, ~150 cycles. ; Otherwise sixteen steps, ~255 cycles. ; A step of the shift-right method: the multiplier's next bit is in C; if ; set, the product's high word gains the multiplicand (the carry out in ; C); then the high word and the multiplier word shift right together, ; the product's low bits filling the multiplier word from the top as its ; own bits leave at the bottom. The loop this replaced shifted the ; multiplicand left in 32 bits and added in 32 bits, ~44 cycles a ; multiplier bit: ~730 cycles for a 16-bit multiplier. ; -------------------------------------------------------------------- ; __mulhi3 -- 16-bit multiply, A * (4,S) -> A. Signed and unsigned are ; the same low word. .globl __mulhi3 __mulhi3: sta 0xe0 ora 0x4, s and #0xff00 bne .Lmh_wide ; Both below 256: a*b = T[a+b] - T[|a-b|], T[k] = k*k/4, the two ; entries indexed by Y and X. lda 0xe0 clc adc 0x4, s asl a tay lda 0xe0 sec sbc 0x4, s bcs .Lmh_qpos eor #0xffff inc a .Lmh_qpos: asl a tax lda __umulhisi3_qsq, y sec sbc __umulhisi3_qsq, x rtl .Lmh_wide: lda 0x4, s sta 0xe2 lda 0xe0 and #0xff00 beq .Lmh_m8 lda 0xe2 and #0xff00 bne .Lmh_m16 ; The multiplicand is the short one: swap, so eight steps do. lda 0xe0 ldx 0xe2 stx 0xe0 sta 0xe2 .Lmh_m8: lda #0x0 lsr 0xe0 bcc .Lmh_e0 clc adc 0xe2 .Lmh_e0: ror a ror 0xe0 bcc .Lmh_e1 clc adc 0xe2 .Lmh_e1: ror a ror 0xe0 bcc .Lmh_e2 clc adc 0xe2 .Lmh_e2: ror a ror 0xe0 bcc .Lmh_e3 clc adc 0xe2 .Lmh_e3: ror a ror 0xe0 bcc .Lmh_e4 clc adc 0xe2 .Lmh_e4: ror a ror 0xe0 bcc .Lmh_e5 clc adc 0xe2 .Lmh_e5: ror a ror 0xe0 bcc .Lmh_e6 clc adc 0xe2 .Lmh_e6: ror a ror 0xe0 bcc .Lmh_e7 clc adc 0xe2 .Lmh_e7: ror a ror 0xe0 ; A = product bits 23..8, $e1 = bits 7..0. sta 0xe4 xba and #0xff00 sta 0xe4 lda 0xe0 xba and #0xff ora 0xe4 rtl .Lmh_m16: lda #0x0 lsr 0xe0 bcc .Lmh_s0 clc adc 0xe2 .Lmh_s0: ror a ror 0xe0 bcc .Lmh_s1 clc adc 0xe2 .Lmh_s1: ror a ror 0xe0 bcc .Lmh_s2 clc adc 0xe2 .Lmh_s2: ror a ror 0xe0 bcc .Lmh_s3 clc adc 0xe2 .Lmh_s3: ror a ror 0xe0 bcc .Lmh_s4 clc adc 0xe2 .Lmh_s4: ror a ror 0xe0 bcc .Lmh_s5 clc adc 0xe2 .Lmh_s5: ror a ror 0xe0 bcc .Lmh_s6 clc adc 0xe2 .Lmh_s6: ror a ror 0xe0 bcc .Lmh_s7 clc adc 0xe2 .Lmh_s7: ror a ror 0xe0 bcc .Lmh_s8 clc adc 0xe2 .Lmh_s8: ror a ror 0xe0 bcc .Lmh_s9 clc adc 0xe2 .Lmh_s9: ror a ror 0xe0 bcc .Lmh_s10 clc adc 0xe2 .Lmh_s10: ror a ror 0xe0 bcc .Lmh_s11 clc adc 0xe2 .Lmh_s11: ror a ror 0xe0 bcc .Lmh_s12 clc adc 0xe2 .Lmh_s12: ror a ror 0xe0 bcc .Lmh_s13 clc adc 0xe2 .Lmh_s13: ror a ror 0xe0 bcc .Lmh_s14 clc adc 0xe2 .Lmh_s14: ror a ror 0xe0 bcc .Lmh_s15 clc adc 0xe2 .Lmh_s15: ror a ror 0xe0 lda 0xe0 rtl ; __umulhisi3 -- unsigned 16x16 -> 32: A * (4,S) -> A (low) : X (high). ; W65816NarrowI32Mul calls it for an i32 multiply of two values whose high ; words are known zero. .globl __umulhisi3 __umulhisi3: sta 0xe0 ora 0x4, s and #0xff00 bne .Lmu_wide ; Both below 256: a*b = T[a+b] - T[|a-b|], T[k] = k*k/4, the two ; entries indexed by Y and X. lda 0xe0 clc adc 0x4, s asl a tay lda 0xe0 sec sbc 0x4, s bcs .Lmu_qpos eor #0xffff inc a .Lmu_qpos: asl a tax lda __umulhisi3_qsq, y sec sbc __umulhisi3_qsq, x ldx #0x0 rtl .Lmu_wide: lda 0x4, s sta 0xe2 lda 0xe0 and #0xff00 beq .Lmu_m8 lda 0xe2 and #0xff00 bne .Lmu_m16 ; The multiplicand is the short one: swap, so eight steps do. lda 0xe0 ldx 0xe2 stx 0xe0 sta 0xe2 .Lmu_m8: lda #0x0 lsr 0xe0 bcc .Lmu_e0 clc adc 0xe2 .Lmu_e0: ror a ror 0xe0 bcc .Lmu_e1 clc adc 0xe2 .Lmu_e1: ror a ror 0xe0 bcc .Lmu_e2 clc adc 0xe2 .Lmu_e2: ror a ror 0xe0 bcc .Lmu_e3 clc adc 0xe2 .Lmu_e3: ror a ror 0xe0 bcc .Lmu_e4 clc adc 0xe2 .Lmu_e4: ror a ror 0xe0 bcc .Lmu_e5 clc adc 0xe2 .Lmu_e5: ror a ror 0xe0 bcc .Lmu_e6 clc adc 0xe2 .Lmu_e6: ror a ror 0xe0 bcc .Lmu_e7 clc adc 0xe2 .Lmu_e7: ror a ror 0xe0 ; A = product bits 23..8, $e1 = bits 7..0. sta 0xe4 xba and #0xff tax lda 0xe4 xba and #0xff00 sta 0xe4 lda 0xe0 xba and #0xff ora 0xe4 rtl .Lmu_m16: lda #0x0 lsr 0xe0 bcc .Lmu_s0 clc adc 0xe2 .Lmu_s0: ror a ror 0xe0 bcc .Lmu_s1 clc adc 0xe2 .Lmu_s1: ror a ror 0xe0 bcc .Lmu_s2 clc adc 0xe2 .Lmu_s2: ror a ror 0xe0 bcc .Lmu_s3 clc adc 0xe2 .Lmu_s3: ror a ror 0xe0 bcc .Lmu_s4 clc adc 0xe2 .Lmu_s4: ror a ror 0xe0 bcc .Lmu_s5 clc adc 0xe2 .Lmu_s5: ror a ror 0xe0 bcc .Lmu_s6 clc adc 0xe2 .Lmu_s6: ror a ror 0xe0 bcc .Lmu_s7 clc adc 0xe2 .Lmu_s7: ror a ror 0xe0 bcc .Lmu_s8 clc adc 0xe2 .Lmu_s8: ror a ror 0xe0 bcc .Lmu_s9 clc adc 0xe2 .Lmu_s9: ror a ror 0xe0 bcc .Lmu_s10 clc adc 0xe2 .Lmu_s10: ror a ror 0xe0 bcc .Lmu_s11 clc adc 0xe2 .Lmu_s11: ror a ror 0xe0 bcc .Lmu_s12 clc adc 0xe2 .Lmu_s12: ror a ror 0xe0 bcc .Lmu_s13 clc adc 0xe2 .Lmu_s13: ror a ror 0xe0 bcc .Lmu_s14 clc adc 0xe2 .Lmu_s14: ror a ror 0xe0 bcc .Lmu_s15 clc adc 0xe2 .Lmu_s15: ror a ror 0xe0 tax lda 0xe0 rtl ; __umul16_core -- the same as a JSR subroutine: $e0 * $e2 -> A : X. __umul16_core: lda 0xe0 ora 0xe2 and #0xff00 bne .Lmc_wide ; Both below 256: a*b = T[a+b] - T[|a-b|], T[k] = k*k/4, the two ; entries indexed by Y and X. lda 0xe0 clc adc 0xe2 asl a tay lda 0xe0 sec sbc 0xe2 bcs .Lmc_qpos eor #0xffff inc a .Lmc_qpos: asl a tax lda __umulhisi3_qsq, y sec sbc __umulhisi3_qsq, x ldx #0x0 rts .Lmc_wide: lda 0xe0 and #0xff00 beq .Lmc_m8 lda 0xe2 and #0xff00 bne .Lmc_m16 ; The multiplicand is the short one: swap, so eight steps do. lda 0xe0 ldx 0xe2 stx 0xe0 sta 0xe2 .Lmc_m8: lda #0x0 lsr 0xe0 bcc .Lmc_e0 clc adc 0xe2 .Lmc_e0: ror a ror 0xe0 bcc .Lmc_e1 clc adc 0xe2 .Lmc_e1: ror a ror 0xe0 bcc .Lmc_e2 clc adc 0xe2 .Lmc_e2: ror a ror 0xe0 bcc .Lmc_e3 clc adc 0xe2 .Lmc_e3: ror a ror 0xe0 bcc .Lmc_e4 clc adc 0xe2 .Lmc_e4: ror a ror 0xe0 bcc .Lmc_e5 clc adc 0xe2 .Lmc_e5: ror a ror 0xe0 bcc .Lmc_e6 clc adc 0xe2 .Lmc_e6: ror a ror 0xe0 bcc .Lmc_e7 clc adc 0xe2 .Lmc_e7: ror a ror 0xe0 ; A = product bits 23..8, $e1 = bits 7..0. sta 0xe4 xba and #0xff tax lda 0xe4 xba and #0xff00 sta 0xe4 lda 0xe0 xba and #0xff ora 0xe4 rts .Lmc_m16: lda #0x0 lsr 0xe0 bcc .Lmc_s0 clc adc 0xe2 .Lmc_s0: ror a ror 0xe0 bcc .Lmc_s1 clc adc 0xe2 .Lmc_s1: ror a ror 0xe0 bcc .Lmc_s2 clc adc 0xe2 .Lmc_s2: ror a ror 0xe0 bcc .Lmc_s3 clc adc 0xe2 .Lmc_s3: ror a ror 0xe0 bcc .Lmc_s4 clc adc 0xe2 .Lmc_s4: ror a ror 0xe0 bcc .Lmc_s5 clc adc 0xe2 .Lmc_s5: ror a ror 0xe0 bcc .Lmc_s6 clc adc 0xe2 .Lmc_s6: ror a ror 0xe0 bcc .Lmc_s7 clc adc 0xe2 .Lmc_s7: ror a ror 0xe0 bcc .Lmc_s8 clc adc 0xe2 .Lmc_s8: ror a ror 0xe0 bcc .Lmc_s9 clc adc 0xe2 .Lmc_s9: ror a ror 0xe0 bcc .Lmc_s10 clc adc 0xe2 .Lmc_s10: ror a ror 0xe0 bcc .Lmc_s11 clc adc 0xe2 .Lmc_s11: ror a ror 0xe0 bcc .Lmc_s12 clc adc 0xe2 .Lmc_s12: ror a ror 0xe0 bcc .Lmc_s13 clc adc 0xe2 .Lmc_s13: ror a ror 0xe0 bcc .Lmc_s14 clc adc 0xe2 .Lmc_s14: ror a ror 0xe0 bcc .Lmc_s15 clc adc 0xe2 .Lmc_s15: ror a ror 0xe0 tax lda 0xe0 rts ; __mulhisi3 -- signed 16x16 -> 32: A * (4,S) -> A (low) : X (high). ; W65816NarrowI32Mul calls it for an i32 multiply of two sign-extended ; 16-bit values (and __mulsi3 jumps here for them). The magnitudes go ; through the unsigned core, so small negative operands take its short ; paths too; the product is negated when the signs differ. .globl __mulhisi3 __mulhisi3: sta 0xe0 eor 0x4, s sta 0xe6 ; bit 15: the product is negative lda 0xe0 bpl .Lmhs_apos eor #0xffff inc a sta 0xe0 .Lmhs_apos: lda 0x4, s bpl .Lmhs_bpos eor #0xffff inc a .Lmhs_bpos: sta 0xe2 jsr __umul16_core ldy 0xe6 bpl .Lmhs_done eor #0xffff clc adc #0x1 sta 0xe4 txa eor #0xffff adc #0x0 tax lda 0xe4 .Lmhs_done: rtl ; -------------------------------------------------------------------- ; __ashlhi3 — A << (4,S) -> A. Shift count is i16 but only the low 4 ; bits are meaningful (counts >=16 are undefined behaviour in C). ; -------------------------------------------------------------------- .globl __ashlhi3 __ashlhi3: pha ; save value on stack so we can free A lda 0x6, s ; arg 1 sits at 6,s now (PHA shifted by 2) tax pla ; restore value .Lashl_loop: cpx #0x0 beq .Lashl_done asl a dex bra .Lashl_loop .Lashl_done: rtl ; -------------------------------------------------------------------- ; __lshrhi3 — A logical >> (4,S) -> A. Same shape as __ashlhi3 with ; LSR instead of ASL. ; -------------------------------------------------------------------- .globl __lshrhi3 __lshrhi3: pha lda 0x6, s tax pla .Llshr_loop: cpx #0x0 beq .Llshr_done lsr a dex bra .Llshr_loop .Llshr_done: rtl ; -------------------------------------------------------------------- ; __ashrhi3 — A arithmetic >> (4,S) -> A. Sign bit is preserved by ; copying it into carry before each ROR via CMP #$8000 (which sets ; carry exactly when the sign bit is set on a 16-bit unsigned compare). ; -------------------------------------------------------------------- .globl __ashrhi3 __ashrhi3: pha lda 0x6, s tax pla .Lashr_loop: cpx #0x0 beq .Lashr_done cmp #0x8000 ror a dex bra .Lashr_loop .Lashr_done: rtl ; -------------------------------------------------------------------- ; 16-bit divide and modulo. __udivmod16 (JSR): $e0 / $e2 -> quotient in ; $e0, remainder in A. Each step shifts the dividend's top bit into the ; remainder, held in A; a 17th bit carried out of A means the remainder is ; past any divisor, so it subtracts without comparing. The quotient's ; bits enter $e0 from the bottom as the dividend's leave at the top. A ; dividend below 256 starts eight bits in. ~28 cycles a step (the loop ; this replaced shifted three words a step, ~45). ; Signed: magnitudes through the same core; the quotient negated when the ; signs differ, the remainder given the dividend's sign (C99). ; -------------------------------------------------------------------- __udivmod16: ldy #0x10 lda 0xe0 and #0xff00 bne .Lud16_go lda 0xe0 xba sta 0xe0 ldy #0x8 .Lud16_go: lda #0x0 .Lud16_loop: asl 0xe0 rol a bcs .Lud16_sub cmp 0xe2 bcc .Lud16_skip .Lud16_sub: sbc 0xe2 inc 0xe0 .Lud16_skip: dey bne .Lud16_loop rts .globl __udivhi3 __udivhi3: sta 0xe0 lda 0x4, s sta 0xe2 jsr __udivmod16 lda 0xe0 rtl .globl __umodhi3 __umodhi3: sta 0xe0 lda 0x4, s sta 0xe2 jsr __udivmod16 rtl .globl __divhi3 __divhi3: sta 0xe0 eor 0x4, s sta 0xe4 ; bit 15: the quotient is negative lda 0xe0 bpl .Ldh_apos eor #0xffff inc a sta 0xe0 .Ldh_apos: lda 0x4, s bpl .Ldh_bpos eor #0xffff inc a .Ldh_bpos: sta 0xe2 jsr __udivmod16 lda 0xe0 ldy 0xe4 bpl .Ldh_done eor #0xffff inc a .Ldh_done: rtl .globl __modhi3 __modhi3: sta 0xe0 sta 0xe4 ; bit 15: the dividend (and remainder) is negative lda 0xe0 bpl .Lmh_apos eor #0xffff inc a sta 0xe0 .Lmh_apos: lda 0x4, s bpl .Lmh_bpos eor #0xffff inc a .Lmh_bpos: sta 0xe2 jsr __udivmod16 ldy 0xe4 bpl .Lmh_done eor #0xffff inc a .Lmh_done: rtl ; ==================================================================== ; 32-bit (long / si) helpers. ; ; ABI for these is the natural extension of the i16 libcalls: ; - arg0_lo in A ; - arg0_hi at (4,s) ; - arg1_lo at (6,s) (or shift count, for the shift helpers) ; - arg1_hi at (8,s) ; - return: result_lo in A, result_hi in X ; ; All are correct-but-unoptimised; goal is unblocking end-to-end builds, ; not winning a 65816 codegolf. ; ; Direct-page scratch for these: ; $e0..$e3 = a (lo, hi) [renamed from $e0/$e2 for the i16 ones] ; $e4..$e7 = b (lo, hi) ; $e8..$eb = result / quotient (lo, hi) ; $ec..$ef = remainder (lo, hi) ; ==================================================================== ; -------------------------------------------------------------------- ; __mulsi3 -- 32-bit multiply, (a * b) mod 2^32. ; ; ABI: A = a_lo, X = a_hi (the i32-first-arg in A:X convention), ; (4,s) = b_lo, (6,s) = b_hi. Result returned in A:X (lo:hi). ; ; Both high words zero: one 16x16 multiply. Both operands sign-extended ; 16-bit values (`(long)s16 * s16`, and the small negatives the old ; 32-step loop paid ~1100 cycles for): __mulhisi3, whose (4,s) is our ; b_lo. Otherwise a_lo*b_lo + ((a_lo*b_hi + a_hi*b_lo) << 16), three ; trips through __umul16_core. ; -------------------------------------------------------------------- .globl __mulsi3 __mulsi3: sta 0xe8 ; a_lo stx 0xea ; a_hi lda 0x4, s sta 0xec ; b_lo lda 0x6, s sta 0xee ; b_hi ora 0xea bne .Lmsi_wide lda 0xe8 sta 0xe0 lda 0xec sta 0xe2 jsr __umul16_core rtl .Lmsi_wide: ; a_hi == sign(a_lo) and b_hi == sign(b_lo)? `asl ; lda #0 ; adc ; #$ffff ; eor #$ffff` makes $ffff from a set bit 15, 0 from a clear one. lda 0xe8 asl a lda #0x0 adc #0xffff eor #0xffff cmp 0xea bne .Lmsi_full lda 0xec asl a lda #0x0 adc #0xffff eor #0xffff cmp 0xee bne .Lmsi_full lda 0xe8 brl __mulhisi3 .Lmsi_full: lda 0xe8 sta 0xe0 lda 0xee sta 0xe2 jsr __umul16_core ; a_lo * b_hi, low word sta 0xe6 lda 0xea sta 0xe0 lda 0xec sta 0xe2 jsr __umul16_core ; a_hi * b_lo, low word clc adc 0xe6 sta 0xe6 lda 0xe8 sta 0xe0 lda 0xec sta 0xe2 jsr __umul16_core ; a_lo * b_lo sta 0xe4 txa clc adc 0xe6 tax lda 0xe4 rtl ; -------------------------------------------------------------------- ; __ashlsi3 — (A:X) << (4,s) -> A:X. Shift count is i16 in low byte; ; counts >= 32 are UB in C. Uses a per-bit loop (cheap on 65816 — one ; ASL + ROL per bit). ; ; ABI: A = a_lo, X = a_hi (i32-first-arg in A:X), (4,s) = count. ; -------------------------------------------------------------------- .globl __ashlsi3 __ashlsi3: sta 0xe0 ; lo stx 0xe2 ; hi lda 0x4, s tay ; count -> Y; sets Z if 0 beq .Lashlsi_done ; count==0: skip loop .Lashlsi_loop: ; Per-bit: 7+7+2+3 = 19 cyc (was 25 with cpy/beq/bra). asl 0xe0 rol 0xe2 dey bne .Lashlsi_loop .Lashlsi_done: ldx 0xe2 lda 0xe0 rtl ; -------------------------------------------------------------------- ; __lshrsi3 — logical >> shift. LSR hi, ROR lo: hi gets a 0, lo gets ; hi's old bit 0. Per-bit loop. ; -------------------------------------------------------------------- .globl __lshrsi3 __lshrsi3: sta 0xe0 stx 0xe2 lda 0x4, s tay beq .Llshrsi_done .Llshrsi_loop: lsr 0xe2 ror 0xe0 dey bne .Llshrsi_loop .Llshrsi_done: ldx 0xe2 lda 0xe0 rtl ; -------------------------------------------------------------------- ; __ashrsi3 — arithmetic >> shift. Sign bit must be preserved on each ; iteration: copy bit 15 of hi into carry (via CMP #$8000), then ROR ; hi, ROR lo. Per-bit loop. ; -------------------------------------------------------------------- .globl __ashrsi3 __ashrsi3: sta 0xe0 stx 0xe2 lda 0x4, s tay beq .Lashrsi_done .Lashrsi_loop: ; CMP #$8000 sets C iff the unsigned value >= 0x8000, i.e. bit 15 ; is set — exactly the sign bit. lda 0xe2 cmp #0x8000 ror 0xe2 ror 0xe0 dey bne .Lashrsi_loop .Lashrsi_done: ldx 0xe2 lda 0xe0 rtl ; -------------------------------------------------------------------- ; __udivmodsi_core -- internal 32-bit unsigned divide (JSR / RTS). ; Dividend in $e0/$e2, divisor in $e4/$e6; quotient out in $e0/$e2, ; remainder in $e8/$ea. ; ; A divisor below 2^16 keeps the remainder in A, as __udivmod16 does: ; the dividend's high word first (skipped, quotient high word 0, when it ; is already below the divisor), then its low word, 16 steps each. A ; divisor of 2^16 or more makes a quotient below 2^16: the remainder ; starts as the dividend's high word and 16 steps shift the low word ; through a 32-bit remainder (a bit carried out of it means the remainder ; is past the divisor). ~450..900 cycles for the first form and ~1000 ; for the second, against ~2400 for the 32 three-word steps before. ; -------------------------------------------------------------------- __udivmodsi_core: lda 0xe6 bne .Lud32_wide lda 0xe2 cmp 0xe4 bcs .Lud32_hi stz 0xe2 bra .Lud32_lo .Lud32_hi: lda #0x0 ldy #0x10 .Lud32_hloop: asl 0xe2 rol a bcs .Lud32_hsub cmp 0xe4 bcc .Lud32_hskip .Lud32_hsub: sbc 0xe4 inc 0xe2 .Lud32_hskip: dey bne .Lud32_hloop .Lud32_lo: ldy #0x10 .Lud32_lloop: asl 0xe0 rol a bcs .Lud32_lsub cmp 0xe4 bcc .Lud32_lskip .Lud32_lsub: sbc 0xe4 inc 0xe0 .Lud32_lskip: dey bne .Lud32_lloop sta 0xe8 stz 0xea rts .Lud32_wide: lda 0xe2 sta 0xe8 stz 0xea stz 0xe2 ldy #0x10 .Lud32_wloop: asl 0xe0 rol 0xe8 rol 0xea bcs .Lud32_wsub lda 0xea cmp 0xe6 bcc .Lud32_wskip bne .Lud32_wsub lda 0xe8 cmp 0xe4 bcc .Lud32_wskip .Lud32_wsub: lda 0xe8 sec sbc 0xe4 sta 0xe8 lda 0xea sbc 0xe6 sta 0xea inc 0xe0 .Lud32_wskip: dey bne .Lud32_wloop rts ; -------------------------------------------------------------------- ; __udivsi3 -- unsigned 32/32 -> 32 divide. ; -------------------------------------------------------------------- .globl __udivsi3 __udivsi3: ; ABI: A = a_lo, X = a_hi, (4,s) = b_lo, (6,s) = b_hi. sta 0xe0 stx 0xe2 lda 0x4, s sta 0xe4 lda 0x6, s sta 0xe6 jsr __udivmodsi_core ldx 0xe2 lda 0xe0 rtl ; -------------------------------------------------------------------- ; __umodsi3 -- unsigned 32/32 -> 32 modulo. ; -------------------------------------------------------------------- .globl __umodsi3 __umodsi3: sta 0xe0 stx 0xe2 lda 0x4, s sta 0xe4 lda 0x6, s sta 0xe6 jsr __udivmodsi_core ldx 0xea lda 0xe8 rtl ; -------------------------------------------------------------------- ; __divsi3 / __modsi3 -- signed 32-bit divide / modulo. Strategy mirrors ; the i16 helpers: stash signs, take abs, run unsigned core, negate ; result(s) as needed. Sign tracker bits in $f0: ; bit 0 = dividend was negative (modulo result sign) ; bit 1 = quotient sign (sign(a) XOR sign(b)) ; -------------------------------------------------------------------- .globl __divsi3 __divsi3: jsr __divmodsi_setup jsr __udivmodsi_core ; Quotient at $e0/$e2. Negate if bit 1 of $f0 is set. lda 0xf0 and #0x2 beq .Ldivsi_pos lda 0xe0 eor #0xffff clc adc #0x1 sta 0xe0 lda 0xe2 eor #0xffff adc #0x0 sta 0xe2 .Ldivsi_pos: ldx 0xe2 lda 0xe0 rtl .globl __modsi3 __modsi3: jsr __divmodsi_setup jsr __udivmodsi_core ; Remainder at $e8/$ea. Negate if bit 0 of $f0 set (dividend ; was negative -- C99 remainder takes dividend's sign). lda 0xf0 and #0x1 beq .Lmodsi_pos lda 0xe8 eor #0xffff clc adc #0x1 sta 0xe8 lda 0xea eor #0xffff adc #0x0 sta 0xea .Lmodsi_pos: ldx 0xea lda 0xe8 rtl ; -------------------------------------------------------------------- ; __divmodsi_setup — common prologue for __divsi3 / __modsi3. ; Reads A=a_lo, X=a_hi (i32-first-arg ABI), (4,s)=b_lo, (6,s)=b_hi. ; Writes |a| to $e0/$e2, |b| to $e4/$e6, sign bits to $f0. JSR/RTS. ; After JSR's 2-byte ret push, callee-relative offsets are (6,s)=b_lo, ; (8,s)=b_hi. ; -------------------------------------------------------------------- __divmodsi_setup: ; Clear sign tracker. STZ preserves A. stz 0xf0 ; |a|: A=a_lo, X=a_hi. Save them first (we need a_hi for sign test). sta 0xe0 ; tentative a_lo (may negate below) stx 0xe2 ; tentative a_hi cpx #0x8000 bcc .Lsetsi_a_pos ; a is negative. Set sign tracker bits 0+1 and negate. lda 0xf0 ora #0x3 sta 0xf0 ; 32-bit negate: invert + 1. lda 0xe0 eor #0xffff clc adc #0x1 sta 0xe0 lda 0xe2 eor #0xffff adc #0x0 sta 0xe2 .Lsetsi_a_pos: ; |b|. Args shifted by 2 (the JSR ret push). lda 0x6, s sta 0xe4 lda 0x8, s sta 0xe6 cmp #0x8000 bcc .Lsetsi_b_pos ; b is negative. Flip bit 1 of $f0. lda 0xf0 eor #0x2 sta 0xf0 lda 0xe4 eor #0xffff clc adc #0x1 sta 0xe4 lda 0xe6 eor #0xffff adc #0x0 sta 0xe6 .Lsetsi_b_pos: rts ; ==================================================================== ; i64 (long long) helpers. ; ; Calling convention (i64 first arg is split via i32-first-arg path): ; A = arg0_lo[0..15] (lowest word) ; X = arg0_lo[16..31] ; 4,S = arg0_hi[0..15] ; 6,S = arg0_hi[16..31] (highest word) ; For binary ops (mul/div/mod), arg1 follows on the stack: ; 8,S = arg1_lo[0..15] ; 10,S = arg1_lo[16..31] ; 12,S = arg1_hi[0..15] ; 14,S = arg1_hi[16..31] ; For shift ops, the count occupies a single i16 at 8,S. ; ; Return ABI (matches LowerReturn for i64): ; A = result_lo[0..15] ; X = result_lo[16..31] ; Y = result_hi[0..15] ; DP $F0..$F1 = result_hi[16..31] ; ; Scratch DP layout (per-libcall, no overlap between concurrent calls): ; $E0..$E7 = a (8 bytes; 4 16-bit words) ; $E8..$EF = b OR product (8 bytes) ; ; All routines run with REP #$30 (M=0, X=0). ; ==================================================================== ; -------------------------------------------------------------------- ; __divmoddi4_stash — common entry point. Stashes a -> $E0..$E7, ; b -> $E8..$EF. Used by __udivdi3 / __umoddi3 / __divdi3 / __moddi3 ; setup; signed variants flip signs around it. ; -------------------------------------------------------------------- __divmoddi4_stash: ; Called via JSR from another libgcc helper that was itself ; called via JSL. Stack layout inside this routine: ; slot 1..2 = JSR return address (2 bytes, same-bank) ; slot 3..5 = JSL return address (3 bytes, long) ; slot 6..7 = first 16-bit stack arg (caller's first push) ; slot 8..9 = second ; ... etc. ; Earlier code read slots 4, 6, 8, 10, 12, 14 — which lands on ; the JSL ret address bytes, treating them as args. Caught by ; `u64mul(0x12, 0x12)` returning the result at $E2 (mid-low) ; instead of $E0 (lo) plus 0x678-shaped garbage at $E6. sta 0xe0 ; a_lo_lo stx 0xe2 ; a_lo_hi lda 0x6, s sta 0xe4 ; a_hi_lo lda 0x8, s sta 0xe6 ; a_hi_hi lda 0xa, s sta 0xe8 ; b_lo_lo lda 0xc, s sta 0xea ; b_lo_hi lda 0xe, s sta 0xec ; b_hi_lo lda 0x10, s sta 0xee ; b_hi_hi rts ; -------------------------------------------------------------------- ; Helper: pack the result at $E0..$E7 into the i64 return ABI. ; Trashes A, Y. Caller falls through to RTL. ; -------------------------------------------------------------------- __retdi: lda 0xe6 sta 0xf0 lda 0xe4 tay lda 0xe2 tax lda 0xe0 rtl ; -------------------------------------------------------------------- ; __ashldi3 — i64 left shift by n. Per-bit loop. Y holds count. ; -------------------------------------------------------------------- .globl __ashldi3 __ashldi3: sta 0xe0 stx 0xe2 lda 0x4, s sta 0xe4 lda 0x6, s sta 0xe6 lda 0x8, s tay ; Y = count .Lashldi_loop: cpy #0x0 beq .Lashldi_done asl 0xe0 rol 0xe2 rol 0xe4 rol 0xe6 dey bra .Lashldi_loop .Lashldi_done: brl __retdi ; -------------------------------------------------------------------- ; __lshrdi3 — i64 logical right shift. LSR top word, ROR rest. ; -------------------------------------------------------------------- .globl __lshrdi3 __lshrdi3: sta 0xe0 stx 0xe2 lda 0x4, s sta 0xe4 lda 0x6, s sta 0xe6 lda 0x8, s tay .Llshrdi_loop: cpy #0x0 beq .Llshrdi_done lsr 0xe6 ror 0xe4 ror 0xe2 ror 0xe0 dey bra .Llshrdi_loop .Llshrdi_done: brl __retdi ; -------------------------------------------------------------------- ; __ashrdi3 — i64 arithmetic right shift. Same as lshrdi3 but the top ; bit replicates: sign-extend by ASL/ROR which would clear; instead ; take a copy of the sign and OR it back, OR use cmp/sbc trick — use ; the standard idiom: capture sign before LSR via "asl; ror" so C is ; preserved. Simpler: copy bit 15 of $E7 into C before each shift. ; -------------------------------------------------------------------- .globl __ashrdi3 __ashrdi3: sta 0xe0 stx 0xe2 lda 0x4, s sta 0xe4 lda 0x6, s sta 0xe6 lda 0x8, s tay .Lashrdi_loop: cpy #0x0 beq .Lashrdi_done ; "ASL $E6" sets C from bit 15 (the sign), then we ROR $E6 back. ; Net effect on $E6: arithmetic right shift by 1 (sign preserved). ; The carry chain into $E4..$E0 is the new bit 15. lda 0xe6 asl a ; C = sign bit; A = (sign<<1) | rest ror 0xe6 ; $E6: (sign << 15) | ($E6 >> 1) ror 0xe4 ror 0xe2 ror 0xe0 dey bra .Lashrdi_loop .Lashrdi_done: brl __retdi ; -------------------------------------------------------------------- ; __muldi3 — i64 multiply (low 64 bits of 64x64 product). ; Shift-and-add over a (64 bits). Product accumulates at $F2..$F9 ; (above the return DP slot, scratch). Need a fresh 8-byte product ; slot since $E0..$EF holds operands. ; -------------------------------------------------------------------- .globl __muldi3 __muldi3: jsr __divmoddi4_stash ; Clear product P0..P3 at $F2..$F8. stz 0xf2 stz 0xf4 stz 0xf6 stz 0xf8 ; Short-circuit when a's high half ($E4/$E6) is zero: bits 32..63 ; of a are 0, so the 32 high iterations would add nothing. Saves ; ~50% of __muldi3 cost in mulhi64Aligned (softDouble.c), which ; passes only u32-wide operands. b's high half is irrelevant for ; this short-circuit — even if b is full-width, iters 32..63 only ; shift b and add zero. lda 0xe4 ora 0xe6 bne .Lmuldi_long ldy #0x20 bra .Lmuldi_loop .Lmuldi_long: ldy #0x40 .Lmuldi_loop: ; Right-shift the 64-bit `a` by 1. $E0=lo..$E6=hi (matches the ; stash + __retdi convention). Must shift HI first (LSR loses ; bit 63 of $E6) so each ROR carries the previous half's bit 0 ; INTO the top of the next-LOWER half — that's the actual ; right-shift direction in a $E0=lo layout. After the chain, ; C = orig $E0_b0 = bit 0 of the 64-bit value, which drops out ; and is what we want to BCC on. The earlier code shifted lo ; first which ran the shift in the WRONG direction (lo → hi) ; and tested $E6_b0 (bit 48) instead of bit 0 — every multiply ; involving bits 16+ came back garbage. lda 0xe6 lsr a sta 0xe6 lda 0xe4 ror a sta 0xe4 lda 0xe2 ror a sta 0xe2 lda 0xe0 ror a sta 0xe0 bcc .Lmuldi_noadd ; Add b ($E8..$EE) to product ($F2..$F8). clc lda 0xf2 adc 0xe8 sta 0xf2 lda 0xf4 adc 0xea sta 0xf4 lda 0xf6 adc 0xec sta 0xf6 lda 0xf8 adc 0xee sta 0xf8 .Lmuldi_noadd: ; Shift b left by 1 (so each iteration uses next bit position). asl 0xe8 rol 0xea rol 0xec rol 0xee dey bne .Lmuldi_loop ; Move product into return slots ($E0..$E7) and tail-call __retdi. lda 0xf2 sta 0xe0 lda 0xf4 sta 0xe2 lda 0xf6 sta 0xe4 lda 0xf8 sta 0xe6 brl __retdi ; -------------------------------------------------------------------- ; __ucmpdi2 — unsigned i64 compare. Returns 0 if ab (libgcc convention). We emit i16 result in A (with the ; high bytes don't-care). ; -------------------------------------------------------------------- .globl __ucmpdi2 __ucmpdi2: ; Compare from MSB downwards. Stash a/b first so we have a stable ; layout. jsr __divmoddi4_stash ; Compare $E6 vs $EE (a_hi_hi vs b_hi_hi). lda 0xe6 cmp 0xee bne .Lucmpdi_decided lda 0xe4 cmp 0xec bne .Lucmpdi_decided lda 0xe2 cmp 0xea bne .Lucmpdi_decided lda 0xe0 cmp 0xe8 bne .Lucmpdi_decided ; Equal. lda #0x1 rtl .Lucmpdi_decided: ; Carry clear -> a < b -> return 0. ; Carry set, Z clear -> a > b -> return 2. bcc .Lucmpdi_lt lda #0x2 rtl .Lucmpdi_lt: lda #0x0 rtl ; -------------------------------------------------------------------- ; __cmpdi2 — signed i64 compare. Same {0,1,2} return convention. ; Implemented by flipping the high-word sign bits before doing an ; unsigned compare ($N XOR $8000 swaps the signed-int order to ; unsigned-int order). ; -------------------------------------------------------------------- .globl __cmpdi2 __cmpdi2: jsr __divmoddi4_stash lda 0xe6 eor #0x8000 sta 0xe6 lda 0xee eor #0x8000 sta 0xee ; Unsigned compare on the rewritten values. lda 0xe6 cmp 0xee bne .Lcmpdi_decided lda 0xe4 cmp 0xec bne .Lcmpdi_decided lda 0xe2 cmp 0xea bne .Lcmpdi_decided lda 0xe0 cmp 0xe8 bne .Lcmpdi_decided lda #0x1 rtl .Lcmpdi_decided: bcc .Lcmpdi_lt lda #0x2 rtl .Lcmpdi_lt: lda #0x0 rtl ; -------------------------------------------------------------------- ; __udivdi3 / __umoddi3 — unsigned 64-bit divide / modulo. Restoring ; division: shift dividend left into a remainder register, conditionally ; subtract the divisor. The two libcalls share the core; quotient ; lands at $E0..$E7, remainder at $F2..$F8. Each entry sets a flag in ; X to select which to return. ; -------------------------------------------------------------------- .globl __udivdi3 __udivdi3: jsr __divmoddi4_stash jsr __udivmoddi_core brl __retdi .globl __umoddi3 __umoddi3: jsr __divmoddi4_stash jsr __udivmoddi_core ; Move remainder ($F2..$F8) -> $E0..$E7 for return. lda 0xf2 sta 0xe0 lda 0xf4 sta 0xe2 lda 0xf6 sta 0xe4 lda 0xf8 sta 0xe6 brl __retdi ; Core: dividend at $E0..$E6, divisor at $E8..$EE. ; Output: quotient at $E0..$E6, remainder at $F2..$F8. __udivmoddi_core: ; Clear remainder $F2..$F8. stz 0xf2 stz 0xf4 stz 0xf6 stz 0xf8 ldy #0x40 .Ludivmoddi_loop: ; Shift left: dividend (becomes quotient) and remainder together ; as a 128-bit register. bit shifted out of dividend top -> remainder LSB. asl 0xe0 rol 0xe2 rol 0xe4 rol 0xe6 rol 0xf2 rol 0xf4 rol 0xf6 rol 0xf8 ; Try remainder - divisor. If no borrow, accept and set quotient bit. sec lda 0xf2 sbc 0xe8 sta 0xfa ; tentative subtract result at $FA..$ lda 0xf4 sbc 0xea sta 0xfc lda 0xf6 sbc 0xec sta 0xfe lda 0xf8 sbc 0xee ; A holds new high word. C = !borrow. bcc .Ludivmoddi_skip ; Accept: remainder = remainder - divisor, quotient bit 0 = 1. sta 0xf8 lda 0xfe sta 0xf6 lda 0xfc sta 0xf4 lda 0xfa sta 0xf2 ; Set bit 0 of dividend (which we shifted left, so position is open). lda 0xe0 ora #0x1 sta 0xe0 .Ludivmoddi_skip: dey bne .Ludivmoddi_loop rts ; -------------------------------------------------------------------- ; __divdi3 / __moddi3 — signed 64-bit divide / modulo. Take absolute ; values, run the unsigned core, fix up the sign. ; div: sign(quotient) = sign(a) XOR sign(b) ; mod: sign(remainder) = sign(a) ; -------------------------------------------------------------------- .globl __divdi3 __divdi3: jsr __divmoddi4_stash ; Track signs: bit 15 of $E6 (a) and $EE (b). Save XOR in a temp. lda 0xe6 eor 0xee and #0x8000 sta 0xfa ; sign of quotient at $FA ; Abs(a) jsr __absdi_a ; Abs(b) jsr __absdi_b jsr __udivmoddi_core ; Fix quotient sign: if $FA != 0, negate $E0..$E6. lda 0xfa beq .Ldivdi_pos jsr __negdi_a .Ldivdi_pos: brl __retdi .globl __moddi3 __moddi3: jsr __divmoddi4_stash ; Mod sign = sign of a. lda 0xe6 and #0x8000 sta 0xfa jsr __absdi_a jsr __absdi_b jsr __udivmoddi_core ; Move remainder to $E0..$E6. lda 0xf2 sta 0xe0 lda 0xf4 sta 0xe2 lda 0xf6 sta 0xe4 lda 0xf8 sta 0xe6 ; Apply sign. lda 0xfa beq .Lmoddi_pos jsr __negdi_a .Lmoddi_pos: brl __retdi ; --- subroutines used by signed div/mod --- ; __absdi_a: if $E6 has sign bit set, negate $E0..$E6. __absdi_a: lda 0xe6 bpl .Labsdi_a_done jsr __negdi_a .Labsdi_a_done: rts ; __absdi_b: if $EE has sign bit set, negate $E8..$EE. __absdi_b: lda 0xee bpl .Labsdi_b_done jsr __negdi_b .Labsdi_b_done: rts ; __negdi_a: 2's complement negate $E0..$E6. __negdi_a: sec lda #0x0 sbc 0xe0 sta 0xe0 lda #0x0 sbc 0xe2 sta 0xe2 lda #0x0 sbc 0xe4 sta 0xe4 lda #0x0 sbc 0xe6 sta 0xe6 rts ; __negdi_b: 2's complement negate $E8..$EE. __negdi_b: sec lda #0x0 sbc 0xe8 sta 0xe8 lda #0x0 sbc 0xea sta 0xea lda #0x0 sbc 0xec sta 0xec lda #0x0 sbc 0xee sta 0xee rts ; -------------------------------------------------------------------- ; setjmp(jmp_buf env) - save calling environment, return 0 ; longjmp(jmp_buf env, int val) - restore environment, return val (or 1 if val == 0) ; ; jmp_buf layout (8 bytes): ; [0..1] = caller's stack pointer (SP+3 at entry to setjmp) ; [2..3] = return address PC lo:hi (16 bits) ; [4] = return address bank (1 byte) ; [5..6] = direct page register (DP) ; [7] = reserved / padding ; ; Caller-save convention: longjmp doesn't restore X / Y / A — caller's ; setjmp returned 0 with all-callee-savable regs already preserved by ; setjmp's caller. ; -------------------------------------------------------------------- ; setjmp / longjmp use the (dp),y indirect mode (opcodes 0x91/0xb1) ; to write through the jmp_buf pointer in $E0. Y is set explicitly ; before each indirect access; M=0 except where noted. .globl setjmp setjmp: sta 0xe0 ; jmp_buf addr -> DP scratch tsc ; A = current SP clc adc #0x3 ; A = caller's SP (undo JSL push) ldy #0 sta (0xe0), y ; env[0..1] = caller SP (16-bit M) lda 0x1, s ; A = retaddr lo:hi ldy #2 sta (0xe0), y ; env[2..3] = retaddr lo:hi sep #0x20 lda 0x3, s ; A_lo = bank ldy #4 sta (0xe0), y ; env[4] = bank (8-bit M) rep #0x20 tdc ; A = DP ldy #5 sta (0xe0), y ; env[5..6] = DP lda #0 ; setjmp returns 0 rtl .globl longjmp longjmp: sta 0xe0 ; jmp_buf addr -> DP scratch lda 0x4, s ; A = val (2nd arg, on stack) sta 0xe2 ; save val ; Restore SP: env[0..1]. TCS directly — the PHAs below consume ; 3 bytes (1 bank + 2 retaddr); RTL pulls them back; net post-RTL ; S = saved_SP. (Earlier this subtracted 3 before TCS, leaving ; S = saved_SP - 3 after RTL, mangling caller's stack-relative ; reads — caught by an SJLJ EH test where main's locals shifted ; by 3 bytes after longjmp.) ldy #0 lda (0xe0), y ; A = saved SP (16-bit) tcs ; SP = saved_SP ; Push retaddr: bank, then 16-bit lo:hi. RTL pulls lo, hi, bank. sep #0x20 ldy #4 lda (0xe0), y ; bank (8-bit) pha rep #0x20 ldy #2 lda (0xe0), y ; lo:hi (16-bit) pha ; Restore DP. ldy #5 lda (0xe0), y ; DP (16-bit) tcd ; Compute return value: val if nonzero, else 1. lda 0xe2 bne .Llj_done lda #1 .Llj_done: rtl ; -------------------------------------------------------------------- ; __umulhisi3_qsq: quarter-square lookup table. ; T[k] = floor(k² / 4) for k ∈ [0, 510]. Used by __umulhisi3's fast ; 8x8 path: a*b = T[a+b] - T[|a-b|]. 511 entries × 2 bytes = 1022 B. ; -------------------------------------------------------------------- __umulhisi3_qsq: .short 0x0000, 0x0000, 0x0001, 0x0002, 0x0004, 0x0006, 0x0009, 0x000c .short 0x0010, 0x0014, 0x0019, 0x001e, 0x0024, 0x002a, 0x0031, 0x0038 .short 0x0040, 0x0048, 0x0051, 0x005a, 0x0064, 0x006e, 0x0079, 0x0084 .short 0x0090, 0x009c, 0x00a9, 0x00b6, 0x00c4, 0x00d2, 0x00e1, 0x00f0 .short 0x0100, 0x0110, 0x0121, 0x0132, 0x0144, 0x0156, 0x0169, 0x017c .short 0x0190, 0x01a4, 0x01b9, 0x01ce, 0x01e4, 0x01fa, 0x0211, 0x0228 .short 0x0240, 0x0258, 0x0271, 0x028a, 0x02a4, 0x02be, 0x02d9, 0x02f4 .short 0x0310, 0x032c, 0x0349, 0x0366, 0x0384, 0x03a2, 0x03c1, 0x03e0 .short 0x0400, 0x0420, 0x0441, 0x0462, 0x0484, 0x04a6, 0x04c9, 0x04ec .short 0x0510, 0x0534, 0x0559, 0x057e, 0x05a4, 0x05ca, 0x05f1, 0x0618 .short 0x0640, 0x0668, 0x0691, 0x06ba, 0x06e4, 0x070e, 0x0739, 0x0764 .short 0x0790, 0x07bc, 0x07e9, 0x0816, 0x0844, 0x0872, 0x08a1, 0x08d0 .short 0x0900, 0x0930, 0x0961, 0x0992, 0x09c4, 0x09f6, 0x0a29, 0x0a5c .short 0x0a90, 0x0ac4, 0x0af9, 0x0b2e, 0x0b64, 0x0b9a, 0x0bd1, 0x0c08 .short 0x0c40, 0x0c78, 0x0cb1, 0x0cea, 0x0d24, 0x0d5e, 0x0d99, 0x0dd4 .short 0x0e10, 0x0e4c, 0x0e89, 0x0ec6, 0x0f04, 0x0f42, 0x0f81, 0x0fc0 .short 0x1000, 0x1040, 0x1081, 0x10c2, 0x1104, 0x1146, 0x1189, 0x11cc .short 0x1210, 0x1254, 0x1299, 0x12de, 0x1324, 0x136a, 0x13b1, 0x13f8 .short 0x1440, 0x1488, 0x14d1, 0x151a, 0x1564, 0x15ae, 0x15f9, 0x1644 .short 0x1690, 0x16dc, 0x1729, 0x1776, 0x17c4, 0x1812, 0x1861, 0x18b0 .short 0x1900, 0x1950, 0x19a1, 0x19f2, 0x1a44, 0x1a96, 0x1ae9, 0x1b3c .short 0x1b90, 0x1be4, 0x1c39, 0x1c8e, 0x1ce4, 0x1d3a, 0x1d91, 0x1de8 .short 0x1e40, 0x1e98, 0x1ef1, 0x1f4a, 0x1fa4, 0x1ffe, 0x2059, 0x20b4 .short 0x2110, 0x216c, 0x21c9, 0x2226, 0x2284, 0x22e2, 0x2341, 0x23a0 .short 0x2400, 0x2460, 0x24c1, 0x2522, 0x2584, 0x25e6, 0x2649, 0x26ac .short 0x2710, 0x2774, 0x27d9, 0x283e, 0x28a4, 0x290a, 0x2971, 0x29d8 .short 0x2a40, 0x2aa8, 0x2b11, 0x2b7a, 0x2be4, 0x2c4e, 0x2cb9, 0x2d24 .short 0x2d90, 0x2dfc, 0x2e69, 0x2ed6, 0x2f44, 0x2fb2, 0x3021, 0x3090 .short 0x3100, 0x3170, 0x31e1, 0x3252, 0x32c4, 0x3336, 0x33a9, 0x341c .short 0x3490, 0x3504, 0x3579, 0x35ee, 0x3664, 0x36da, 0x3751, 0x37c8 .short 0x3840, 0x38b8, 0x3931, 0x39aa, 0x3a24, 0x3a9e, 0x3b19, 0x3b94 .short 0x3c10, 0x3c8c, 0x3d09, 0x3d86, 0x3e04, 0x3e82, 0x3f01, 0x3f80 .short 0x4000, 0x4080, 0x4101, 0x4182, 0x4204, 0x4286, 0x4309, 0x438c .short 0x4410, 0x4494, 0x4519, 0x459e, 0x4624, 0x46aa, 0x4731, 0x47b8 .short 0x4840, 0x48c8, 0x4951, 0x49da, 0x4a64, 0x4aee, 0x4b79, 0x4c04 .short 0x4c90, 0x4d1c, 0x4da9, 0x4e36, 0x4ec4, 0x4f52, 0x4fe1, 0x5070 .short 0x5100, 0x5190, 0x5221, 0x52b2, 0x5344, 0x53d6, 0x5469, 0x54fc .short 0x5590, 0x5624, 0x56b9, 0x574e, 0x57e4, 0x587a, 0x5911, 0x59a8 .short 0x5a40, 0x5ad8, 0x5b71, 0x5c0a, 0x5ca4, 0x5d3e, 0x5dd9, 0x5e74 .short 0x5f10, 0x5fac, 0x6049, 0x60e6, 0x6184, 0x6222, 0x62c1, 0x6360 .short 0x6400, 0x64a0, 0x6541, 0x65e2, 0x6684, 0x6726, 0x67c9, 0x686c .short 0x6910, 0x69b4, 0x6a59, 0x6afe, 0x6ba4, 0x6c4a, 0x6cf1, 0x6d98 .short 0x6e40, 0x6ee8, 0x6f91, 0x703a, 0x70e4, 0x718e, 0x7239, 0x72e4 .short 0x7390, 0x743c, 0x74e9, 0x7596, 0x7644, 0x76f2, 0x77a1, 0x7850 .short 0x7900, 0x79b0, 0x7a61, 0x7b12, 0x7bc4, 0x7c76, 0x7d29, 0x7ddc .short 0x7e90, 0x7f44, 0x7ff9, 0x80ae, 0x8164, 0x821a, 0x82d1, 0x8388 .short 0x8440, 0x84f8, 0x85b1, 0x866a, 0x8724, 0x87de, 0x8899, 0x8954 .short 0x8a10, 0x8acc, 0x8b89, 0x8c46, 0x8d04, 0x8dc2, 0x8e81, 0x8f40 .short 0x9000, 0x90c0, 0x9181, 0x9242, 0x9304, 0x93c6, 0x9489, 0x954c .short 0x9610, 0x96d4, 0x9799, 0x985e, 0x9924, 0x99ea, 0x9ab1, 0x9b78 .short 0x9c40, 0x9d08, 0x9dd1, 0x9e9a, 0x9f64, 0xa02e, 0xa0f9, 0xa1c4 .short 0xa290, 0xa35c, 0xa429, 0xa4f6, 0xa5c4, 0xa692, 0xa761, 0xa830 .short 0xa900, 0xa9d0, 0xaaa1, 0xab72, 0xac44, 0xad16, 0xade9, 0xaebc .short 0xaf90, 0xb064, 0xb139, 0xb20e, 0xb2e4, 0xb3ba, 0xb491, 0xb568 .short 0xb640, 0xb718, 0xb7f1, 0xb8ca, 0xb9a4, 0xba7e, 0xbb59, 0xbc34 .short 0xbd10, 0xbdec, 0xbec9, 0xbfa6, 0xc084, 0xc162, 0xc241, 0xc320 .short 0xc400, 0xc4e0, 0xc5c1, 0xc6a2, 0xc784, 0xc866, 0xc949, 0xca2c .short 0xcb10, 0xcbf4, 0xccd9, 0xcdbe, 0xcea4, 0xcf8a, 0xd071, 0xd158 .short 0xd240, 0xd328, 0xd411, 0xd4fa, 0xd5e4, 0xd6ce, 0xd7b9, 0xd8a4 .short 0xd990, 0xda7c, 0xdb69, 0xdc56, 0xdd44, 0xde32, 0xdf21, 0xe010 .short 0xe100, 0xe1f0, 0xe2e1, 0xe3d2, 0xe4c4, 0xe5b6, 0xe6a9, 0xe79c .short 0xe890, 0xe984, 0xea79, 0xeb6e, 0xec64, 0xed5a, 0xee51, 0xef48 .short 0xf040, 0xf138, 0xf231, 0xf32a, 0xf424, 0xf51e, 0xf619, 0xf714 .short 0xf810, 0xf90c, 0xfa09, 0xfb06, 0xfc04, 0xfd02, 0xfe01 ; -------------------------------------------------------------------- ; memcpy / memmove / memset -- block moves with MVN / MVP. ; ; C signatures (size_t is 32 bits): ; void *memcpy (void *dst, const void *src, size_t n); ; void *memmove(void *dst, const void *src, size_t n); ; void *memset (void *dst, int c, size_t n); ; dst arrives in A (offset) : X (bank, pad byte 0); the other arguments ; are on the stack after the 3-byte return address: src at 4,s and n at ; 8,s, or c at 4,s and n at 6,s. All three return dst in A:X. ; ; Counts below MEM_SHORT copy through [dp],y with 16-bit loads and stores ; (10.5 cycles a byte): MVN's setup costs as much as that many bytes of ; the loop. A long-indirect-indexed address is a full 24-bit sum, so ; these loops cross bank boundaries on their own. ; ; Longer copies run MVN (forward) or MVP (backward), 7 cycles a byte. ; Both wrap X and Y inside their banks, so a copy is cut into runs that ; stop at every 64 KB boundary of the source and of the destination (most ; copies are one run). The bank operands are immediates, so each call ; builds its own 4-byte stub on the stack -- MVN/MVP, dst bank, src bank, ; RTL -- and RTLs into it; the stack is always bank 0, so this works from ; any code bank. Nothing shared is modified: an interrupt that lands ; mid-move (MVN/MVP resume after RTI) may itself call these routines, as ; may another GNO thread, without disturbing the interrupted move. MVN / ; MVP set DBR to the destination bank; the caller's DBR is saved on entry ; and restored on exit. D is never changed; the direct-page bytes used ; are the $E0-$EB libcall scratch, so an interrupt handler that runs C ; must switch D (or save that scratch) like it must for any C code. ; ; memset needs no block move: a 16-bit store loop fills at 5.9 cycles a ; byte, faster than MVN, without its setup. It fills in passes of at ; most MEM_PASS bytes so the 16-bit index never wraps. ; -------------------------------------------------------------------- MEM_SRC = 0xe0 ; DP: source (memset: fill) far pointer MEM_DST = 0xe4 ; DP: destination far pointer, the result MEM_REM = 0xe8 ; DP: memset bytes still to fill, 32 bits MEM_SHORT = 34 ; from this count on, copies use MVN / MVP MEM_PASS = 0x8000 ; memset pass length for fills of 64 KB up OP_MVN = 0x54 OP_MVP = 0x44 OP_RTL = 0x6b OP_FWD = 0x10 ; the opcode bit that tells MVN from MVP ; The multi-run frame, as stack offsets from its base S0 (the stack ; pointer at the top of every run). F_CNT = 1 ; bytes - 1 in this run (MVN/MVP's A) F_REM = 3 ; bytes still to move, 32 bits F_STUB = 7 ; stub: opcode, dst bank, src bank, RTL F_DBR = 11 ; the caller's DBR .section .text.memcpy,"ax",@progbits .globl memcpy .globl memmove .globl memset memcpy: rep #0x30 sta MEM_DST stx MEM_DST+2 lda 4, s sta MEM_SRC lda 6, s sta MEM_SRC+2 lda 10, s ; n.hi bne .LmemFwdFar lda 8, s ; n.lo cmp #MEM_SHORT bcs .LmemFwdLong tay beq .LmemRet ; Copy [0, Y) top down in words, an odd last byte first: right for any ; copy but an overlapping one with dst below src. Y < $8000. .LmemDown: tya lsr a ; C = odd count bcc .LmemDownPair dey sep #0x20 .a8 lda [MEM_SRC], y sta [MEM_DST], y rep #0x20 .a16 .LmemDownPair: dey dey ; the last whole word, or $FFFE: none left bmi .LmemRet .LmemDownWords: lda [MEM_SRC], y sta [MEM_DST], y dey dey bpl .LmemDownWords .LmemRet: lda MEM_DST ldx MEM_DST+2 rtl ; One MVN when neither block crosses a bank boundary. A = n < 64 KB. .LmemFwdLong: dec a tax ; n - 1 clc adc MEM_SRC bcs .LmemFwdFar txa clc adc MEM_DST bcs .LmemFwdFar ldx MEM_SRC ldy MEM_DST lda #OP_MVN ; One MVN / MVP (A = its opcode) of n bytes from X to Y, in the source's ; and the destination's banks. The stub's RTL returns to .LmemOneBack. .LmemOne: phb sep #0x20 .a8 xba ; keep the opcode in B lda #OP_RTL pha lda MEM_SRC+2 pha lda MEM_DST+2 pha xba pha ; the stub is 4,s..7,s from here phk per .LmemOneBack-1 lda #0 pha ; the stack's bank rep #0x20 .a16 tsc clc adc #4 ; the stub's address - 1 pha lda 19, s ; n.lo (n was 8,s on entry, 11 bytes ago) dec a rtl .LmemOneBack: pla ; drop the stub pla plb lda MEM_DST ldx MEM_DST+2 rtl .LmemFwdFar: brl .LmemFwdRuns memmove: rep #0x30 sta MEM_DST stx MEM_DST+2 lda 4, s sta MEM_SRC lda 6, s sta MEM_SRC+2 ; dst below src moves forward, dst above src backward; the pad ; bytes above the banks take no part in the compare. sep #0x20 .a8 lda MEM_DST+2 cmp MEM_SRC+2 rep #0x20 .a16 bne .LmemOrdered lda MEM_DST cmp MEM_SRC beq .LmemMoveRet .LmemOrdered: bcc .LmemMoveUp lda 10, s ; n.hi bne .LmemBwdRuns lda 8, s ; n.lo cmp #MEM_SHORT bcs .LmemBwdLong tay beq .LmemMoveRet brl .LmemDown .LmemMoveUp: lda 10, s ; n.hi bne .LmemMoveFwdRuns lda 8, s ; n.lo cmp #MEM_SHORT bcs .LmemMoveFwdLong tay beq .LmemMoveRet ; Copy [0, n) bottom up in words, an odd last byte last: right for an ; overlapping copy with dst below src. A = n; C survives the loop. ldy #0 lsr a ; C = odd count, A = whole words tax beq .LmemUpOdd .LmemUpWords: lda [MEM_SRC], y sta [MEM_DST], y iny iny dex bne .LmemUpWords .LmemUpOdd: bcc .LmemMoveRet sep #0x20 .a8 lda [MEM_SRC], y sta [MEM_DST], y rep #0x20 .a16 .LmemMoveRet: lda MEM_DST ldx MEM_DST+2 rtl .LmemMoveFwdLong: brl .LmemFwdLong .LmemMoveFwdRuns: brl .LmemFwdRuns ; One MVP from the last bytes when neither block crosses a bank ; boundary. A = n < 64 KB. .LmemBwdLong: dec a tay ; n - 1 clc adc MEM_SRC bcs .LmemBwdRuns tax ; the source's last byte tya clc adc MEM_DST bcs .LmemBwdRuns tay ; the destination's last byte lda #OP_MVP brl .LmemOne ; Several runs, backward: X / Y = the last byte's offset in its bank, the ; stub's bank bytes = that byte's bank. .LmemBwdRuns: phb pea OP_RTL << 8 pea OP_MVP ; S0 + 6 here: the stub is 1,s..4,s and n 13,s..16,s. lda MEM_SRC clc adc 13, s tax sep #0x20 .a8 lda MEM_SRC+2 adc 15, s sta 3, s rep #0x20 .a16 txa bne .LmemBwdSrc sep #0x20 .a8 lda 3, s dec a sta 3, s rep #0x20 .a16 .LmemBwdSrc: dex lda MEM_DST clc adc 13, s tay sep #0x20 .a8 lda MEM_DST+2 adc 15, s sta 2, s rep #0x20 .a16 tya bne .LmemBwdDst sep #0x20 .a8 lda 2, s dec a sta 2, s rep #0x20 .a16 .LmemBwdDst: dey bra .LmemRem ; Several runs, forward: X / Y = the first byte's offset. .LmemFwdRuns: phb pea OP_RTL << 8 pea OP_MVN sep #0x20 .a8 lda MEM_SRC+2 sta 3, s lda MEM_DST+2 sta 2, s rep #0x20 .a16 ldx MEM_SRC ldy MEM_DST .LmemRem: lda 15, s ; n.hi pha lda 15, s ; n.lo pha pha ; F_CNT ; One run: as many bytes as fit before the source or the destination ; leaves its bank, or the rest of the copy if that is less. .LmemRun: lda F_STUB, s and #OP_FWD beq .LmemRunDown txa eor #0xffff ; bytes - 1 up to the source bank's end sta F_CNT, s tya eor #0xffff ; and to the destination bank's end bra .LmemRunMin .LmemRunDown: txa ; bytes - 1 down to the source bank's start sta F_CNT, s tya ; and to the destination bank's start .LmemRunMin: cmp F_CNT, s bcs .LmemRunRem sta F_CNT, s .LmemRunRem: lda F_REM+2, s bne .LmemRunGo ; 64 KB or more to go: a bank edge is first lda F_REM, s dec a cmp F_CNT, s bcs .LmemRunGo sta F_CNT, s .LmemRunGo: lda F_CNT, s eor #0xffff ; -(bytes in this run) clc adc F_REM, s sta F_REM, s lda F_REM+2, s adc #0xffff sta F_REM+2, s ; RTL into the stub (bank 0), whose RTL comes back here. phk per .LmemRunBack-1 sep #0x20 .a8 lda #0 pha rep #0x20 .a16 tsc clc adc #F_STUB+3 ; S0 - 4 now: the stub's address - 1 pha lda F_CNT+6, s rtl .LmemRunBack: lda F_REM, s ora F_REM+2, s beq .LmemRunsDone ; X / Y wrapped to 0 (forward) or $FFFF (backward) where the source / ; destination left its bank: step that bank byte. lda F_STUB, s and #OP_FWD sep #0x20 .a8 beq .LmemStepDown cpx #0 bne .LmemStepUpDst lda F_STUB+2, s inc a sta F_STUB+2, s .LmemStepUpDst: cpy #0 bne .LmemStepDone lda F_STUB+1, s inc a sta F_STUB+1, s bra .LmemStepDone .LmemStepDown: cpx #0xffff bne .LmemStepDownDst lda F_STUB+2, s dec a sta F_STUB+2, s .LmemStepDownDst: cpy #0xffff bne .LmemStepDone lda F_STUB+1, s dec a sta F_STUB+1, s .LmemStepDone: rep #0x20 .a16 bra .LmemRun .LmemRunsDone: tsc clc adc #F_DBR-1 ; drop the run count, the rest and the stub tcs plb lda MEM_DST ldx MEM_DST+2 rtl memset: rep #0x30 sta MEM_DST stx MEM_DST+2 sta MEM_SRC ; the fill pointer moves; MEM_DST is the result stx MEM_SRC+2 lda 4, s ; c: only its low byte counts sep #0x20 .a8 xba lda 4, s rep #0x20 .a16 tax ; X = the fill byte, twice lda 8, s ; n.hi sta MEM_REM+2 bne .LmemSetBig lda 6, s ; n.lo tay beq .LmemSetRet ; Fill [0, Y) top down: an odd last byte, words down to a multiple of 8 ; bytes, then 8 bytes a loop. .LmemSetFill: tya lsr a ; C = odd count bcc .LmemSetPeel dey txa sep #0x20 .a8 sta [MEM_SRC], y rep #0x20 .a16 .LmemSetPeel: tya bit #0x0006 beq .LmemSetOcts txa dey dey sta [MEM_SRC], y bra .LmemSetPeel .LmemSetOcts: txa cpy #0 beq .LmemSetDone .LmemSetOct: dey dey sta [MEM_SRC], y dey dey sta [MEM_SRC], y dey dey sta [MEM_SRC], y dey dey sta [MEM_SRC], y bne .LmemSetOct .LmemSetDone: lda MEM_REM+2 bne .LmemSetStep .LmemSetRet: lda MEM_DST ldx MEM_DST+2 rtl ; 64 KB or more: MEM_PASS bytes a pass while the rest's high word is ; nonzero (so it is nonzero after every such pass's fill), then the rest. .LmemSetBig: lda 6, s ; n.lo sta MEM_REM .LmemSetPass: ldy #MEM_PASS bra .LmemSetFill .LmemSetStep: lda MEM_SRC clc adc #MEM_PASS sta MEM_SRC lda MEM_SRC+2 adc #0 sta MEM_SRC+2 lda MEM_REM sec sbc #MEM_PASS sta MEM_REM lda MEM_REM+2 sbc #0 sta MEM_REM+2 bne .LmemSetPass ldy MEM_REM bne .LmemSetFill bra .LmemSetRet