Skip to content

Commit 62238a6

Browse files
committed
WiP Optimize the worst case of RuntimeLong division and remainder.
TODO: Check the case where the divisor is >= 2^62. TODO: Write down the proof.
1 parent 8d55e54 commit 62238a6

File tree

3 files changed

+43
-83
lines changed

3 files changed

+43
-83
lines changed

linker-private-library/src/main/scala/org/scalajs/linker/runtime/RuntimeLong.scala

Lines changed: 33 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,9 @@ final class RuntimeLong(val lo: Int, val hi: Int) {
7676

7777
// A few operator-friendly methods used by the division algorithms
7878

79-
@inline private def <<(b: Int): RuntimeLong = RuntimeLong.shl(this, b)
80-
@inline private def >>>(b: Int): RuntimeLong = RuntimeLong.shr(this, b)
8179
@inline private def +(b: RuntimeLong): RuntimeLong = RuntimeLong.add(this, b)
8280
@inline private def -(b: RuntimeLong): RuntimeLong = RuntimeLong.sub(this, b)
81+
@inline private def *(b: RuntimeLong): RuntimeLong = RuntimeLong.mul(this, b)
8382
}
8483

8584
object RuntimeLong {
@@ -1094,73 +1093,45 @@ object RuntimeLong {
10941093
* remainder. Stores the hi word of the result in `hiReturn`, and returns
10951094
* the lo word.
10961095
*/
1096+
@inline // inlined twice; specializes for askQuotient
10971097
private def unsignedDivModHelper(alo: Int, ahi: Int, blo: Int, bhi: Int,
10981098
askQuotient: Boolean): Int = {
10991099

1100-
var shift =
1101-
inlineNumberOfLeadingZeros(blo, bhi) - inlineNumberOfLeadingZeros(alo, ahi)
1102-
val initialBShift = new RuntimeLong(blo, bhi) << shift
1103-
var bShiftLo = initialBShift.lo
1104-
var bShiftHi = initialBShift.hi
1105-
var remLo = alo
1106-
var remHi = ahi
1107-
var quotLo = 0
1108-
var quotHi = 0
1109-
1110-
/* Invariants:
1111-
* bShift == b << shift == b * 2^shift
1112-
* quot >= 0
1113-
* 0 <= rem < 2 * bShift
1114-
* quot * b + rem == a
1115-
*
1116-
* The loop condition should be
1117-
* while (shift >= 0 && !isUnsignedSafeDouble(remHi))
1118-
* but we manually inline isUnsignedSafeDouble because remHi is a var. If
1119-
* we let the optimizer inline it, it will first store remHi in a temporary
1120-
* val, which will explose the while condition as a while(true) + if +
1121-
* break, and we don't want that.
1122-
*/
1123-
while (shift >= 0 && (remHi & SafeDoubleHiMask) != 0) {
1124-
if (inlineUnsigned_>=(remLo, remHi, bShiftLo, bShiftHi)) {
1125-
val newRem =
1126-
new RuntimeLong(remLo, remHi) - new RuntimeLong(bShiftLo, bShiftHi)
1127-
remLo = newRem.lo
1128-
remHi = newRem.hi
1129-
if (shift < 32)
1130-
quotLo |= (1 << shift)
1131-
else
1132-
quotHi |= (1 << shift) // == (1 << (shift - 32))
1133-
}
1134-
shift -= 1
1135-
val newBShift = new RuntimeLong(bShiftLo, bShiftHi) >>> 1
1136-
bShiftLo = newBShift.lo
1137-
bShiftHi = newBShift.hi
1138-
}
1139-
1140-
// Now rem < 2^53, we can finish with a double division
1141-
if (inlineUnsigned_>=(remLo, remHi, blo, bhi)) {
1142-
val remDouble = asSafeDouble(remLo, remHi)
1143-
val bDouble = asSafeDouble(blo, bhi)
1144-
1145-
if (askQuotient) {
1146-
val rem_div_bDouble = fromUnsignedSafeDouble(remDouble / bDouble)
1147-
val newQuot = new RuntimeLong(quotLo, quotHi) + rem_div_bDouble
1148-
hiReturn = newQuot.hi
1149-
newQuot.lo
1150-
} else {
1151-
val rem_mod_bDouble = remDouble % bDouble
1152-
hiReturn = unsignedSafeDoubleHi(rem_mod_bDouble)
1153-
unsignedSafeDoubleLo(rem_mod_bDouble)
1154-
}
1100+
val result = if (bhi == 0 && inlineUnsignedInt_<(blo, 1 << 21)) {
1101+
// b < 2^21
1102+
1103+
val quotHi = Integer.divideUnsigned(ahi, blo) // takes care of the division by zero check
1104+
val k = ahi - quotHi * blo // remainder of the above division; k < blo
1105+
// (alo, k) is exact because it uses at most 32 + 21 = 53 bits
1106+
val remainingNum = asSafeDouble(alo, k)
1107+
if (askQuotient)
1108+
new RuntimeLong(rawToInt(remainingNum / blo.toDouble), quotHi)
1109+
else
1110+
new RuntimeLong(rawToInt(remainingNum % blo.toDouble), 0)
11551111
} else {
1156-
if (askQuotient) {
1157-
hiReturn = quotHi
1158-
quotLo
1112+
// b >= 2^21
1113+
1114+
val longNum = new RuntimeLong(alo, ahi)
1115+
val longDivisor = new RuntimeLong(blo, bhi)
1116+
val approxDivisor = unsignedToDoubleApprox(blo, bhi)
1117+
val approxNum = unsignedToDoubleApprox(alo, ahi)
1118+
val approxQuot = fromUnsignedSafeDouble(approxNum / approxDivisor)
1119+
val approxRem = longNum - longDivisor * approxQuot
1120+
1121+
if (approxRem.hi < 0) {
1122+
if (askQuotient) approxQuot - new RuntimeLong(1, 0)
1123+
else approxRem + longDivisor
1124+
} else if (geu(approxRem, longDivisor)) {
1125+
if (askQuotient) approxQuot + new RuntimeLong(1, 0)
1126+
else approxRem - longDivisor
11591127
} else {
1160-
hiReturn = remHi
1161-
remLo
1128+
if (askQuotient) approxQuot
1129+
else approxRem
11621130
}
11631131
}
1132+
1133+
hiReturn = result.hi
1134+
result.lo
11641135
}
11651136

11661137
@inline
@@ -1291,17 +1262,6 @@ object RuntimeLong {
12911262
@inline def log2OfPowerOfTwo(i: Int): Int =
12921263
31 - Integer.numberOfLeadingZeros(i)
12931264

1294-
/** Returns the number of leading zeros in the given long (lo, hi). */
1295-
@inline def inlineNumberOfLeadingZeros(lo: Int, hi: Int): Int =
1296-
if (hi != 0) Integer.numberOfLeadingZeros(hi)
1297-
else Integer.numberOfLeadingZeros(lo) + 32
1298-
1299-
/** Tests whether the unsigned long (alo, ahi) is >= (blo, bhi). */
1300-
@inline
1301-
def inlineUnsigned_>=(alo: Int, ahi: Int, blo: Int, bhi: Int): Boolean =
1302-
if (ahi == bhi) inlineUnsignedInt_>=(alo, blo)
1303-
else inlineUnsignedInt_>=(ahi, bhi)
1304-
13051265
@inline
13061266
def inlineUnsignedInt_<(a: Int, b: Int): Boolean =
13071267
(a ^ 0x80000000) < (b ^ 0x80000000)

linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ class LibrarySizeTest {
7070
)
7171

7272
testLinkedSizes(
73-
expectedFastLinkSize = 147932,
74-
expectedFullLinkSizeWithoutClosure = 87506,
75-
expectedFullLinkSizeWithClosure = 20696,
73+
expectedFastLinkSize = 148748,
74+
expectedFullLinkSizeWithoutClosure = 88261,
75+
expectedFullLinkSizeWithClosure = 20680,
7676
classDefs,
7777
moduleInitializers = MainTestModuleInitializers
7878
)

project/Build.scala

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2053,32 +2053,32 @@ object Build {
20532053
case `default212Version` =>
20542054
if (!useMinifySizes) {
20552055
Some(ExpectedSizes(
2056-
fastLink = 624000 to 625000,
2056+
fastLink = 625000 to 626000,
20572057
fullLink = 94000 to 95000,
20582058
fastLinkGz = 75000 to 79000,
20592059
fullLinkGz = 24000 to 25000,
20602060
))
20612061
} else {
20622062
Some(ExpectedSizes(
2063-
fastLink = 425000 to 426000,
2064-
fullLink = 282000 to 283000,
2065-
fastLinkGz = 61000 to 62000,
2063+
fastLink = 426000 to 427000,
2064+
fullLink = 283000 to 284000,
2065+
fastLinkGz = 60000 to 61000,
20662066
fullLinkGz = 43000 to 44000,
20672067
))
20682068
}
20692069

20702070
case `default213Version` =>
20712071
if (!useMinifySizes) {
20722072
Some(ExpectedSizes(
2073-
fastLink = 442000 to 443000,
2073+
fastLink = 443000 to 444000,
20742074
fullLink = 90000 to 91000,
20752075
fastLinkGz = 57000 to 58000,
20762076
fullLinkGz = 24000 to 25000,
20772077
))
20782078
} else {
20792079
Some(ExpectedSizes(
2080-
fastLink = 301000 to 302000,
2081-
fullLink = 258000 to 259000,
2080+
fastLink = 302000 to 303000,
2081+
fullLink = 259000 to 260000,
20822082
fastLinkGz = 47000 to 48000,
20832083
fullLinkGz = 42000 to 43000,
20842084
))

0 commit comments

Comments
 (0)