; SF format is: | |

; | |

; [sign] 1.[23bits] E[8bits(n-127)] | |

; | |

; SEEEEEEE Emmmmmmm mmmmmmmm mmmmmmmm | |

; | |

; [A+0] mmmmmmmm | |

; [A+1] mmmmmmmm | |

; [A+2] Emmmmmmm | |

; [A+3] SEEEEEEE | |

; | |

; Special values (xxx != 0): | |

; | |

; s1111111 10000000 00000000 00000000 infinity | |

; s1111111 1xxxxxxx xxxxxxxx xxxxxxxx NaN | |

; s0000000 00000000 00000000 00000000 zero | |

; s0000000 0xxxxxxx xxxxxxxx xxxxxxxx denormals | |

; | |

; Note that CMPtype is "signed char" for rl78 | |

; | |

#include "vregs.h" | |

#define Z PSW.6 | |

START_FUNC ___negsf2 | |

;; Negate the floating point value. | |

;; Input at [SP+4]..[SP+7]. | |

;; Output to R8..R11. | |

movw ax, [SP+4] | |

movw r8, ax | |

movw ax, [SP+6] | |

xor a, #0x80 | |

movw r10, ax | |

ret | |

END_FUNC ___negsf2 | |

;; ------------------internal functions used by later code -------------- | |

START_FUNC __int_isnan | |

;; [HL] points to value, returns Z if it's a NaN | |

mov a, [hl+2] | |

and a, #0x80 | |

mov x, a | |

mov a, [hl+3] | |

and a, #0x7f | |

cmpw ax, #0x7f80 | |

skz | |

ret ; return NZ if not NaN | |

mov a, [hl+2] | |

and a, #0x7f | |

or a, [hl+1] | |

or a, [hl] | |

bnz $1f | |

clr1 Z ; Z, normal | |

ret | |

1: | |

set1 Z ; nan | |

ret | |

END_FUNC __int_isnan | |

START_FUNC __int_eithernan | |

;; call from toplevel functions, returns Z if either number is a NaN, | |

;; or NZ if both are OK. | |

movw ax, sp | |

addw ax, #8 | |

movw hl, ax | |

call $!__int_isnan | |

bz $1f | |

movw ax, sp | |

addw ax, #12 | |

movw hl, ax | |

call $!__int_isnan | |

1: | |

ret | |

END_FUNC __int_eithernan | |

START_FUNC __int_iszero | |

;; [HL] points to value, returns Z if it's zero | |

mov a, [hl+3] | |

and a, #0x7f | |

or a, [hl+2] | |

or a, [hl+1] | |

or a, [hl] | |

ret | |

END_FUNC __int_iszero | |

START_FUNC __int_cmpsf | |

;; This is always called from some other function here, | |

;; so the stack offsets are adjusted accordingly. | |

;; X [SP+8] <=> Y [SP+12] : <a> <=> 0 | |

movw ax, sp | |

addw ax, #8 | |

movw hl, ax | |

call $!__int_iszero | |

bnz $1f | |

movw ax, sp | |

addw ax, #12 | |

movw hl, ax | |

call $!__int_iszero | |

bnz $2f | |

;; At this point, both args are zero. | |

mov a, #0 | |

ret | |

2: | |

movw ax, sp | |

addw ax, #8 | |

movw hl, ax | |

1: | |

;; At least one arg is non-zero so we can just compare magnitudes. | |

;; Args are [HL] and [HL+4]. | |

mov a, [HL+3] | |

xor a, [HL+7] | |

mov1 cy, a.7 | |

bnc $1f | |

mov a, [HL+3] | |

sar a, 7 | |

or a, #1 | |

ret | |

1: ;; Signs the same, compare magnitude. It's safe to lump | |

;; the sign bits, exponent, and mantissa together here, since they're | |

;; stored in the right sequence. | |

movw ax, [HL+2] | |

cmpw ax, [HL+6] | |

bc $ybig_cmpsf ; branch if X < Y | |

bnz $xbig_cmpsf ; branch if X > Y | |

movw ax, [HL] | |

cmpw ax, [HL+4] | |

bc $ybig_cmpsf ; branch if X < Y | |

bnz $xbig_cmpsf ; branch if X > Y | |

mov a, #0 | |

ret | |

xbig_cmpsf: ; |X| > |Y| so return A = 1 if pos, 0xff if neg | |

mov a, [HL+3] | |

sar a, 7 | |

or a, #1 | |

ret | |

ybig_cmpsf: ; |X| < |Y| so return A = 0xff if pos, 1 if neg | |

mov a, [HL+3] | |

