diff --git a/javalib/src/main/scala/java/lang/Long.scala b/javalib/src/main/scala/java/lang/Long.scala index 5c347eecd4..bf27b15544 100644 --- a/javalib/src/main/scala/java/lang/Long.scala +++ b/javalib/src/main/scala/java/lang/Long.scala @@ -120,7 +120,7 @@ object Long { if (radix == 10 || radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) toString(i) else - toStringImpl(i, radix) + toStringImpl(i.toInt, (i >>> 32).toInt, radix) } @inline // because radix is almost certainly constant at call site @@ -133,7 +133,7 @@ object Long { val radix1 = if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) 10 else radix - toUnsignedStringImpl(i, radix1) + toUnsignedStringImpl(i.toInt, (i >>> 32).toInt, radix1) } } @@ -141,40 +141,42 @@ object Long { @inline def toString(i: scala.Long): String = "" + i @inline def toUnsignedString(i: scala.Long): String = - toUnsignedStringImpl(i, 10) + toUnsignedStringImpl(i.toInt, (i >>> 32).toInt, 10) // Must be called only with valid radix - private def toStringImpl(i: scala.Long, radix: Int): String = { - val lo = i.toInt - val hi = (i >>> 32).toInt + private def toStringImpl(lo: Int, hi: Int, radix: Int): String = { + import js.JSNumberOps.enableJSNumberOps if (lo >> 31 == hi) { // It's a signed int32 - import js.JSNumberOps.enableJSNumberOps lo.toString(radix) - } else if (hi < 0) { - val neg = -i - "-" + toUnsignedStringInternalLarge(neg.toInt, (neg >>> 32).toInt, radix) + } else if (((hi ^ (hi >> 10)) & 0xffe00000) == 0) { // see RuntimeLong.isSignedSafeDouble + // (lo, hi) is small enough to be a Double, so toDouble is exact + makeLongFromLoHi(lo, hi).toDouble.toString(radix) } else { - toUnsignedStringInternalLarge(lo, hi, radix) + val abs = Math.abs(makeLongFromLoHi(lo, hi)) + val s = toUnsignedStringInternalLarge(abs.toInt, (abs >>> 32).toInt, radix) + if (hi < 0) "-" + s else s } } // Must be called only with valid radix - private def toUnsignedStringImpl(i: scala.Long, radix: Int): String = { - val lo = i.toInt - val hi = (i >>> 32).toInt + private def toUnsignedStringImpl(lo: Int, hi: Int, radix: Int): String = { + import js.JSNumberOps.enableJSNumberOps if (hi == 0) { // It's an unsigned int32 - import js.JSNumberOps.enableJSNumberOps Integer.toUnsignedDouble(lo).toString(radix) + } else if ((hi & 0xffe00000) == 0) { // see RuntimeLong.isUnsignedSafeDouble + // (lo, hi) is small enough to be a Double, so toDouble is exact + makeLongFromLoHi(lo, hi).toDouble.toString(radix) } else { toUnsignedStringInternalLarge(lo, hi, radix) } } - // Must be called only with valid radix and with (lo, hi) >= 2^30 + // Must be called only with valid radix and with (lo, hi) >= 2^53 + @inline // inlined twice: once in toStringImpl and once in toUnsignedStringImpl private def toUnsignedStringInternalLarge(lo: Int, hi: Int, radix: Int): String = { import js.JSNumberOps.enableJSNumberOps import js.JSStringOps.enableJSStringOps diff --git a/linker-private-library/src/main/scala/org/scalajs/linker/runtime/RuntimeLong.scala b/linker-private-library/src/main/scala/org/scalajs/linker/runtime/RuntimeLong.scala index 71066e0f2f..e434624112 100644 --- a/linker-private-library/src/main/scala/org/scalajs/linker/runtime/RuntimeLong.scala +++ b/linker-private-library/src/main/scala/org/scalajs/linker/runtime/RuntimeLong.scala @@ -76,10 +76,9 @@ final class RuntimeLong(val lo: Int, val hi: Int) { // A few operator-friendly methods used by the division algorithms - @inline private def <<(b: Int): RuntimeLong = RuntimeLong.shl(this, b) - @inline private def >>>(b: Int): RuntimeLong = RuntimeLong.shr(this, b) @inline private def +(b: RuntimeLong): RuntimeLong = RuntimeLong.add(this, b) @inline private def -(b: RuntimeLong): RuntimeLong = RuntimeLong.sub(this, b) + @inline private def *(b: RuntimeLong): RuntimeLong = RuntimeLong.mul(this, b) } object RuntimeLong { @@ -90,7 +89,7 @@ object RuntimeLong { * double. * @see isUnsignedSafeDouble */ - private final val UnsignedSafeDoubleHiMask = 0xffe00000 + private final val SafeDoubleHiMask = 0xffe00000 /** The hi part of a (lo, hi) return value. */ private[this] var hiReturn: Int = _ @@ -591,103 +590,98 @@ object RuntimeLong { private def toString(lo: Int, hi: Int): String = { if (isInt32(lo, hi)) { lo.toString() - } else if (hi < 0) { - val neg = inline_negate(lo, hi) - "-" + toUnsignedString(neg.lo, neg.hi) + } else if (isSignedSafeDouble(hi)) { + asSafeDouble(lo, hi).toString() } else { - toUnsignedString(lo, hi) + val abs = inline_abs(lo, hi) + val s = toUnsignedStringLarge(abs.lo, abs.hi) + if (hi < 0) "-" + s else s } } - private def toUnsignedString(lo: Int, hi: Int): String = { - // This is called only if (lo, hi) is not an Int32 - - if (isUnsignedSafeDouble(hi)) { - // (lo, hi) is small enough to be a Double, use that directly - asUnsignedSafeDouble(lo, hi).toString - } else { - /* At this point, (lo, hi) >= 2^53. - * - * The idea is to divide (lo, hi) once by 10^9 and keep the remainder. - * - * The remainder must then be < 10^9, and is therefore an int32. - * - * The quotient must be <= ULong.MaxValue / 10^9, which is < 2^53, and - * is therefore a valid double. It must also be non-zero, since - * (lo, hi) >= 2^53 > 10^9. - * - * We should do that single division as a Long division. However, that is - * slow. We can cheat with a Double division instead. - * - * We convert the unsigned value num = (lo, hi) to a Double value - * approxNum. This is an approximation. It can lose as many as - * 64 - 53 = 11 low-order bits. Hence |approxNum - num| <= 2^12. - * - * We then compute an approximated quotient - * approxQuot = floor(approxNum / 10^9) - * instead of the theoretical value - * quot = floor(num / 10^9) - * - * Since 10^9 > 2^29 > 2^12, we have |approxNum - num| < 10^9. - * Therefore, |approxQuot - quot| <= 1. - * - * We also have 0 <= approxQuot < 2^53, which means that approxQuot is an - * "unsigned safe double" and that `approxQuot.toLong` is lossless. - * - * At this point, we compute the approximated remainder - * approxRem = num - 10^9 * approxQuot.toLong - * as if with Long arithmetics. - * - * Since the theoretical remainder rem = num - 10^9 * quot is such that - * 0 <= rem < 10^9, and since |approxQuot - quot| <= 1, we have that - * -10^9 <= approxRem < 2 * 10^9 - * - * Interestingly, that range entirely fits within a signed int32. - * That means approxRem = approxRem.toInt, and therefore - * - * approxRem - * = (num - 10^9 * approxQuot.toLong).toInt - * = num.toInt - 10^9 * approxQuot.toLong.toInt (thanks to modular arithmetics) - * = lo - 10^9 * unsignedSafeDoubleLo(approxQuot) - * - * That allows to compute approxRem with Int arithmetics without loss of - * precision. - * - * We can use approxRem to detect and correct the error on approxQuot. - * If approxRem < 0, correct approxQuot by -1 and approxRem by +10^9. - * If approxRem >= 10^9, correct them by +1 and -10^9, respectively. - * - * After the correction, we know that approxQuot and approxRem are equal - * to their theoretical counterparts quot and rem. We have successfully - * computed the correct quotient and remainder without using any Long - * division. - * - * We can finally convert both to strings using the native string - * conversions, and concatenate the results to produce our final result. - */ - - // constants - val divisor = 1000000000 // 10^9 - val divisorInv = 1.0 / divisor.toDouble - - // initial approximation of the quotient and remainder - val approxNum = unsignedToDoubleApprox(lo, hi) - var approxQuot = scala.scalajs.js.Math.floor(approxNum * divisorInv) - var approxRem = lo - divisor * unsignedSafeDoubleLo(approxQuot) - - // correct the approximations - if (approxRem < 0) { - approxQuot -= 1.0 - approxRem += divisor - } else if (approxRem >= divisor) { - approxQuot += 1.0 - approxRem -= divisor - } + @inline + private def toUnsignedStringLarge(lo: Int, hi: Int): String = { + /* This is called only if (lo, hi) is >= 2^53. + * + * The idea is to divide (lo, hi) once by 10^9 and keep the remainder. + * + * The remainder must then be < 10^9, and is therefore an int32. + * + * The quotient must be <= ULong.MaxValue / 10^9, which is < 2^53, and + * is therefore a valid double. It must also be non-zero, since + * (lo, hi) >= 2^53 > 10^9. + * + * We should do that single division as a Long division. However, that is + * slow. We can cheat with a Double division instead. + * + * We convert the unsigned value num = (lo, hi) to a Double value + * approxNum. This is an approximation. It can lose as many as + * 64 - 53 = 11 low-order bits. Hence |approxNum - num| <= 2^12. + * + * We then compute an approximated quotient + * approxQuot = floor(approxNum / 10^9) + * instead of the theoretical value + * quot = floor(num / 10^9) + * + * Since 10^9 > 2^29 > 2^12, we have |approxNum - num| < 10^9. + * Therefore, |approxQuot - quot| <= 1. + * + * We also have 0 <= approxQuot < 2^53, which means that approxQuot is an + * "unsigned safe double" and that `approxQuot.toLong` is lossless. + * + * At this point, we compute the approximated remainder + * approxRem = num - 10^9 * approxQuot.toLong + * as if with Long arithmetics. + * + * Since the theoretical remainder rem = num - 10^9 * quot is such that + * 0 <= rem < 10^9, and since |approxQuot - quot| <= 1, we have that + * -10^9 <= approxRem < 2 * 10^9 + * + * Interestingly, that range entirely fits within a signed int32. + * That means approxRem = approxRem.toInt, and therefore + * + * approxRem + * = (num - 10^9 * approxQuot.toLong).toInt + * = num.toInt - 10^9 * approxQuot.toLong.toInt (thanks to modular arithmetics) + * = lo - 10^9 * unsignedSafeDoubleLo(approxQuot) + * + * That allows to compute approxRem with Int arithmetics without loss of + * precision. + * + * We can use approxRem to detect and correct the error on approxQuot. + * If approxRem < 0, correct approxQuot by -1 and approxRem by +10^9. + * If approxRem >= 10^9, correct them by +1 and -10^9, respectively. + * + * After the correction, we know that approxQuot and approxRem are equal + * to their theoretical counterparts quot and rem. We have successfully + * computed the correct quotient and remainder without using any Long + * division. + * + * We can finally convert both to strings using the native string + * conversions, and concatenate the results to produce our final result. + */ - // build the result string - val remStr = approxRem.toString() - approxQuot.toString() + substring("000000000", remStr.length()) + remStr + // constants + val divisor = 1000000000 // 10^9 + val divisorInv = 1.0 / divisor.toDouble + + // initial approximation of the quotient and remainder + val approxNum = unsignedToDoubleApprox(lo, hi) + var approxQuot = scala.scalajs.js.Math.floor(approxNum * divisorInv) + var approxRem = lo - divisor * unsignedSafeDoubleLo(approxQuot) + + // correct the approximations + if (approxRem < 0) { + approxQuot -= 1.0 + approxRem += divisor + } else if (approxRem >= divisor) { + approxQuot += 1.0 + approxRem -= divisor } + + // build the result string + val remStr = approxRem.toString() + approxQuot.toString() + substring("000000000", remStr.length()) + remStr } @inline @@ -695,23 +689,11 @@ object RuntimeLong { a.lo @inline - def toDouble(a: RuntimeLong): Double = { - val lo = a.lo - val hi = a.hi - if (hi < 0) { - // We need unsignedToDoubleApprox specifically for MinValue - val neg = inline_negate(lo, hi) - -unsignedToDoubleApprox(neg.lo, neg.hi) - } else { - nonNegativeToDoubleApprox(lo, hi) - } - } + def toDouble(a: RuntimeLong): Double = + signedToDoubleApprox(a.lo, a.hi) @inline - def toFloat(a: RuntimeLong): Float = - toFloat(a.lo, a.hi) - - private def toFloat(lo: Int, hi: Int): Float = { + def toFloat(a: RuntimeLong): Float = { /* This implementation is based on the property that, *if* the conversion * `x.toDouble` is lossless, then the result of `x.toFloat` is equivalent * to `x.toDouble.toFloat`. @@ -730,39 +712,48 @@ object RuntimeLong { * * The algorithm works as follows: * - * First, we take the absolute value of the input. We will negate the - * result at the end if the input was negative. - * - * Second, if the abs input is an unsigned safe Double, then the conversion - * to double is lossless, so we don't have to do anything special - * (`y == x` in terms of the above explanation). - * - * Otherwise, we know that the input's highest 1 bit is in the 11 - * highest-order bits. That means that rounding to float, which only has 24 - * bits in the significand, can only take into account the - * `11 + 23 + 1 = 35` highest-order bits (the `+ 1` is for the rounding - * bit). The remaining bits can only affect the result by two states: - * either they are all 0's, or there is at least one 1. We use that - * property to "compress" the 16 low-order bits into a single 0 or 1 bit - * representing those two states. The compressed Long value - * `y = (compressedAbsLo, abs.hi)` has at most `32 + 17 = 49` significant + * Second, if the input is a signed safe Double, then the conversion to + * double is lossless, so we don't have to do anything special (`y == x` in + * terms of the above explanation). + * + * Otherwise, let us first assume that `x >= 0`. In that case, we know that + * the input's highest 1 bit is in the 11 highest-order bits. That means + * that rounding to float, which only has 24 bits in the significand, can + * only take into account the `11 + 23 + 1 = 35` highest-order bits (the + * `+ 1` is for the rounding bit). The remaining bits can only affect the + * result by two states: either they are all 0's, or there is at least one + * 1. We use that property to "compress" the 16 low-order bits into a + * single 0 or 1 bit representing those two states. The compressed Long + * value `y = (compressedLo, hi)` has at most `32 + 17 = 49` significant * bits. Therefore its conversion to Double is lossless. * * Now that we always have a lossless compression to Double, we can perform * it, followed by a conversion from Double to Float, which will apply the * appropriate rounding. * - * (A similar strategy is used in `parseFloat` for the hexadecimal format.) + * (A similar strategy is used in `parseFloat` for the hexadecimal format, + * where we only have the non-negative case.) + * + * For the case `x < 0`, logically we should negate it, perform the above + * transformation and convert to Double, then negate the result. It turns + * out we do not need a separate code path. Indeed, if x is a safe double, + * then -x also converts losslessly (-x may not be safe double by our + * definition, because it could be exactly 2^53, but the conversion is + * still exact). Otherwise, we should apply a compression if + * `(-x & 0xffffL) != 0L`. Because of how two's complement negation work, + * that is equivalent to `(x & 0xffffL) != 0L`, and therefore also + * equivalent to `(lo & 0xffff) != 0`. When we do need a compression, we + * can do it on the signed representation just as well as the unsigned + * representation, because it only affects `lo`, and `lo` is interpreted as + * unsigned regardless, when converting to a double. */ - val abs = inline_abs(lo, hi) - val compressedAbsLo = - if (isUnsignedSafeDouble(abs.hi) || (abs.lo & 0xffff) == 0) abs.lo - else (abs.lo & ~0xffff) | 0x8000 - - val absRes = unsignedToDoubleApprox(compressedAbsLo, abs.hi) - - (if (hi < 0) -absRes else absRes).toFloat + val lo = a.lo + val hi = a.hi + val compressedLo = + if (isSignedSafeDouble(hi) || (lo & 0xffff) == 0) lo + else (lo & ~0xffff) | 0x8000 + signedToDoubleApprox(compressedLo, hi).toFloat } @inline @@ -917,38 +908,12 @@ object RuntimeLong { new RuntimeLong(lo, hiReturn) } + @noinline def divideImpl(alo: Int, ahi: Int, blo: Int, bhi: Int): Int = { - if (isZero(blo, bhi)) - throw new ArithmeticException("/ by zero") - - if (isInt32(alo, ahi)) { - if (isInt32(blo, bhi)) { - if (alo == Int.MinValue && blo == -1) { - hiReturn = 0 - Int.MinValue - } else { - val lo = alo / blo - hiReturn = lo >> 31 - lo - } - } else { - // Either a == Int.MinValue && b == (Int.MaxValue + 1), or (abs(b) > abs(a)) - if (alo == Int.MinValue && (blo == 0x80000000 && bhi == 0)) { - hiReturn = -1 - -1 - } else { - // 0L, because abs(b) > abs(a) - hiReturn = 0 - 0 - } - } - } else { - val aAbs = inline_abs(alo, ahi) - val bAbs = inline_abs(blo, bhi) - val absRLo = unsigned_/(aAbs.lo, aAbs.hi, bAbs.lo, bAbs.hi) - if ((ahi ^ bhi) >= 0) absRLo // a and b have the same sign bit - else inline_negate_hiReturn(absRLo, hiReturn) - } + val aAbs = inline_abs(alo, ahi) + val bAbs = inline_abs(blo, bhi) + val absR = unsignedDivRem(aAbs.lo, aAbs.hi, bAbs.lo, bAbs.hi, askQuotient = true) + intoHiReturn(negateIfSign(absR, (ahi ^ bhi) >> 31)) // sign on if a and b have opposite signs } @inline @@ -957,51 +922,10 @@ object RuntimeLong { new RuntimeLong(lo, hiReturn) } + @noinline def divideUnsignedImpl(alo: Int, ahi: Int, blo: Int, bhi: Int): Int = { - if (isZero(blo, bhi)) - throw new ArithmeticException("/ by zero") - - if (isUInt32(ahi)) { - if (isUInt32(bhi)) { - hiReturn = 0 - Integer.divideUnsigned(alo, blo) - } else { - // a < b - hiReturn = 0 - 0 - } - } else { - unsigned_/(alo, ahi, blo, bhi) - } - } - - private def unsigned_/(alo: Int, ahi: Int, blo: Int, bhi: Int): Int = { - // This method is not called if isInt32(alo, ahi) nor if isZero(blo, bhi) - if (isUnsignedSafeDouble(ahi)) { - if (isUnsignedSafeDouble(bhi)) { - val aDouble = asUnsignedSafeDouble(alo, ahi) - val bDouble = asUnsignedSafeDouble(blo, bhi) - val rDouble = aDouble / bDouble - hiReturn = unsignedSafeDoubleHi(rDouble) - unsignedSafeDoubleLo(rDouble) - } else { - // 0L, because b > a - hiReturn = 0 - 0 - } - } else { - if (bhi == 0 && isPowerOfTwo_IKnowItsNot0(blo)) { - val pow = log2OfPowerOfTwo(blo) - hiReturn = ahi >>> pow - (alo >>> pow) | (ahi << 1 << (31-pow)) - } else if (blo == 0 && isPowerOfTwo_IKnowItsNot0(bhi)) { - val pow = log2OfPowerOfTwo(bhi) - hiReturn = 0 - ahi >>> pow - } else { - unsignedDivModHelper(alo, ahi, blo, bhi, askQuotient = true) - } - } + val r = unsignedDivRem(alo, ahi, blo, bhi, askQuotient = true) + intoHiReturn(r) } @inline @@ -1010,39 +934,12 @@ object RuntimeLong { new RuntimeLong(lo, hiReturn) } + @noinline def remainderImpl(alo: Int, ahi: Int, blo: Int, bhi: Int): Int = { - if (isZero(blo, bhi)) - throw new ArithmeticException("/ by zero") - - if (isInt32(alo, ahi)) { - if (isInt32(blo, bhi)) { - if (blo != -1) { - val lo = alo % blo - hiReturn = lo >> 31 - lo - } else { - // Work around https://github.com/ariya/phantomjs/issues/12198 - hiReturn = 0 - 0 - } - } else { - // Either a == Int.MinValue && b == (Int.MaxValue + 1), or (abs(b) > abs(a)) - if (alo == Int.MinValue && (blo == 0x80000000 && bhi == 0)) { - hiReturn = 0 - 0 - } else { - // a, because abs(b) > abs(a) - hiReturn = ahi - alo - } - } - } else { - val aAbs = inline_abs(alo, ahi) - val bAbs = inline_abs(blo, bhi) - val absRLo = unsigned_%(aAbs.lo, aAbs.hi, bAbs.lo, bAbs.hi) - if (ahi < 0) inline_negate_hiReturn(absRLo, hiReturn) - else absRLo - } + val aAbs = inline_abs(alo, ahi) + val bAbs = inline_abs(blo, bhi) + val absR = unsignedDivRem(aAbs.lo, aAbs.hi, bAbs.lo, bAbs.hi, askQuotient = false) + intoHiReturn(negateIfSign(absR, ahi >> 31)) // the result should have the same sign as a } @inline @@ -1051,122 +948,51 @@ object RuntimeLong { new RuntimeLong(lo, hiReturn) } - def remainderUnsignedImpl(alo: Int, ahi: Int, blo: Int, bhi: Int): Int = { - if (isZero(blo, bhi)) - throw new ArithmeticException("/ by zero") - - if (isUInt32(ahi)) { - if (isUInt32(bhi)) { - hiReturn = 0 - Integer.remainderUnsigned(alo, blo) - } else { - // a < b - hiReturn = ahi - alo - } - } else { - unsigned_%(alo, ahi, blo, bhi) - } - } - - private def unsigned_%(alo: Int, ahi: Int, blo: Int, bhi: Int): Int = { - // This method is not called if isInt32(alo, ahi) nor if isZero(blo, bhi) - if (isUnsignedSafeDouble(ahi)) { - if (isUnsignedSafeDouble(bhi)) { - val aDouble = asUnsignedSafeDouble(alo, ahi) - val bDouble = asUnsignedSafeDouble(blo, bhi) - val rDouble = aDouble % bDouble - hiReturn = unsignedSafeDoubleHi(rDouble) - unsignedSafeDoubleLo(rDouble) - } else { - // a, because b > a - hiReturn = ahi - alo - } - } else { - if (bhi == 0 && isPowerOfTwo_IKnowItsNot0(blo)) { - hiReturn = 0 - alo & (blo - 1) - } else if (blo == 0 && isPowerOfTwo_IKnowItsNot0(bhi)) { - hiReturn = ahi & (bhi - 1) - alo - } else { - unsignedDivModHelper(alo, ahi, blo, bhi, askQuotient = false) - } - } + @noinline + private def remainderUnsignedImpl(alo: Int, ahi: Int, blo: Int, bhi: Int): Int = { + val r = unsignedDivRem(alo, ahi, blo, bhi, askQuotient = false) + intoHiReturn(r) } - /** Helper for `unsigned_/` and `unsigned_%`. + /** Common implementation of the four division operations. * * If `askQuotient` is true, computes the quotient, otherwise computes the - * remainder. Stores the hi word of the result in `hiReturn`, and returns - * the lo word. + * remainder. */ - private def unsignedDivModHelper(alo: Int, ahi: Int, blo: Int, bhi: Int, - askQuotient: Boolean): Int = { - - var shift = - inlineNumberOfLeadingZeros(blo, bhi) - inlineNumberOfLeadingZeros(alo, ahi) - val initialBShift = new RuntimeLong(blo, bhi) << shift - var bShiftLo = initialBShift.lo - var bShiftHi = initialBShift.hi - var remLo = alo - var remHi = ahi - var quotLo = 0 - var quotHi = 0 - - /* Invariants: - * bShift == b << shift == b * 2^shift - * quot >= 0 - * 0 <= rem < 2 * bShift - * quot * b + rem == a - * - * The loop condition should be - * while (shift >= 0 && !isUnsignedSafeDouble(remHi)) - * but we manually inline isUnsignedSafeDouble because remHi is a var. If - * we let the optimizer inline it, it will first store remHi in a temporary - * val, which will explose the while condition as a while(true) + if + - * break, and we don't want that. - */ - while (shift >= 0 && (remHi & UnsignedSafeDoubleHiMask) != 0) { - if (inlineUnsigned_>=(remLo, remHi, bShiftLo, bShiftHi)) { - val newRem = - new RuntimeLong(remLo, remHi) - new RuntimeLong(bShiftLo, bShiftHi) - remLo = newRem.lo - remHi = newRem.hi - if (shift < 32) - quotLo |= (1 << shift) - else - quotHi |= (1 << shift) // == (1 << (shift - 32)) - } - shift -= 1 - val newBShift = new RuntimeLong(bShiftLo, bShiftHi) >>> 1 - bShiftLo = newBShift.lo - bShiftHi = newBShift.hi - } - - // Now rem < 2^53, we can finish with a double division - if (inlineUnsigned_>=(remLo, remHi, blo, bhi)) { - val remDouble = asUnsignedSafeDouble(remLo, remHi) - val bDouble = asUnsignedSafeDouble(blo, bhi) - - if (askQuotient) { - val rem_div_bDouble = fromUnsignedSafeDouble(remDouble / bDouble) - val newQuot = new RuntimeLong(quotLo, quotHi) + rem_div_bDouble - hiReturn = newQuot.hi - newQuot.lo - } else { - val rem_mod_bDouble = remDouble % bDouble - hiReturn = unsignedSafeDoubleHi(rem_mod_bDouble) - unsignedSafeDoubleLo(rem_mod_bDouble) - } + @inline // inlined 4 times and specialized by askQuotient, so we get 2 copies of each + private def unsignedDivRem(alo: Int, ahi: Int, blo: Int, bhi: Int, + askQuotient: Boolean): RuntimeLong = { + + if (bhi == 0 && inlineUnsignedInt_<(blo, 1 << 21)) { + // b < 2^21 + + val quotHi = Integer.divideUnsigned(ahi, blo) // takes care of the division by zero check + val k = ahi - quotHi * blo // remainder of the above division; k < blo + // (alo, k) is exact because it uses at most 32 + 21 = 53 bits + val remainingNum = asSafeDouble(alo, k) + if (askQuotient) + new RuntimeLong(rawToInt(remainingNum / blo.toDouble), quotHi) + else + new RuntimeLong(rawToInt(remainingNum % blo.toDouble), 0) } else { - if (askQuotient) { - hiReturn = quotHi - quotLo + // b >= 2^21 + + val longNum = new RuntimeLong(alo, ahi) + val longDivisor = new RuntimeLong(blo, bhi) + val approxDivisor = unsignedToDoubleApprox(blo, bhi) + val approxNum = unsignedToDoubleApprox(alo, ahi) + val approxQuot = fromUnsignedSafeDouble(approxNum / approxDivisor) + val approxRem = longNum - longDivisor * approxQuot + + if (approxRem.hi < 0) { + if (askQuotient) approxQuot - new RuntimeLong(1, 0) + else approxRem + longDivisor + } else if (geu(approxRem, longDivisor)) { + if (askQuotient) approxQuot + new RuntimeLong(1, 0) + else approxRem - longDivisor } else { - hiReturn = remHi - remLo + if (askQuotient) approxQuot + else approxRem } } } @@ -1177,18 +1003,10 @@ object RuntimeLong { s.jsSubstring(start) } - /** Tests whether the long (lo, hi) is 0. */ - @inline def isZero(lo: Int, hi: Int): Boolean = - (lo | hi) == 0 - /** Tests whether the long (lo, hi)'s mathematical value fits in a signed Int. */ @inline def isInt32(lo: Int, hi: Int): Boolean = hi == (lo >> 31) - /** Tests whether the long (_, hi)'s mathematical value fits in an unsigned Int. */ - @inline def isUInt32(hi: Int): Boolean = - hi == 0 - /** Tests whether an unsigned long (lo, hi) is a safe Double. * This test is in fact slightly stricter than necessary, as it tests * whether `x < 2^53`, although x == 2^53 would be a perfectly safe @@ -1199,11 +1017,27 @@ object RuntimeLong { * stay on the fast side. */ @inline def isUnsignedSafeDouble(hi: Int): Boolean = - (hi & UnsignedSafeDoubleHiMask) == 0 + (hi & SafeDoubleHiMask) == 0 - /** Converts an unsigned safe double into its Double representation. */ - @inline def asUnsignedSafeDouble(lo: Int, hi: Int): Double = - nonNegativeToDoubleApprox(lo, hi) + /** Tests whether a signed long (lo, hi) is a safe Double. + * + * This test is in fact slightly stricter than necessary, as it tests + * whether `-2^53 <= x < 2^53`, although x == 2^53 would be a perfectly safe + * Double. We do it this way because it corresponds to testing whether the + * value can be represented as a signed 54-bit integer. That is true if and + * only if the (64 - 54) = 10 most significant bits are all equal to bit 53, + * or equivalently, whether the 11 most significant bits all equal. + * + * Since there is virtually no gain to treating 2^53 itself as a safe + * Double, compared to all numbers smaller than it, we don't bother, and + * stay on the fast side. + */ + @inline def isSignedSafeDouble(hi: Int): Boolean = + ((hi ^ (hi >> 10)) & SafeDoubleHiMask) == 0 + + /** Converts a safe double (signed or unsigned) into its exact Double representation. */ + @inline def asSafeDouble(lo: Int, hi: Int): Double = + signedToDoubleApprox(lo, hi) /** Converts an unsigned safe double into its RuntimeLong representation. */ @inline def fromUnsignedSafeDouble(x: Double): RuntimeLong = @@ -1221,14 +1055,41 @@ object RuntimeLong { @inline def unsignedToDoubleApprox(lo: Int, hi: Int): Double = uintToDouble(hi) * TwoPow32 + uintToDouble(lo) - /** Approximates a non-negative (lo, hi) with a Double. + /** Approximates a signed (lo, hi) with a Double. * * If `hi` is known to be non-negative, this method is equivalent to * `unsignedToDoubleApprox`, but it can fold away part of the computation if * `hi` is in fact constant. */ - @inline def nonNegativeToDoubleApprox(lo: Int, hi: Int): Double = + @inline def signedToDoubleApprox(lo: Int, hi: Int): Double = { + /* We note a_u the mathematical value of a when interpreted as an unsigned + * quantity, and a_s when interpreted as a signed quantity. + * + * For x = (lo, hi), the result must be the correctly rounded value of x_s. + * + * If x_s >= 0, then hi_s >= 0. The obvious mathematical value of x_s is + * x_s = hi_s * 2^32 + lo_u + * + * If x_s < 0, then hi_s < 0. The fundamental definition of two's + * completement means that + * x_s = -2^64 + hi_u * 2^32 + lo_u + * Likewise, + * hi_s = -2^32 + hi_u + * + * Now take the computation for the x_s >= 0 case, but substituting values + * for the negative case: + * hi_s * 2^32 + lo_u + * = (-2^32 + hi_u) * 2^32 + lo_u + * = (-2^64 + hi_u * 2^32) + lo_u + * which is the correct mathematical result for x_s in the negative case. + * + * Therefore, we can always compute + * x_s = hi_s * 2^32 + lo_u + * When computed with `Double` values, only the last `+` can be inexact, + * hence the result is correctly round. + */ hi.toDouble * TwoPow32 + uintToDouble(lo) + } /** Interprets an `Int` as an unsigned integer and returns its value as a * `Double`. @@ -1248,25 +1109,6 @@ object RuntimeLong { (x | 0).asInstanceOf[Int] } - /** Tests whether the given non-zero unsigned Int is an exact power of 2. */ - @inline def isPowerOfTwo_IKnowItsNot0(i: Int): Boolean = - (i & (i - 1)) == 0 - - /** Returns the log2 of the given unsigned Int assuming it is an exact power of 2. */ - @inline def log2OfPowerOfTwo(i: Int): Int = - 31 - Integer.numberOfLeadingZeros(i) - - /** Returns the number of leading zeros in the given long (lo, hi). */ - @inline def inlineNumberOfLeadingZeros(lo: Int, hi: Int): Int = - if (hi != 0) Integer.numberOfLeadingZeros(hi) - else Integer.numberOfLeadingZeros(lo) + 32 - - /** Tests whether the unsigned long (alo, ahi) is >= (blo, bhi). */ - @inline - def inlineUnsigned_>=(alo: Int, ahi: Int, blo: Int, bhi: Int): Boolean = - if (ahi == bhi) inlineUnsignedInt_>=(alo, blo) - else inlineUnsignedInt_>=(ahi, bhi) - @inline def inlineUnsignedInt_<(a: Int, b: Int): Boolean = (a ^ 0x80000000) < (b ^ 0x80000000) @@ -1284,18 +1126,25 @@ object RuntimeLong { (a ^ 0x80000000) >= (b ^ 0x80000000) @inline - def inline_negate(lo: Int, hi: Int): RuntimeLong = - sub(new RuntimeLong(0, 0), new RuntimeLong(lo, hi)) + def intoHiReturn(x: RuntimeLong): Int = { + hiReturn = x.hi + x.lo + } @inline - def inline_negate_hiReturn(lo: Int, hi: Int): Int = { - val n = inline_negate(lo, hi) - hiReturn = n.hi - n.lo - } + def inline_abs(lo: Int, hi: Int): RuntimeLong = + negateIfSign(lo, hi, hi >> 31) @inline - def inline_abs(lo: Int, hi: Int): RuntimeLong = { + def negateIfSign(x: RuntimeLong, sign: Int): RuntimeLong = + negateIfSign(x.lo, x.hi, sign) + + /** Returns (lo, hi) if sign == 0, or -(lo, hi) if sign == -1. + * + * The function assumes that `sign` is either 0 or -1. + */ + @inline + def negateIfSign(lo: Int, hi: Int, sign: Int): RuntimeLong = { /* The algorithm here is inspired by Hacker's Delight formula for `abs`. * However, a naive application of that formula does not give good code for * our RuntimeLong implementation. @@ -1379,7 +1228,6 @@ object RuntimeLong { * imagine" step. We inline the rhs of xhi at the only place where it is * used, and we get the final algorithm. */ - val sign = hi >> 31 val xlo = lo ^ sign val rlo = xlo - sign val rhi = (hi ^ sign) + ((xlo & ~rlo) >>> 31) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala index e6d062aab1..6fe99505a3 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala @@ -70,9 +70,9 @@ class LibrarySizeTest { ) testLinkedSizes( - expectedFastLinkSize = 148481, - expectedFullLinkSizeWithoutClosure = 87816, - expectedFullLinkSizeWithClosure = 20704, + expectedFastLinkSize = 147372, + expectedFullLinkSizeWithoutClosure = 87808, + expectedFullLinkSizeWithClosure = 20680, classDefs, moduleInitializers = MainTestModuleInitializers ) diff --git a/project/Build.scala b/project/Build.scala index 071d5016db..560f1f34b3 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2053,16 +2053,16 @@ object Build { case `default212Version` => if (!useMinifySizes) { Some(ExpectedSizes( - fastLink = 625000 to 626000, + fastLink = 624000 to 625000, fullLink = 94000 to 95000, fastLinkGz = 75000 to 79000, fullLinkGz = 24000 to 25000, )) } else { Some(ExpectedSizes( - fastLink = 426000 to 427000, - fullLink = 283000 to 284000, - fastLinkGz = 61000 to 62000, + fastLink = 425000 to 426000, + fullLink = 282000 to 283000, + fastLinkGz = 60000 to 61000, fullLinkGz = 43000 to 44000, )) } @@ -2070,7 +2070,7 @@ object Build { case `default213Version` => if (!useMinifySizes) { Some(ExpectedSizes( - fastLink = 443000 to 444000, + fastLink = 441000 to 442000, fullLink = 90000 to 91000, fastLinkGz = 57000 to 58000, fullLinkGz = 24000 to 25000,