1487 lines
41 KiB
ArmAsm
1487 lines
41 KiB
ArmAsm
; 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.
|
||
;
|
||
; 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. An indirect call (function pointer) stores
|
||
; the target's 16-bit address to __indirTarget before JSL'ing here.
|
||
; This routine does a JMP indirect through that variable: control
|
||
; transfers to the target with the original caller's JSL frame still
|
||
; on the stack, so target's RTL returns to the original caller (one
|
||
; frame, no double-RTL).
|
||
;
|
||
; Caller emit sequence in W65816ISelLowering::LowerCall:
|
||
; sta __indirTarget ; store ptr (must precede any A clobber for args)
|
||
; ... arg pushes ...
|
||
; jsl __jsl_indir
|
||
;
|
||
; __indirTarget MUST live at a fixed bank-0 storage slot. On the
|
||
; 65816, JMP (abs) (opcode 0x6C) reads its vector from BANK 0
|
||
; unconditionally — it ignores both PBR and DBR. If __indirTarget
|
||
; were in normal BSS, GS/OS Loader / GNO would relocate the BSS
|
||
; storage into the program's bank (e.g., $01:9180), but the JMP would
|
||
; still read from $00:9180 — wherever, garbage, crash. Pinning it
|
||
; at DP $B8 sidesteps the relocation: DP=0 in our runtime, so DP slot
|
||
; $B8 IS bank-0 byte $00:00B8. Callers store via `sta $B8` (DP form),
|
||
; which writes to the same $00:00B8 regardless of DBR; the trampoline
|
||
; below reads via `jmp ($00B8)` (16-bit abs operand into bank-0
|
||
; indirect). Single-bank assumption on the *target's* code remains
|
||
; (JMP indirect transfers to current PBR), but program data location
|
||
; no longer matters.
|
||
; --------------------------------------------------------------------
|
||
.globl __indirTarget
|
||
__indirTarget = 0xb8
|
||
|
||
.text
|
||
.globl __jsl_indir
|
||
__jsl_indir:
|
||
; PC <- mem[$00:00B8]. Indirect dispatch through the bank-0-
|
||
; anchored function-pointer slot — opcode 0x6C (JMP (abs)).
|
||
jmp (__indirTarget)
|
||
|
||
; --------------------------------------------------------------------
|
||
; __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
|
||
|
||
; --------------------------------------------------------------------
|
||
; __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
|
||
|
||
; --------------------------------------------------------------------
|
||
; __mulhi3 — 16-bit multiply. A * (4,S) -> A.
|
||
; Signed and unsigned share an implementation: only the low 16 bits of
|
||
; the product are returned, which is identical for both. Uses
|
||
; shift-and-add over the multiplier bits.
|
||
; --------------------------------------------------------------------
|
||
.globl __mulhi3
|
||
__mulhi3:
|
||
sta 0xe0 ; multiplier
|
||
lda 0x4, s
|
||
sta 0xe2 ; multiplicand
|
||
stz 0xe4 ; running product = 0
|
||
.Lmul_loop:
|
||
lda 0xe0
|
||
beq .Lmul_done
|
||
lsr a
|
||
sta 0xe0
|
||
bcc .Lmul_skip
|
||
lda 0xe4
|
||
clc
|
||
adc 0xe2
|
||
sta 0xe4
|
||
.Lmul_skip:
|
||
asl 0xe2
|
||
bra .Lmul_loop
|
||
.Lmul_done:
|
||
lda 0xe4
|
||
rtl
|
||
|
||
; --------------------------------------------------------------------
|
||
; __umulhisi3 — unsigned 16x16 -> 32 multiply. A = multiplier (16-bit),
|
||
; (4,s) = multiplicand (16-bit). Returns A:X = 32-bit product (A=lo,
|
||
; X=hi). Used by the i32-mul-of-zext-i16 DAG combine in the W65816
|
||
; backend to avoid the full __mulsi3 (32x32 -> 32) call when the user
|
||
; writes `(u32)a * b` with a/b as u16 (e.g. `unsigned long s += i*i;`).
|
||
; ~16 iterations instead of 16-32 for the equivalent __mulsi3 fast path,
|
||
; AND avoids zext/zero-fill overhead in arg setup.
|
||
;
|
||
; The accumulator and shifted multiplicand both need 32-bit space:
|
||
; $e0/$e1 multiplier (shifted right; tested for add)
|
||
; $e2..$e5 multiplicand (16-bit input, shifts up into the high
|
||
; half over the loop)
|
||
; $e6..$e9 32-bit product accumulator
|
||
; --------------------------------------------------------------------
|
||
.globl __umulhisi3
|
||
__umulhisi3:
|
||
; Fast path: if both args fit in 8 bits, use a quarter-square
|
||
; table for 8x8 → 16 (result hi half is always 0). Identity:
|
||
; a*b = T[a+b] - T[|a-b|] where T[k] = floor(k²/4)
|
||
; 511-entry, 2-byte table = 1022 bytes ROM. Fast path is ~55
|
||
; cyc vs ~150 cyc for the shift-and-add fallback at i ~ 50.
|
||
; Hot in `i*i` patterns where i is a loop counter ≤ 255.
|
||
sta 0xe0 ; save arg0
|
||
ora 0x4, s ; OR with arg1
|
||
and #0xff00 ; isolate hi bytes
|
||
bne .Lumulhisi_slow ; if either hi byte nonzero, fallback
|
||
; Both args fit in 8 bits — quarter-square path.
|
||
lda 0xe0 ; arg0
|
||
clc
|
||
adc 0x4, s ; A = a + b
|
||
asl a ; A = 2*(a+b) — word index
|
||
tax
|
||
lda __umulhisi3_qsq, x ; T[a+b]
|
||
sta 0xe2 ; stash
|
||
lda 0xe0 ; arg0
|
||
sec
|
||
sbc 0x4, s ; A = a - b
|
||
bpl .Lumulhisi_qsq_pos
|
||
; A negative; negate to get |a-b|.
|
||
eor #0xffff
|
||
inc a
|
||
.Lumulhisi_qsq_pos:
|
||
asl a ; A = 2*|a-b|
|
||
tax
|
||
lda 0xe2 ; T[a+b]
|
||
sec
|
||
sbc __umulhisi3_qsq, x ; T[a+b] - T[|a-b|]
|
||
ldx #0 ; result hi half = 0
|
||
rtl
|
||
.Lumulhisi_slow:
|
||
; Multiplier already at $e0 from the fast-path check above.
|
||
lda 0x4, s
|
||
sta 0xe2 ; multiplicand lo in $e2/$e3
|
||
stz 0xe4 ; multiplicand hi (initially 0) in $e4/$e5
|
||
stz 0xe6 ; product lo at $e6/$e7
|
||
stz 0xe8 ; product hi at $e8/$e9
|
||
.Lumulhisi_loop:
|
||
lda 0xe0
|
||
beq .Lumulhisi_done
|
||
lsr a
|
||
sta 0xe0
|
||
bcc .Lumulhisi_skip
|
||
; Add 32-bit multiplicand to 32-bit product.
|
||
clc
|
||
lda 0xe6
|
||
adc 0xe2
|
||
sta 0xe6
|
||
lda 0xe8
|
||
adc 0xe4
|
||
sta 0xe8
|
||
.Lumulhisi_skip:
|
||
; Shift 32-bit multiplicand left by 1.
|
||
asl 0xe2
|
||
rol 0xe4
|
||
bra .Lumulhisi_loop
|
||
.Lumulhisi_done:
|
||
ldx 0xe8
|
||
lda 0xe6
|
||
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
|
||
|
||
; --------------------------------------------------------------------
|
||
; __udivhi3 — A unsigned / (4,S) -> A.
|
||
; Restoring shift-subtract division. Common helper; __umodhi3 reuses
|
||
; the algorithm and returns the remainder instead.
|
||
; Scratch: $e6 = numerator, $e8 = denominator,
|
||
; $ea = quotient, $ec = remainder.
|
||
; --------------------------------------------------------------------
|
||
.globl __udivhi3
|
||
__udivhi3:
|
||
; Public entry: A=dividend, (4,S)=divisor. Set up scratch and
|
||
; call the same JSR-based core used by signed divide.
|
||
sta 0xe6
|
||
lda 0x4, s
|
||
sta 0xe8
|
||
jsr __udivmod_core
|
||
lda 0xea
|
||
rtl
|
||
|
||
.globl __umodhi3
|
||
__umodhi3:
|
||
sta 0xe6
|
||
lda 0x4, s
|
||
sta 0xe8
|
||
jsr __udivmod_core
|
||
lda 0xec
|
||
rtl
|
||
|
||
; --------------------------------------------------------------------
|
||
; __divhi3 / __modhi3 — signed 16-bit divide and modulo. Strategy:
|
||
; - Stash sign of dividend in $ee bit 0 (used by modulo).
|
||
; - Stash result sign of quotient (sign(a) XOR sign(b)) in $ee bit 1
|
||
; (used by divide).
|
||
; - Take absolute values, run the unsigned core, then negate the
|
||
; appropriate result if its sign bit is set.
|
||
; C99: quotient truncates toward zero; remainder takes the sign of the
|
||
; dividend.
|
||
; --------------------------------------------------------------------
|
||
.globl __divhi3
|
||
__divhi3:
|
||
jsr __divmod_setup
|
||
jsr __udivmod_core
|
||
; Quotient is in $ea. Negate if bit 1 of $ee is set.
|
||
pei 0xea
|
||
lda 0xee
|
||
and #0x2
|
||
beq .Ldiv_pos
|
||
pla
|
||
eor #0xffff
|
||
clc
|
||
adc #0x1
|
||
rtl
|
||
.Ldiv_pos:
|
||
pla
|
||
rtl
|
||
|
||
.globl __modhi3
|
||
__modhi3:
|
||
jsr __divmod_setup
|
||
jsr __udivmod_core
|
||
; Remainder is in $ec. Negate if bit 0 of $ee is set (dividend
|
||
; was negative).
|
||
pei 0xec
|
||
lda 0xee
|
||
and #0x1
|
||
beq .Lmod_pos
|
||
pla
|
||
eor #0xffff
|
||
clc
|
||
adc #0x1
|
||
rtl
|
||
.Lmod_pos:
|
||
pla
|
||
rtl
|
||
|
||
; --------------------------------------------------------------------
|
||
; __divmod_setup — common prologue for __divhi3/__modhi3. Reads
|
||
; A=dividend and (4,S)=divisor (the public-entry stack frame is intact
|
||
; because we used JSR not JSL, so (4,S) still points to the user's
|
||
; pushed arg1 relative to the original JSL). Computes |a| -> $e6,
|
||
; |b| -> $e8, and sign tracker -> $ee:
|
||
; bit 0 = 1 if dividend was negative (modulo result sign)
|
||
; bit 1 = 1 if dividend XOR divisor signs differ (quotient sign)
|
||
; Uses JSR/RTS, same bank.
|
||
; --------------------------------------------------------------------
|
||
__divmod_setup:
|
||
; Sign tracker. STZ doesn't touch A — preserves the value
|
||
; we still need below.
|
||
stz 0xee
|
||
; Dividend sign + abs value.
|
||
cmp #0x8000
|
||
bcc .Lset_a_pos
|
||
; Negative: set bits 0 and 1 (dividend sign, result sign so far).
|
||
pha
|
||
lda 0xee
|
||
ora #0x3
|
||
sta 0xee
|
||
pla
|
||
eor #0xffff
|
||
clc
|
||
adc #0x1
|
||
.Lset_a_pos:
|
||
sta 0xe6
|
||
; Divisor sign + abs value. After our JSR (pushed 2 bytes of
|
||
; near-return), the user's arg1 has shifted up by 2 from (4,S)
|
||
; to (6,S).
|
||
lda 0x6, s
|
||
cmp #0x8000
|
||
bcc .Lset_b_pos
|
||
; Negative: flip bit 1 of $ee (XOR with sign of dividend).
|
||
pha
|
||
lda 0xee
|
||
eor #0x2
|
||
sta 0xee
|
||
pla
|
||
eor #0xffff
|
||
clc
|
||
adc #0x1
|
||
.Lset_b_pos:
|
||
sta 0xe8
|
||
rts
|
||
|
||
; --------------------------------------------------------------------
|
||
; __udivmod_core — internal restoring divide. Inputs at $e6/$e8,
|
||
; outputs quotient at $ea, remainder at $ec. JSR/RTS local helper.
|
||
; --------------------------------------------------------------------
|
||
__udivmod_core:
|
||
stz 0xea
|
||
stz 0xec
|
||
ldx #0x10
|
||
.Lcore_loop:
|
||
asl 0xe6
|
||
rol 0xec
|
||
asl 0xea
|
||
lda 0xec
|
||
cmp 0xe8
|
||
bcc .Lcore_skip
|
||
sec
|
||
sbc 0xe8
|
||
sta 0xec
|
||
inc 0xea
|
||
.Lcore_skip:
|
||
dex
|
||
bne .Lcore_loop
|
||
rts
|
||
|
||
; ====================================================================
|
||
; 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. Shift-and-add over 32 bits of the
|
||
; multiplier. Result = (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).
|
||
; --------------------------------------------------------------------
|
||
.globl __mulsi3
|
||
__mulsi3:
|
||
; Stash a (multiplier) into $e0/$e2.
|
||
sta 0xe0
|
||
stx 0xe2
|
||
; Stash b (multiplicand) into $e4/$e6.
|
||
lda 0x4, s
|
||
sta 0xe4
|
||
lda 0x6, s
|
||
sta 0xe6
|
||
; Clear running product at $e8/$ea.
|
||
stz 0xe8
|
||
stz 0xea
|
||
; Asymmetric-size swap: the loop iterates based on the multiplier's
|
||
; bit width. If a is large (a_hi != 0) but b is small (b_hi == 0),
|
||
; swap so the small one becomes the multiplier — the early-exit
|
||
; check (lda 0xe0; beq done) then fires sooner. Saves a lot for
|
||
; djb2-style `h * 33` patterns where h grows but the constant
|
||
; multiplier is small. Cost: ~12 cyc unconditional check; benefit:
|
||
; ~10 cyc per saved iter × ~10 iters = 100 cyc.
|
||
lda 0xe2
|
||
beq .Lmulsi_no_swap ; a_hi == 0, no need to swap (or already covered by u16 path below)
|
||
lda 0xe6
|
||
bne .Lmulsi_no_swap ; b_hi != 0, both large, no benefit
|
||
; Swap (a_lo, a_hi) ↔ (b_lo, b_hi). Both are at DP; XOR-swap or
|
||
; load/store; we use load/store with A scratch since the values are
|
||
; 16 bits each.
|
||
lda 0xe0
|
||
ldx 0xe4
|
||
stx 0xe0
|
||
sta 0xe4
|
||
lda 0xe2
|
||
ldx 0xe6
|
||
stx 0xe2
|
||
sta 0xe6
|
||
.Lmulsi_no_swap:
|
||
; Fast path: if multiplier's high half ($e2) is 0, we only
|
||
; need 16 loop iterations AND we can drop the multiplier-hi
|
||
; shift step entirely (lsr $e2 + bcc + ora #$8000) — that step
|
||
; only ever fires when hi has bits to shift out, which it
|
||
; doesn't here. Saves ~8 cyc/iter × 16 iters = ~128 cyc/call
|
||
; vs the generic 16-iter path. Common in C code where both
|
||
; source operands are zext'd from i16 (e.g. `i*i` with i a
|
||
; `unsigned short`) — sumOfSquares benchmark hits this on every
|
||
; iteration.
|
||
lda 0xe2
|
||
bne .Lmulsi_full
|
||
; Quarter-square fast path: when BOTH args fit in 8 bits (e2==0
|
||
; already verified, e6==0, e0<256, e4<256), use the same T[k]=k^2/4
|
||
; table that __umulhisi3 uses to compute a*b = T[a+b] - T[|a-b|]
|
||
; in ~50 cyc vs the u16 loop's ~150-250 cyc for small multipliers.
|
||
; Common when dotProduct etc. sign-extend small i16 values to i32.
|
||
lda 0xe6
|
||
bne .Lmulsi_u16_entry
|
||
lda 0xe0
|
||
ora 0xe4
|
||
and #0xff00
|
||
bne .Lmulsi_u16_entry
|
||
; T[a+b] -> $e8 (product lo).
|
||
lda 0xe0
|
||
clc
|
||
adc 0xe4
|
||
asl a
|
||
tax
|
||
lda __umulhisi3_qsq, x
|
||
sta 0xe8
|
||
; |a-b| as word index.
|
||
lda 0xe0
|
||
sec
|
||
sbc 0xe4
|
||
bpl .Lmulsi_qsq_pos
|
||
eor #0xffff
|
||
inc a
|
||
.Lmulsi_qsq_pos:
|
||
asl a
|
||
tax
|
||
lda 0xe8
|
||
sec
|
||
sbc __umulhisi3_qsq, x
|
||
ldx #0
|
||
rtl
|
||
.Lmulsi_u16_entry:
|
||
ldy #0x10
|
||
.Lmulsi_u16_loop:
|
||
; Shift multiplier right; bit-out tested for add. Bottom of loop
|
||
; checks multiplier==0 BEFORE the multiplicand shift, so on the
|
||
; iter that clears the multiplier we save 14 cyc of unused
|
||
; asl/rol on the multiplicand. Combined with the early-exit
|
||
; saves ~30 cyc/call on small multipliers (1-50 range typical
|
||
; for sumOfSquares).
|
||
lda 0xe0
|
||
lsr a
|
||
sta 0xe0
|
||
bcc .Lmulsi_u16_noadd
|
||
clc
|
||
lda 0xe8
|
||
adc 0xe4
|
||
sta 0xe8
|
||
lda 0xea
|
||
adc 0xe6
|
||
sta 0xea
|
||
.Lmulsi_u16_noadd:
|
||
lda 0xe0
|
||
beq .Lmulsi_done
|
||
asl 0xe4
|
||
rol 0xe6
|
||
dey
|
||
bne .Lmulsi_u16_loop
|
||
.Lmulsi_done:
|
||
ldx 0xea
|
||
lda 0xe8
|
||
rtl
|
||
.Lmulsi_full:
|
||
ldy #0x20
|
||
.Lmulsi_loop:
|
||
; Test bit 0 of multiplier (lo word).
|
||
lda 0xe0
|
||
lsr a
|
||
sta 0xe0
|
||
bcc .Lmulsi_noadd
|
||
; Add multiplicand to product (32-bit).
|
||
clc
|
||
lda 0xe8
|
||
adc 0xe4
|
||
sta 0xe8
|
||
lda 0xea
|
||
adc 0xe6
|
||
sta 0xea
|
||
.Lmulsi_noadd:
|
||
; Stream multiplier hi's LSB into lo's MSB so subsequent iters
|
||
; test bits 16..31 via the same lo-bit test.
|
||
lsr 0xe2
|
||
bcc .Lmulsi_no_borrow
|
||
lda 0xe0
|
||
ora #0x8000
|
||
sta 0xe0
|
||
.Lmulsi_no_borrow:
|
||
; Early exit: if BOTH halves of multiplier are 0, no more bits
|
||
; remain. Saves the multiplicand shift on the terminating iter
|
||
; AND the rest of the loop on small multipliers.
|
||
lda 0xe0
|
||
bne .Lmulsi_shift_mc
|
||
lda 0xe2
|
||
beq .Lmulsi_full_done
|
||
.Lmulsi_shift_mc:
|
||
; Shift multiplicand left for the next iter's potential add.
|
||
asl 0xe4
|
||
rol 0xe6
|
||
dey
|
||
bne .Lmulsi_loop
|
||
.Lmulsi_full_done:
|
||
; Result is in $e8 (lo) / $ea (hi).
|
||
ldx 0xea
|
||
lda 0xe8
|
||
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. Inputs in
|
||
; $e0/$e2 (numerator) and $e4/$e6 (denominator); outputs quotient in
|
||
; $e8/$ea and remainder in $ec/$ee. 32-iteration restoring divide.
|
||
; JSR/RTS local helper.
|
||
; --------------------------------------------------------------------
|
||
__udivmodsi_core:
|
||
stz 0xe8
|
||
stz 0xea
|
||
stz 0xec
|
||
stz 0xee ; was `sta 0xee` — A held b_hi at entry,
|
||
; so for divisors > 0xFFFF (b_hi != 0)
|
||
; the remainder started contaminated and
|
||
; produced wrong quotients. Bug masked
|
||
; for b_hi==0 (e.g. /60, /1000) because
|
||
; sta-of-zero == stz. Caught by /86400.
|
||
ldy #0x20
|
||
.Lcoresi_loop:
|
||
; Shift numerator left through remainder.
|
||
asl 0xe0
|
||
rol 0xe2
|
||
rol 0xec
|
||
rol 0xee
|
||
; Shift quotient left.
|
||
asl 0xe8
|
||
rol 0xea
|
||
; Compare remainder to denominator (32-bit).
|
||
lda 0xee
|
||
cmp 0xe6
|
||
bcc .Lcoresi_skip
|
||
bne .Lcoresi_take
|
||
lda 0xec
|
||
cmp 0xe4
|
||
bcc .Lcoresi_skip
|
||
.Lcoresi_take:
|
||
; Remainder >= denominator: subtract and set quotient bit 0.
|
||
sec
|
||
lda 0xec
|
||
sbc 0xe4
|
||
sta 0xec
|
||
lda 0xee
|
||
sbc 0xe6
|
||
sta 0xee
|
||
inc 0xe8
|
||
.Lcoresi_skip:
|
||
dey
|
||
bne .Lcoresi_loop
|
||
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 0xea
|
||
lda 0xe8
|
||
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 0xee
|
||
lda 0xec
|
||
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 $e8/$ea. Negate if bit 1 of $f0 is set.
|
||
lda 0xf0
|
||
and #0x2
|
||
beq .Ldivsi_pos
|
||
; 32-bit two's complement of quotient.
|
||
lda 0xe8
|
||
eor #0xffff
|
||
clc
|
||
adc #0x1
|
||
sta 0xe8
|
||
lda 0xea
|
||
eor #0xffff
|
||
adc #0x0
|
||
sta 0xea
|
||
.Ldivsi_pos:
|
||
ldx 0xea
|
||
lda 0xe8
|
||
rtl
|
||
|
||
.globl __modsi3
|
||
__modsi3:
|
||
jsr __divmodsi_setup
|
||
jsr __udivmodsi_core
|
||
; Remainder at $ec/$ee. Negate if bit 0 of $f0 set (dividend
|
||
; was negative — C99 remainder takes dividend's sign).
|
||
lda 0xf0
|
||
and #0x1
|
||
beq .Lmodsi_pos
|
||
lda 0xec
|
||
eor #0xffff
|
||
clc
|
||
adc #0x1
|
||
sta 0xec
|
||
lda 0xee
|
||
eor #0xffff
|
||
adc #0x0
|
||
sta 0xee
|
||
.Lmodsi_pos:
|
||
ldx 0xee
|
||
lda 0xec
|
||
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 a<b, 1 if a==b,
|
||
; 2 if a>b (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
|
||
|