xor a, #0x80 | |

sar a, 7 | |

or a, #1 | |

ret | |

END_FUNC __int_cmpsf | |

;; ---------------------------------------------------------- | |

START_FUNC ___cmpsf2 | |

;; This functions calculates "A <=> B". That is, if A is less than B | |

;; they return -1, if A is greater than B, they return 1, and if A | |

;; and B are equal they return 0. If either argument is NaN the | |

;; behaviour is undefined. | |

;; Input at [SP+4]..[SP+7]. | |

;; Output to R8..R9. | |

call $!__int_eithernan | |

bnz $1f | |

movw r8, #1 | |

ret | |

1: | |

call $!__int_cmpsf | |

mov r8, a | |

sar a, 7 | |

mov r9, a | |

ret | |

END_FUNC ___cmpsf2 | |

;; ---------------------------------------------------------- | |

;; These functions are all basically the same as ___cmpsf2 | |

;; except that they define how they handle NaNs. | |

START_FUNC ___eqsf2 | |

;; Returns zero iff neither argument is NaN | |

;; and both arguments are equal. | |

START_ANOTHER_FUNC ___nesf2 | |

;; Returns non-zero iff either argument is NaN or the arguments are | |

;; unequal. Effectively __nesf2 is the same as __eqsf2 | |

START_ANOTHER_FUNC ___lesf2 | |

;; Returns a value less than or equal to zero if neither | |

;; argument is NaN, and the first is less than or equal to the second. | |

START_ANOTHER_FUNC ___ltsf2 | |

;; Returns a value less than zero if neither argument is | |

;; NaN, and the first is strictly less than the second. | |

;; Input at [SP+4]..[SP+7]. | |

;; Output to R8. | |

mov r8, #1 | |

;;; Fall through | |

START_ANOTHER_FUNC __int_cmp_common | |

call $!__int_eithernan | |

sknz | |

;; return value (pre-filled-in below) for "either is nan" | |

ret | |

call $!__int_cmpsf | |

mov r8, a | |

ret | |

END_ANOTHER_FUNC __int_cmp_common | |

END_ANOTHER_FUNC ___ltsf2 | |

END_ANOTHER_FUNC ___lesf2 | |

END_ANOTHER_FUNC ___nesf2 | |

END_FUNC ___eqsf2 | |

START_FUNC ___gesf2 | |

;; Returns a value greater than or equal to zero if neither argument | |

;; is a NaN and the first is greater than or equal to the second. | |

START_ANOTHER_FUNC ___gtsf2 | |

;; Returns a value greater than zero if neither argument | |

;; is NaN, and the first is strictly greater than the second. | |

mov r8, #0xffff | |

br $__int_cmp_common | |

END_ANOTHER_FUNC ___gtsf2 | |

END_FUNC ___gesf2 | |

;; ---------------------------------------------------------- | |

START_FUNC ___unordsf2 | |

;; Returns a nonzero value if either argument is NaN, otherwise 0. | |

call $!__int_eithernan | |

movw r8, #0 | |

sknz ; this is from the call, not the movw | |

movw r8, #1 | |

ret | |

END_FUNC ___unordsf2 | |

;; ---------------------------------------------------------- | |

START_FUNC ___fixsfsi | |

;; Converts its floating point argument into a signed long, | |

;; rounding toward zero. | |

;; The behaviour with NaNs and Infinities is not well defined. | |

;; We choose to return 0 for NaNs, -INTMAX for -inf and INTMAX for +inf. | |

;; This matches the behaviour of the C function in libgcc2.c. | |

;; Input at [SP+4]..[SP+7], result is in (lsb) R8..R11 (msb). | |

;; Special case handling for infinities as __fixunssfsi | |

;; will not give us the values that we want. | |

movw ax, sp | |

addw ax, #4 | |

movw hl, ax | |

call !!__int_isinf | |

bnz $1f | |

mov a, [SP+7] | |

bt a.7, $2f | |

;; +inf | |

movw r8, #-1 | |

movw r10, #0x7fff | |

ret | |

;; -inf | |

2: mov r8, #0 | |

mov r10, #0x8000 | |

ret | |

;; Load the value into r10:r11:X:A | |

1: movw ax, [SP+4] | |

movw r10, ax | |

movw ax, [SP+6] | |

;; If the value is positive we can just use __fixunssfsi | |

bf a.7, $__int_fixunssfsi | |

;; Otherwise we negate the value, call __fixunssfsi and | |

;; then negate its result. | |

clr1 a.7 | |

call $!__int_fixunssfsi | |

movw ax, #0 | |

