; 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 (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

; --------------------------------------------------------------------
; __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

; --------------------------------------------------------------------
; __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

