65816-llvm-mos/runtime/src/libgcc.s
Scott Duensing da095402ec Updated
2026-06-02 23:17:57 -05:00

1487 lines
41 KiB
ArmAsm
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

; 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