; ARM multiprecision integer arithmetic routines for pgp
; (c) 1997 Philipp Hullmann <hullmann@nano.fkp.uni-hannover.de>

	GET	s.arm_regs

	AREA	|C$$code|, CODE, READONLY
	IMPORT	|x$stack_overflow|
	IMPORT	global_precision
	IMPORT	munit_prec

global_precision_root
	DCD	global_precision
munit_precision_root
	DCD	munit_prec


; --------------------------------------------------------------------
; Copy a multi-precision number
; unit: 32 bit
; void arm_move(unitptr dst, unitptr src);

	EXPORT	arm_move

amvn_s	DCB 	"arm_move", 0
	ALIGN	4
amvn_e	DCD	&ff000000 + amvn_e - amvn_s

arm_move
	LDR	ip, global_precision_root	; initialize loop counter
	LDR	a3, [ip]
	CMP	a3, #0
	BEQ	arm_move_end
arm_move_loop
	LDR	a4, [a2], #4
	STR	a4, [a1], #4
	SUBS	a3, a3, #1
	BNE	arm_move_loop
arm_move_end
	MOVS	pc, lr		; return


; --------------------------------------------------------------------
; Fill array of 32-bit units with 0
; void arm_unitfill0(void *r, unsigned int elements);

	EXPORT	arm_unitfill0

auf0n_s	DCB 	"arm_unitfill0", 0
	ALIGN	4
auf0n_e	DCD	&ff000000 + auf0n_e - auf0n_s

arm_unitfill0
	MOV	a3, #0		; this will be written to the array
	CMP	a2, #0		; test for the trivial case
	BEQ	|arm_unitfill0_end|
arm_unitfill0_loop
	STR	a3, [a1], #4	; write contents of a3, increment pointer
	SUBS	a2, a2, #1	; decrement counter
	BNE	|arm_unitfill0_loop|
arm_unitfill0_end
	MOVS	pc, lr		; return


; --------------------------------------------------------------------
; Multiprecision add with carry
; unit: 32bit
; boolean arm_addc(unitptr r1, unitptr r2, boolean carry)

	EXPORT	arm_addc

addcn_s	DCB 	"arm_addc", 0
	ALIGN	4
addcn_e	DCD	&ff000000 + addcn_e - addcn_s

arm_addc
	MOV	ip, sp		; ip now points to the fifth argument
	STMFD	sp!, {v1, v2, fp, ip, lr, pc}
	SUB	fp, ip, #4	; fp points to the saved pc
	CMP	sp, sl 		; Test for stack overflow
	BLLT	|x$stack_overflow|

	LDR	ip, global_precision_root	; initialize loop counter
	LDR	a4, [ip]

	CMP	a4, #0		; test for the trivial case
	BEQ	arm_addc_end

arm_addc_loop
	LDR	v1, [a1]	; load unit of first argument
	LDR	v2, [a2], #4	; load unit of second argument,
				; increment pointer
	MOV	ip, pc		; get processor status
	CMP	a3, #0		; check the carry variable
	BICEQ	ip, ip, #1<<29  ; clear carry if a3 == 0
	ORRNE	ip, ip, #1<<29	; set carry otherwise
	TEQP	ip, #0		; update the real carry flag
	ADCS	v1, v1, v2	; add with carry
	MOVCS	a3, #1		; save carry flag
	MOVCC	a3, #0
	STR	v1, [a1], #4	; store the result, overwrite argument 1 and
				; increment pointer
	SUBS	a4, a4, #1	; decrement counter
	BNE	arm_addc_loop	; loop if counter non-zero

arm_addc_end
	MOV	a1, a3		; return the final carry bit
	LDMEA	fp, {v1, v2, fp, sp, pc}^


; --------------------------------------------------------------------
; Multiprecision subtract with borrow
; unit: 32bit
; boolean arm_subb(unitptr r1, unitptr r2, boolean borrow)

	EXPORT	arm_subb

subbn_s	DCB 	"arm_subb", 0
	ALIGN	4
subbn_e	DCD	&ff000000 + subbn_e - subbn_s

arm_subb
	MOV	ip, sp		; ip now points to the fifth argument
	STMFD	sp!, {v1, v2, fp, ip, lr, pc}
	SUB	fp, ip, #4	; fp points to the saved pc
	CMP	sp, sl 		; Test for stack overflow
	BLLT	|x$stack_overflow|

	LDR	ip, global_precision_root	; initialize loop counter
	LDR	a4, [ip]
	CMP	a4, #0		; check for the trivial case
	BEQ	arm_subb_end

arm_subb_loop
	LDR	v1, [a1]	; v1: unit of first argument
	LDR	v2, [a2], #4	; v2: unit of second argument; increment a2

	SUB	ip, v1, v2	; subtract the two units

	CMP	a3, #0		; test for borrow
	BEQ	arm_subb_no_borrow

	SUB	ip, ip, #1	; subtract borrow flag
	MOV	a3, #0		; clear old borrow flag
	CMP	v1, v2
	MOVLS	a3, #1		; set new borrow flag if required
	B	arm_subb_store