subw ax, r8 | |

movw r8, ax | |

movw ax, #0 | |

sknc | |

decw ax | |

subw ax, r10 | |

movw r10, ax | |

;; Check for a positive result (which should only happen when | |

;; __fixunssfsi returns UINTMAX or 0). In such cases just return 0. | |

mov a, r11 | |

bt a.7, $1f | |

movw r10,#0x0 | |

movw r8, #0x0 | |

1: ret | |

END_FUNC ___fixsfsi | |

START_FUNC ___fixunssfsi | |

;; Converts its floating point argument into an unsigned long | |

;; rounding towards zero. Negative arguments all become zero. | |

;; We choose to return 0 for NaNs and -inf, but UINTMAX for +inf. | |

;; This matches the behaviour of the C function in libgcc2.c. | |

;; Input at [SP+4]..[SP+7], result is in (lsb) R8..R11 (msb) | |

;; Get the input value. | |

movw ax, [SP+4] | |

movw r10, ax | |

movw ax, [SP+6] | |

;; Fall through into the internal function. | |

.global __int_fixunssfsi | |

__int_fixunssfsi: | |

;; Input in (lsb) r10.r11.x.a (msb). | |

;; Test for a negative input. We shift the other bits at the | |

;; same time so that A ends up holding the whole exponent: | |

;; | |

;; before: | |

;; SEEEEEEE EMMMMMMM MMMMMMMM MMMMMMMM | |

;; A X R11 R10 | |

;; | |

;; after: | |

;; EEEEEEEE MMMMMMM0 MMMMMMMM MMMMMMMM | |

;; A X R11 R10 | |

shlw ax, 1 | |

bnc $1f | |

;; Return zero. | |

2: movw r8, #0 | |

movw r10, #0 | |

ret | |

;; An exponent of -1 is either a NaN or infinity. | |

1: cmp a, #-1 | |

bnz $3f | |

;; For NaN we return 0. For infinity we return UINTMAX. | |

mov a, x | |

or a, r10 | |

or a, r11 | |

cmp0 a | |

bnz $2b | |

6: movw r8, #-1 ; -1 => UINT_MAX | |

movw r10, #-1 | |

ret | |

;; If the exponent is negative the value is < 1 and so the | |

;; converted value is 0. Note we must allow for the bias | |

;; applied to the exponent. Thus a value of 127 in the | |

;; EEEEEEEE bits actually represents an exponent of 0, whilst | |

;; a value less than 127 actually represents a negative exponent. | |

;; Also if the EEEEEEEE bits are all zero then this represents | |

;; either a denormal value or 0.0. Either way for these values | |

;; we return 0. | |

3: sub a, #127 | |

bc $2b | |

;; A now holds the bias adjusted exponent, which is known to be >= 0. | |

;; If the exponent is > 31 then the conversion will overflow. | |

cmp a, #32 | |

bnc $6b | |

4: | |

;; Save the exponent in H. We increment it by one because we want | |

;; to be sure that the loop below will always execute at least once. | |

inc a | |

mov h, a | |

;; Get the top 24 bits of the mantissa into A:X:R10 | |

;; Include the implicit 1-bit that is inherent in the IEEE fp format. | |

;; | |

;; before: | |

;; EEEEEEEE MMMMMMM0 MMMMMMMM MMMMMMMM | |

;; H X R11 R10 | |

;; after: | |

;; EEEEEEEE 1MMMMMMM MMMMMMMM MMMMMMMM | |

;; H A X R10 | |

mov a, r11 | |

xch a, x | |

shr a, 1 | |

set1 a.7 | |

;; Clear B:C:R12:R13 | |

movw bc, #0 | |

movw r12, #0 | |

;; Shift bits from the mantissa (A:X:R10) into (B:C:R12:R13), | |

;; decrementing the exponent as we go. | |

;; before: | |

;; MMMMMMMM MMMMMMMM MMMMMMMM xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | |

;; A X R10 B C R12 R13 | |

;; first iter: | |

;; MMMMMMMM MMMMMMMM MMMMMMM0 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxM | |

;; A X R10 B C R12 R13 | |

;; second iter: | |

;; MMMMMMMM MMMMMMMM MMMMMM00 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxMM | |

;; A X R10 B C R12 R13 | |

;; etc. | |

5: | |

xch a, r10 | |

shl a, 1 | |

xch a, r10 | |

rolwc ax, 1 | |

xch a, r13 | |

rolc a, 1 | |

xch a, r13 | |

xch a, r12 | |

rolc a, 1 | |

xch a, r12 | |

rolwc bc, 1 | |

dec h | |

bnz $5b | |

;; Result is currently in (lsb) r13.r12. c. b. (msb), | |

;; Move it into (lsb) r8. r9. r10. r11 (msb). | |

mov a, r13 | |

mov r8, a | |

mov a, r12 | |

mov r9, a | |

mov a, c | |

mov r10, a | |

mov a, b | |

mov r11, a | |

ret | |

END_FUNC ___fixunssfsi | |

;; ------------------------------------------------------------------------ | |

START_FUNC ___floatsisf | |

;; Converts its signed long argument into a floating point. | |

;; Argument in [SP+4]..[SP+7]. Result in R8..R11. | |

;; Get the argument. | |

movw ax, [SP+4] | |

movw bc, ax | |

movw ax, [SP+6] | |

;; Test the sign bit. If the value is positive then drop into | |

;; the unsigned conversion routine. | |

bf a.7, $2f | |

;; If negative convert to positive ... | |

movw hl, ax | |

movw ax, #0 | |

subw ax, bc | |

movw bc, ax | |

movw ax, #0 | |

sknc | |

decw ax | |

subw ax, hl | |

;; If the result is negative then the input was 0x80000000 and | |

;; we want to return -0.0, which will not happen if we call | |

;; __int_floatunsisf. | |

bt a.7, $1f | |

;; Call the unsigned conversion routine. | |

call $!__int_floatunsisf | |

;; Negate the result. | |

set1 r11.7 | |

;; Done. | |

ret | |

1: ;; Return -0.0 aka 0xcf000000 | |

clrb a | |

mov r8, a | |

mov r9, a | |

mov r10, a | |

mov a, #0xcf | |

mov r11, a | |

ret | |

START_ANOTHER_FUNC ___floatunsisf | |

;; Converts its unsigned long argument into a floating point. | |

;; Argument in [SP+4]..[SP+7]. Result in R8..R11. | |

;; Get the argument. | |

movw ax, [SP+4] | |

movw bc, ax | |

movw ax, [SP+6] | |

2: ;; Internal entry point from __floatsisf | |

;; Input in AX (high) and BC (low) | |

.global __int_floatunsisf | |

__int_floatunsisf: | |

;; Special case handling for zero. | |

cmpw ax, #0 | |

bnz $1f | |

movw ax, bc | |

cmpw ax, #0 | |

movw ax, #0 | |

bnz $1f | |

;; Return 0.0 | |

movw r8, ax | |

movw r10, ax | |

ret | |

1: ;; Pre-load the loop count/exponent. | |

;; Exponents are biased by 0x80 and we start the loop knowing that | |

;; we are going to skip the highest set bit. Hence the highest value | |

;; that we can get for the exponent is 0x1e (bits from input) + 0x80 = 0x9e. | |

mov h, #0x9e | |

;; Move bits off the top of AX:BC until we hit a 1 bit. | |

;; Decrement the count of remaining bits as we go. | |

2: shlw bc, 1 | |

rolwc ax, 1 | |

bc $3f | |

dec h | |

br $2b | |

;; Ignore the first one bit - it is implicit in the IEEE format. | |

;; The count of remaining bits is the exponent. | |

;; Assemble the final floating point value. We have... | |

;; before: | |

;; EEEEEEEE MMMMMMMM MMMMMMMM MMMMMMMM xxxxxxxx | |

;; H A X B C | |

;; after: | |

;; 0EEEEEEE EMMMMMMM MMMMMMMM MMMMMMMM | |

;; R11 R10 R9 R8 | |

3: shrw ax, 1 | |

mov r10, a | |

mov a, x | |

mov r9, a | |

mov a, b | |

rorc a, 1 | |

;; If the bottom bit of B was set before we shifted it out then we | |

;; need to round the result up. Unless none of the bits in C are set. | |

;; In this case we are exactly half-way between two values, and we | |

;; round towards an even value. We round up by increasing the | |

;; mantissa by 1. If this results in a zero mantissa we have to | |

;; increment the exponent. We round down by ignoring the dropped bits. | |

bnc $4f | |

cmp0 c | |

sknz | |

bf a.0, $4f | |

5: ;; Round the mantissa up by 1. | |

add a, #1 | |

addc r9, #0 | |

addc r10, #0 | |

bf r10.7, $4f | |

inc h | |

clr1 r10.7 | |

4: mov r8, a | |

mov a, h | |

shr a, 1 | |

mov r11, a | |

sknc | |

set1 r10.7 | |

ret | |

END_ANOTHER_FUNC ___floatunsisf | |

END_FUNC ___floatsisf |