arm_subb_no_borrow
	MOV	a3, #0		; clear old borrow flag
	CMP	v1, v2
	MOVCC	a3, #1		; set new borrow flag if required
arm_subb_store
	STR	ip, [a1], #4	; store the result, overwrite arg 1, inc a1
	SUBS	a4, a4, #1	; decrement counter
	BNE	arm_subb_loop	; loop if counter non-zero

arm_subb_end
	MOV	a1, a3		; return final borrow flag
	LDMEA	fp, {v1, v2, fp, sp, pc}^


; --------------------------------------------------------------------
; Multiprecision rotate left 1 bit with carry
; unit: 32bit
; boolean arm_rotate_left(unitptr r1, boolean carry)

	EXPORT	arm_rotate_left

rlftn_s	DCB 	"arm_rotate_left", 0
	ALIGN	4
rlftn_e	DCD	&ff000000 + rlftn_e - rlftn_s

arm_rotate_left
	LDR	ip, global_precision_root
	LDR	a4, [ip]	; initialize loop counter

	CMP	a4, #0		; test for trivial case
	BEQ	arm_rotate_left_end

arm_rotate_left_loop
	LDR	ip, [a1]	; load unit of argument

	MOVS	a3, ip, LSL #1	; shift one step, leftmost bit in carry flag
	MOVCS	ip, #1		; store the leftmost bit in ip
	MOVCC	ip, #0

	CMP	a2, #0		; check for carry from previous iteration ...
	ORRNE	a3, a3, #1	; ... and set the first bit accordingly.
	BICEQ	a3, a3, #1

	STR	a3, [a1], #4	; store result, increment pointer
	MOV	a2, ip		; move carry to a2

	SUBS	a4, a4, #1	; decrement counter
	BNE	arm_rotate_left_loop	; loop if counter non-zero

arm_rotate_left_end
	MOV	a1, a2		; return final carry bit
	MOVS	pc, lr


; --------------------------------------------------------------------
; Compare multiprecision integers:
;	-1 iff *r1 <  *r2
;	 0 iff *r1 == *r2
;	+1 iff *r1 >  *r2
; unit: 32bit
; boolean arm_compare(unitptr r1, unitptr r2)

	EXPORT	arm_compare

cmpn_s	DCB 	"arm_compare", 0
	ALIGN	4
cmpn_e	DCD	&ff000000 + cmpn_e - cmpn_s

arm_compare
	MOV	ip, sp		; ip now points to the fifth argument
	STMFD	sp!, {v1, fp, ip, lr, pc}
	SUB	fp, ip, #4	; fp points to the saved pc
	CMP	sp, sl 		; Test for stack overflow
	BLLT	|x$stack_overflow|

	LDR	ip, global_precision_root
	LDR	a4, [ip] 	; a4: number of units to compare
	MOV	a3, a4, ASL #2	; number of bytes to compare
	SUB	a3, a3, #4	; offset between LSW and MSW
	MOV	a4, a1		; a4: argument 1 start address
	ADD	a1, a1, a3	; address of MSW, argument 1
	ADD	a2, a2, a3	; address of MSW, argument 2
	MOV	ip, #0		; use ip for result
arm_compare_loop
	CMP	a1, a4
	BLT	arm_compare_end	; no more units to compare
	LDR	v1, [a1], #-4	; load unit of first operand
	LDR	a3, [a2], #-4	; load unit of second operand
	CMP	v1, a3		; compare the two
	MVNCC	ip, #1		; unsigned lower, return -1
	MOVHI	ip, #1		; unsigned higher, return 1
	BEQ	arm_compare_loop	; if equal, continue comparison
arm_compare_end
	MOV 	a1, ip		; return result
	LDMEA	fp, {v1, fp, sp, pc}^


; --------------------------------------------------------------------
; Multiply vector with unit
; MULTUNIT: 32bit
; void arm_smul(MULTUNIT *prod, MULTUNIT *multiplicand,
;		MULTUNIT multiplier)

	EXPORT	arm_smul

smuln_s	DCB 	"arm_smul", 0
	ALIGN	4
smuln_e	DCD	&ff000000 + smuln_e - smuln_s

arm_smul
	MOV	ip, sp		; ip now points to the fifth argument
	STMFD	sp!, {v1, v2, v3, v4, v5, fp, ip, lr, pc}
	SUB	fp, ip, #4	; fp points to the saved pc

	CMP	sp, sl 		; Test for stack overflow
	BLLT	|x$stack_overflow|

	LDR	ip, munit_precision_root
	LDR	a4, [ip]		; load number of munits to multiply
	CMP	a4, #0			; catch the trivial case
	BEQ	arm_smul_end

	MOV	v1, a3, LSL #16		; lower half of multiplier
	MOV	v1, v1, LSR #16
	MOV	v2, a3, LSR #16		; higher half of multiplier

	; registers:
	; a1: pointer to product
	; a2: pointer to multiplicand
	; a3: scratch
	; a4: precision (counter)
	; v1: multiplier, lower 16 bits
	; v2: multiplier, higher 16 bits
	; v3: multiplicand, lower 16 bits
	; v4: multiplicand, higher 16 bits
	; v5: scratch
	; ip: scratch

arm_smul_loop
	; load multiplicand
	LDR	a3, [a2], #4		; load multiplicand, increment a2
	MOV	v3, a3, LSL #16		; lower half of multiplicand
	MOV	v3, v3, LSR #16
	MOV	v4, a3, LSR #16		; upper half of multiplicand

	; multiply the lower halfwords
	MUL	a3, v1, v3
	LDR	v5, [a1]		; load product accumulator
	ADDS	v5, v5, a3		; add to product
	STR	v5, [a1]		; store the result
        MOVCS	v5, a1
	BLCS	arm_smul_handle_carry

	; multiply the upper halfwords
	MUL	a3, v2, v4
	LDR	v5, [a1, #4]
	ADDS	v5, v5, a3
	STR	v5, [a1, #4]
        ADDCS	v5, a1, #4
	BLCS	arm_smul_handle_carry

	; lower halfword of multiplier times upper halfword of multiplicand
	MUL	a3, v1, v4
	MOV	v5, a3, LSL #16	; lower 16 bits of product in
				; upper half of register
	LDR	ip, [a1]	; load product accumulator, lower word
	ADDS	ip, v5, ip	; add to accumulator
	STR	ip, [a1]	; store result
	MOV	v5, a3, LSR #16	; upper halfword of result in lower half of
				; register
	ADDCS	v5, v5, #1	; add carry if required
	LDR	ip, [a1, #4]	; load product accumulator, higher word
	ADDS	ip, v5, ip	; add to accumulator
	STR	ip, [a1, #4]	; store result
        ADDCS	v5, a1, #4
	BLCS	arm_smul_handle_carry

	; upper halfword of multiplier times lower halfword of multiplicand
	MUL	a3, v2, v3
	MOV	v5, a3, LSL #16	; lower 16 bits of product in upper half
				; of register
	LDR	ip, [a1]	; load product accumulator, lower word
	ADDS	ip, v5, ip	; add to accumulator
	STR	ip, [a1]	; store result
	MOV	v5, a3, LSR #16	; upper halfword of result in lower half
				; of register
	ADDCS	v5, v5, #1	; add carry if required
	LDR	ip, [a1, #4]	; load product accumulator, higher word
	ADDS	ip, v5, ip	; add to accumulator
	STR	ip, [a1, #4]!	; store result, increment product pointer
        MOVCS	v5, a1
	BLCS	arm_smul_handle_carry

	SUBS	a4, a4, #1	; decrement counter
	BNE	arm_smul_loop	; loop if counter non-zero

arm_smul_end
	LDMEA	fp, {v1, v2, v3, v4, v5, fp, sp, pc}^

; mini-subroutine to handle carry. pointer to product buffer in v5,
; corrupts v5 and a3. this will crash if the product buffer
; is initialized to something ridiculous.
arm_smul_handle_carry
	LDR	a3, [v5, #4]
	ADDS	a3, a3, #1
	STR	a3, [v5, #4]!
	BCS	arm_smul_handle_carry
	MOVS	pc, lr


; --------------------------------------------------------------------
; Multiple precision multiplication.
; unit: 32bit
; void arm_dmul(unitptr prod, unitptr multiplicand, unitptr multiplier)

	EXPORT	arm_dmul

dmuln_s	DCB 	"arm_dmul", 0
	ALIGN	4
dmuln_e	DCD	&ff000000 + dmuln_e - dmuln_s

arm_dmul
	MOV	ip, sp		; ip now points to the fifth argument
	STMFD	sp!, {v1, v2, v3, v4, fp, ip, lr, pc}
	SUB	fp, ip, #4	; fp points to the saved pc
	CMP	sp, sl 		; Test for stack overflow
	BLLT	|x$stack_overflow|

	MOV	v1, a2				; v1: p_multiplicand
	MOV	v2, a3				; v2: p_multiplier
	MOV	v3, a1				; v3: prodp

	LDR	ip, global_precision_root	; load global_precision
	LDR	v4, [ip]			; v4: loop counter
	LDR	ip, munit_precision_root	; set munit_prec
	STR	v4, [ip]
	MOV	a2, v4, ASL #1
	BL	arm_unitfill0			; pre-zero product buffer

	CMP	v4, #0
	BEQ	arm_dmul_end

arm_dmul_loop
	MOV	a1, v3
	MOV	a2, v1
	LDR	a3, [v2], #4
	BL	arm_smul
	ADD	v3, v3, #4
	SUBS	v4, v4, #1
	BNE	arm_dmul_loop

arm_dmul_end
	LDMEA	fp, {v1, v2, v3, v4, fp, sp, pc}^


	END
