diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 490f8d3d9d..3839c61e8c 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -4526,50 +4526,78 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) if (opType == jstpe.AnyType) rsrc_in else adaptPrimitive(rsrc_in, if (isShift) jstpe.IntType else opType) + def regular(op: js.BinaryOp.Code): js.Tree = + js.BinaryOp(op, lsrc, rsrc) + (opType: @unchecked) match { case jstpe.IntType => - val op = (code: @switch) match { - case ADD => Int_+ - case SUB => Int_- - case MUL => Int_* - case DIV => Int_/ - case MOD => Int_% - case OR => Int_| - case AND => Int_& - case XOR => Int_^ - case LSL => Int_<< - case LSR => Int_>>> - case ASR => Int_>> - case EQ => Int_== - case NE => Int_!= - case LT => Int_< - case LE => Int_<= - case GT => Int_> - case GE => Int_>= + def comparison(signedOp: js.BinaryOp.Code, unsignedOp: js.BinaryOp.Code): js.Tree = { + (lsrc, rsrc) match { + case (IntFlipSign(flippedLhs), IntFlipSign(flippedRhs)) => + js.BinaryOp(unsignedOp, flippedLhs, flippedRhs) + case (IntFlipSign(flippedLhs), js.IntLiteral(r)) => + js.BinaryOp(unsignedOp, flippedLhs, js.IntLiteral(r ^ Int.MinValue)(rsrc.pos)) + case (js.IntLiteral(l), IntFlipSign(flippedRhs)) => + js.BinaryOp(unsignedOp, js.IntLiteral(l ^ Int.MinValue)(lsrc.pos), flippedRhs) + case _ => + regular(signedOp) + } + } + + (code: @switch) match { + case ADD => regular(Int_+) + case SUB => regular(Int_-) + case MUL => regular(Int_*) + case DIV => regular(Int_/) + case MOD => regular(Int_%) + case OR => regular(Int_|) + case AND => regular(Int_&) + case XOR => regular(Int_^) + case LSL => regular(Int_<<) + case LSR => regular(Int_>>>) + case ASR => regular(Int_>>) + case EQ => regular(Int_==) + case NE => regular(Int_!=) + + case LT => comparison(Int_<, Int_unsigned_<) + case LE => comparison(Int_<=, Int_unsigned_<=) + case GT => comparison(Int_>, Int_unsigned_>) + case GE => comparison(Int_>=, Int_unsigned_>=) } - js.BinaryOp(op, lsrc, rsrc) case jstpe.LongType => - val op = (code: @switch) match { - case ADD => Long_+ - case SUB => Long_- - case MUL => Long_* - case DIV => Long_/ - case MOD => Long_% - case OR => Long_| - case XOR => Long_^ - case AND => Long_& - case LSL => Long_<< - case LSR => Long_>>> - case ASR => Long_>> - case EQ => Long_== - case NE => Long_!= - case LT => Long_< - case LE => Long_<= - case GT => Long_> - case GE => Long_>= + def comparison(signedOp: js.BinaryOp.Code, unsignedOp: js.BinaryOp.Code): js.Tree = { + (lsrc, rsrc) match { + case (LongFlipSign(flippedLhs), LongFlipSign(flippedRhs)) => + js.BinaryOp(unsignedOp, flippedLhs, flippedRhs) + case (LongFlipSign(flippedLhs), js.LongLiteral(r)) => + js.BinaryOp(unsignedOp, flippedLhs, js.LongLiteral(r ^ Long.MinValue)(rsrc.pos)) + case (js.LongLiteral(l), LongFlipSign(flippedRhs)) => + js.BinaryOp(unsignedOp, js.LongLiteral(l ^ Long.MinValue)(lsrc.pos), flippedRhs) + case _ => + regular(signedOp) + } + } + + (code: @switch) match { + case ADD => regular(Long_+) + case SUB => regular(Long_-) + case MUL => regular(Long_*) + case DIV => regular(Long_/) + case MOD => regular(Long_%) + case OR => regular(Long_|) + case XOR => regular(Long_^) + case AND => regular(Long_&) + case LSL => regular(Long_<<) + case LSR => regular(Long_>>>) + case ASR => regular(Long_>>) + case EQ => regular(Long_==) + case NE => regular(Long_!=) + case LT => comparison(Long_<, Long_unsigned_<) + case LE => comparison(Long_<=, Long_unsigned_<=) + case GT => comparison(Long_>, Long_unsigned_>) + case GE => comparison(Long_>=, Long_unsigned_>=) } - js.BinaryOp(op, lsrc, rsrc) case jstpe.FloatType => def withFloats(op: Int): js.Tree = @@ -7357,6 +7385,28 @@ private object GenJSCode { } } + private object IntFlipSign { + def unapply(tree: js.Tree): Option[js.Tree] = tree match { + case js.BinaryOp(js.BinaryOp.Int_^, lhs, js.IntLiteral(Int.MinValue)) => + Some(lhs) + case js.BinaryOp(js.BinaryOp.Int_^, js.IntLiteral(Int.MinValue), rhs) => + Some(rhs) + case _ => + None + } + } + + private object LongFlipSign { + def unapply(tree: js.Tree): Option[js.Tree] = tree match { + case js.BinaryOp(js.BinaryOp.Long_^, lhs, js.LongLiteral(Long.MinValue)) => + Some(lhs) + case js.BinaryOp(js.BinaryOp.Long_^, js.LongLiteral(Long.MinValue), rhs) => + Some(rhs) + case _ => + None + } + } + private abstract class JavalibOpBody { /** Generates the body of this special method, given references to the receiver and parameters. */ def generate(receiver: js.Tree, args: List[js.Tree])(implicit pos: ir.Position): js.Tree @@ -7425,6 +7475,7 @@ private object GenJSCode { val byClass: Map[ClassName, Map[MethodName, JavalibOpBody]] = Map( jswkn.BoxedIntegerClass.withSuffix("$") -> Map( + m("toUnsignedLong", List(I), J) -> ArgUnaryOp(unop.UnsignedIntToLong), m("divideUnsigned", List(I, I), I) -> ArgBinaryOp(binop.Int_unsigned_/), m("remainderUnsigned", List(I, I), I) -> ArgBinaryOp(binop.Int_unsigned_%), m("numberOfLeadingZeros", List(I), I) -> ArgUnaryOp(unop.Int_clz) diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala index b10bef4b95..f38e2adf28 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala @@ -582,6 +582,72 @@ class OptimizationTest extends JSASTTest { case js.LoadModule(`testName`) => } } + + @Test + def unsignedComparisonsInt: Unit = { + import js.BinaryOp._ + + val comparisons = List( + (Int_unsigned_<, "<"), + (Int_unsigned_<=, "<="), + (Int_unsigned_>, ">"), + (Int_unsigned_>=, ">=") + ) + + for ((op, codeOp) <- comparisons) { + s""" + class Test { + private final val SignBit = Int.MinValue + + def unsignedComparisonsInt(x: Int, y: Int): Unit = { + (x ^ 0x80000000) $codeOp (y ^ 0x80000000) + (SignBit ^ x) $codeOp (y ^ SignBit) + (SignBit ^ x) $codeOp 0x80000010 + 0x00000020 $codeOp (y ^ SignBit) + } + } + """.hasExactly(4, "unsigned comparisons") { + case js.BinaryOp(`op`, _, _) => + }.hasNot("any Int_^") { + case js.BinaryOp(Int_^, _, _) => + }.hasNot("any signed comparison") { + case js.BinaryOp(Int_< | Int_<= | Int_> | Int_>=, _, _) => + } + } + } + + @Test + def unsignedComparisonsLong: Unit = { + import js.BinaryOp._ + + val comparisons = List( + (Long_unsigned_<, "<"), + (Long_unsigned_<=, "<="), + (Long_unsigned_>, ">"), + (Long_unsigned_>=, ">=") + ) + + for ((op, codeOp) <- comparisons) { + s""" + class Test { + private final val SignBit = Long.MinValue + + def unsignedComparisonsInt(x: Long, y: Long): Unit = { + (x ^ 0x8000000000000000L) $codeOp (y ^ 0x8000000000000000L) + (SignBit ^ x) $codeOp (y ^ SignBit) + (SignBit ^ x) $codeOp 0x8000000000000010L + 0x0000000000000020L $codeOp (y ^ SignBit) + } + } + """.hasExactly(4, "unsigned comparisons") { + case js.BinaryOp(`op`, _, _) => + }.hasNot("any Long_^") { + case js.BinaryOp(Long_^, _, _) => + }.hasNot("any signed comparison") { + case js.BinaryOp(Long_< | Long_<= | Long_> | Long_>=, _, _) => + } + } + } } object OptimizationTest { diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala index f2035fd7f8..216bec733e 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala @@ -453,6 +453,8 @@ object Printers { case Int_clz => p("(", ")") case Long_clz => p("(", ")") + + case UnsignedIntToLong => p("(", ")") } case BinaryOp(BinaryOp.Int_-, IntLiteral(0), rhs) => @@ -584,6 +586,16 @@ object Printers { case Int_unsigned_% => "unsigned_%[int]" case Long_unsigned_/ => "unsigned_/[long]" case Long_unsigned_% => "unsigned_%[long]" + + case Int_unsigned_< => "unsigned_<[int]" + case Int_unsigned_<= => "unsigned_<=[int]" + case Int_unsigned_> => "unsigned_>[int]" + case Int_unsigned_>= => "unsigned_>=[int]" + + case Long_unsigned_< => "unsigned_<[long]" + case Long_unsigned_<= => "unsigned_<=[long]" + case Long_unsigned_> => "unsigned_>[long]" + case Long_unsigned_>= => "unsigned_>=[long]" }) print(' ') print(rhs) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index 14b748cf48..ca2c76dcc8 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -520,6 +520,7 @@ object Trees { // Other nodes introduced in 1.20 final val Int_clz = 38 final val Long_clz = 39 + final val UnsignedIntToLong = 40 def isClassOp(op: Code): Boolean = op >= Class_name && op <= Class_superClass @@ -545,7 +546,7 @@ object Trees { String_length | Array_length | IdentityHashCode | Float_toBits | Int_clz | Long_clz => IntType - case IntToLong | DoubleToLong | Double_toBits => + case IntToLong | DoubleToLong | Double_toBits | UnsignedIntToLong => LongType case DoubleToFloat | LongToFloat | Float_fromBits => FloatType @@ -685,11 +686,22 @@ object Trees { final val Class_newArray = 62 // New in 1.20 + final val Int_unsigned_/ = 63 final val Int_unsigned_% = 64 final val Long_unsigned_/ = 65 final val Long_unsigned_% = 66 + final val Int_unsigned_< = 67 + final val Int_unsigned_<= = 68 + final val Int_unsigned_> = 69 + final val Int_unsigned_>= = 70 + + final val Long_unsigned_< = 71 + final val Long_unsigned_<= = 72 + final val Long_unsigned_> = 73 + final val Long_unsigned_>= = 74 + def isClassOp(op: Code): Boolean = op >= Class_isInstance && op <= Class_newArray @@ -699,7 +711,9 @@ object Trees { Int_== | Int_!= | Int_< | Int_<= | Int_> | Int_>= | Long_== | Long_!= | Long_< | Long_<= | Long_> | Long_>= | Double_== | Double_!= | Double_< | Double_<= | Double_> | Double_>= | - Class_isInstance | Class_isAssignableFrom => + Class_isInstance | Class_isAssignableFrom | + Int_unsigned_< | Int_unsigned_<= | Int_unsigned_> | Int_unsigned_>= | + Long_unsigned_< | Long_unsigned_<= | Long_unsigned_> | Long_unsigned_>= => BooleanType case String_+ => StringType diff --git a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala index 997e396530..ea68b14864 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -522,6 +522,8 @@ class PrintersTest { assertPrintEquals("(x)", UnaryOp(Int_clz, ref("x", IntType))) assertPrintEquals("(x)", UnaryOp(Long_clz, ref("x", LongType))) + + assertPrintEquals("(x)", UnaryOp(UnsignedIntToLong, ref("x", IntType))) } @Test def printPseudoUnaryOp(): Unit = { @@ -683,6 +685,24 @@ class PrintersTest { BinaryOp(Long_unsigned_/, ref("x", LongType), ref("y", LongType))) assertPrintEquals("(x unsigned_%[long] y)", BinaryOp(Long_unsigned_%, ref("x", LongType), ref("y", LongType))) + + assertPrintEquals("(x unsigned_<[int] y)", + BinaryOp(Int_unsigned_<, ref("x", IntType), ref("y", IntType))) + assertPrintEquals("(x unsigned_<=[int] y)", + BinaryOp(Int_unsigned_<=, ref("x", IntType), ref("y", IntType))) + assertPrintEquals("(x unsigned_>[int] y)", + BinaryOp(Int_unsigned_>, ref("x", IntType), ref("y", IntType))) + assertPrintEquals("(x unsigned_>=[int] y)", + BinaryOp(Int_unsigned_>=, ref("x", IntType), ref("y", IntType))) + + assertPrintEquals("(x unsigned_<[long] y)", + BinaryOp(Long_unsigned_<, ref("x", LongType), ref("y", LongType))) + assertPrintEquals("(x unsigned_<=[long] y)", + BinaryOp(Long_unsigned_<=, ref("x", LongType), ref("y", LongType))) + assertPrintEquals("(x unsigned_>[long] y)", + BinaryOp(Long_unsigned_>, ref("x", LongType), ref("y", LongType))) + assertPrintEquals("(x unsigned_>=[long] y)", + BinaryOp(Long_unsigned_>=, ref("x", LongType), ref("y", LongType))) } @Test def printNewArray(): Unit = { diff --git a/javalib/src/main/scala/java/lang/Integer.scala b/javalib/src/main/scala/java/lang/Integer.scala index 6c9f33ddfd..f817acfd67 100644 --- a/javalib/src/main/scala/java/lang/Integer.scala +++ b/javalib/src/main/scala/java/lang/Integer.scala @@ -186,18 +186,20 @@ object Integer { parse(s, base) } - @inline def compare(x: scala.Int, y: scala.Int): scala.Int = - if (x == y) 0 else if (x < y) -1 else 1 + @inline def compare(x: scala.Int, y: scala.Int): scala.Int = { + if (x == y) 0 + else if (x < y) -1 + else 1 + } @inline def compareUnsigned(x: scala.Int, y: scala.Int): scala.Int = { - import Utils.toUint if (x == y) 0 - else if (toUint(x) > toUint(y)) 1 - else -1 + else if ((x ^ Int.MinValue) < (y ^ Int.MinValue)) -1 + else 1 } @inline def toUnsignedLong(x: Int): scala.Long = - x.toLong & 0xffffffffL + throw new Error("stub") // body replaced by the compiler back-end // Wasm intrinsic def bitCount(i: scala.Int): scala.Int = { diff --git a/javalib/src/main/scala/java/lang/Long.scala b/javalib/src/main/scala/java/lang/Long.scala index 4fc7a32505..de3ed8ae94 100644 --- a/javalib/src/main/scala/java/lang/Long.scala +++ b/javalib/src/main/scala/java/lang/Long.scala @@ -337,16 +337,19 @@ object Long { @inline def hashCode(value: scala.Long): Int = value.toInt ^ (value >>> 32).toInt - // Intrinsic + // RuntimeLong intrinsic @inline def compare(x: scala.Long, y: scala.Long): scala.Int = { if (x == y) 0 else if (x < y) -1 else 1 } - // TODO Intrinsic? - @inline def compareUnsigned(x: scala.Long, y: scala.Long): scala.Int = - compare(x ^ SignBit, y ^ SignBit) + // TODO RuntimeLong intrinsic? + @inline def compareUnsigned(x: scala.Long, y: scala.Long): scala.Int = { + if (x == y) 0 + else if ((x ^ scala.Long.MinValue) < (y ^ scala.Long.MinValue)) -1 + else 1 + } @inline def divideUnsigned(dividend: scala.Long, divisor: scala.Long): scala.Long = throw new Error("stub") // body replaced by the compiler back-end 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 f6d30c1dd7..045ac6bf9d 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 @@ -156,6 +156,50 @@ object RuntimeLong { else ahi > bhi } + @inline + def ltu(a: RuntimeLong, b: RuntimeLong): Boolean = { + /* Manually inline `inlineUnsignedInt_<(a.lo, b.lo)`. + * See the comment in `<` for the rationale. + */ + val ahi = a.hi + val bhi = b.hi + if (ahi == bhi) (a.lo ^ 0x80000000) < (b.lo ^ 0x80000000) + else inlineUnsignedInt_<(ahi, bhi) + } + + @inline + def leu(a: RuntimeLong, b: RuntimeLong): Boolean = { + /* Manually inline `inlineUnsignedInt_<=(a.lo, b.lo)`. + * See the comment in `<` for the rationale. + */ + val ahi = a.hi + val bhi = b.hi + if (ahi == bhi) (a.lo ^ 0x80000000) <= (b.lo ^ 0x80000000) + else inlineUnsignedInt_<=(ahi, bhi) + } + + @inline + def gtu(a: RuntimeLong, b: RuntimeLong): Boolean = { + /* Manually inline `inlineUnsignedInt_>(a.lo, b.lo)`. + * See the comment in `<` for the rationale. + */ + val ahi = a.hi + val bhi = b.hi + if (ahi == bhi) (a.lo ^ 0x80000000) > (b.lo ^ 0x80000000) + else inlineUnsignedInt_>(ahi, bhi) + } + + @inline + def geu(a: RuntimeLong, b: RuntimeLong): Boolean = { + /* Manually inline `inlineUnsignedInt_>=(a.lo, b.lo)`. + * See the comment in `<` for the rationale. + */ + val ahi = a.hi + val bhi = b.hi + if (ahi == bhi) (a.lo ^ 0x80000000) >= (b.lo ^ 0x80000000) + else inlineUnsignedInt_>=(ahi, bhi) + } + // Bitwise operations @inline @@ -730,6 +774,10 @@ object RuntimeLong { def fromInt(value: Int): RuntimeLong = new RuntimeLong(value, value >> 31) + @inline + def fromUnsignedInt(value: Int): RuntimeLong = + new RuntimeLong(value, 0) + @inline def fromDouble(value: Double): RuntimeLong = { val lo = fromDoubleImpl(value) @@ -1204,6 +1252,10 @@ object RuntimeLong { def inlineUnsignedInt_<(a: Int, b: Int): Boolean = (a ^ 0x80000000) < (b ^ 0x80000000) + @inline + def inlineUnsignedInt_<=(a: Int, b: Int): Boolean = + (a ^ 0x80000000) <= (b ^ 0x80000000) + @inline def inlineUnsignedInt_>(a: Int, b: Int): Boolean = (a ^ 0x80000000) > (b ^ 0x80000000) diff --git a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureAstTransformer.scala b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureAstTransformer.scala index 2397ff94af..56c0232121 100644 --- a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureAstTransformer.scala +++ b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureAstTransformer.scala @@ -376,6 +376,8 @@ private class ClosureAstTransformer(featureSet: FeatureSet, if (value) new Node(Token.TRUE) else new Node(Token.FALSE) case IntLiteral(value) => mkNumberLiteral(value) + case UintLiteral(value) => + mkNumberLiteral(Integer.toUnsignedLong(value).toDouble) case DoubleLiteral(value) => mkNumberLiteral(value) case StringLiteral(value) => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index 604aa09971..e3f696248c 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -2208,8 +2208,12 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { def or0(tree: js.Tree): js.Tree = js.BinaryOp(JSBinaryOp.|, tree, js.IntLiteral(0)) - def shr0(tree: js.Tree): js.Tree = - js.BinaryOp(JSBinaryOp.>>>, tree, js.IntLiteral(0)) + def shr0(tree: js.Tree): js.Tree = tree match { + case js.IntLiteral(value) => + js.UintLiteral(value) + case _ => + js.BinaryOp(JSBinaryOp.>>>, tree, js.IntLiteral(0)) + } def bigIntShiftRhs(tree: js.Tree): js.Tree = { tree match { @@ -2540,6 +2544,12 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { genCallHelper(VarField.longClz, newLhs) else genLongApplyStatic(LongImpl.clz, newLhs) + + case UnsignedIntToLong => + if (useBigIntForLongs) + js.Apply(genGlobalVarRef("BigInt"), List(shr0(newLhs))) + else + genLongApplyStatic(LongImpl.fromUnsignedInt, newLhs) } case BinaryOp(op, lhs, rhs) => @@ -2843,6 +2853,32 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { js.Apply(extractClassData(lhs, newLhs) DOT cpn.cast, newRhs :: Nil) case Class_newArray => js.Apply(extractClassData(lhs, newLhs) DOT cpn.newArray, newRhs :: Nil) + + case Int_unsigned_< => js.BinaryOp(JSBinaryOp.<, shr0(newLhs), shr0(newRhs)) + case Int_unsigned_<= => js.BinaryOp(JSBinaryOp.<=, shr0(newLhs), shr0(newRhs)) + case Int_unsigned_> => js.BinaryOp(JSBinaryOp.>, shr0(newLhs), shr0(newRhs)) + case Int_unsigned_>= => js.BinaryOp(JSBinaryOp.>=, shr0(newLhs), shr0(newRhs)) + + case Long_unsigned_< => + if (useBigIntForLongs) + js.BinaryOp(JSBinaryOp.<, wrapBigIntU64(newLhs), wrapBigIntU64(newRhs)) + else + genLongApplyStatic(LongImpl.ltu, newLhs, newRhs) + case Long_unsigned_<= => + if (useBigIntForLongs) + js.BinaryOp(JSBinaryOp.<=, wrapBigIntU64(newLhs), wrapBigIntU64(newRhs)) + else + genLongApplyStatic(LongImpl.leu, newLhs, newRhs) + case Long_unsigned_> => + if (useBigIntForLongs) + js.BinaryOp(JSBinaryOp.>, wrapBigIntU64(newLhs), wrapBigIntU64(newRhs)) + else + genLongApplyStatic(LongImpl.gtu, newLhs, newRhs) + case Long_unsigned_>= => + if (useBigIntForLongs) + js.BinaryOp(JSBinaryOp.>=, wrapBigIntU64(newLhs), wrapBigIntU64(newRhs)) + else + genLongApplyStatic(LongImpl.geu, newLhs, newRhs) } case NewArray(typeRef, length) => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/LongImpl.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/LongImpl.scala index 10d0acf68b..98f1b8cccf 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/LongImpl.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/LongImpl.scala @@ -81,6 +81,10 @@ private[linker] object LongImpl { final val le = compareOp("le") final val gt = compareOp("gt") final val ge = compareOp("ge") + final val ltu = compareOp("ltu") + final val leu = compareOp("leu") + final val gtu = compareOp("gtu") + final val geu = compareOp("geu") final val toInt = MethodName("toInt", OneRTLongRef, IntRef) final val toFloat = MethodName("toFloat", OneRTLongRef, FloatRef) @@ -89,6 +93,7 @@ private[linker] object LongImpl { final val clz = MethodName("clz", OneRTLongRef, IntRef) final val fromInt = MethodName("fromInt", List(IntRef), RTLongRef) + final val fromUnsignedInt = MethodName("fromUnsignedInt", List(IntRef), RTLongRef) final val fromDouble = MethodName("fromDouble", List(DoubleRef), RTLongRef) final val fromDoubleBits = MethodName("fromDoubleBits", List(DoubleRef, ObjectRef), RTLongRef) @@ -96,9 +101,9 @@ private[linker] object LongImpl { add, sub, mul, divide, remainder, divideUnsigned, remainderUnsigned, or, and, xor, shl, shr, sar, - equals_, notEquals, lt, le, gt, ge, + equals_, notEquals, lt, le, gt, ge, ltu, leu, gtu, geu, toInt, toFloat, toDouble, bitsToDouble, clz, - fromInt, fromDouble, fromDoubleBits + fromInt, fromUnsignedInt, fromDouble, fromDoubleBits ) // Methods used for intrinsics diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala index d4fb5f2284..0d9420dc41 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala @@ -371,7 +371,7 @@ object Printers { case DotSelect(qualifier, item) => qualifier match { - case _:IntLiteral | _:DoubleLiteral => + case _:IntLiteral | _:UintLiteral | _:DoubleLiteral => print("(") print(qualifier) print(")") @@ -552,6 +552,10 @@ object Printers { } printSeparatorIfStat() + case UintLiteral(value) => + print(Integer.toUnsignedString(value)) + printSeparatorIfStat() + case DoubleLiteral(value) => if (value == 0 && 1 / value < 0) { print("(-0)") diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Trees.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Trees.scala index 0ed4501d8f..1482d5e478 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Trees.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Trees.scala @@ -416,6 +416,9 @@ object Trees { sealed case class IntLiteral(value: Int)(implicit val pos: Position) extends Literal + sealed case class UintLiteral(value: Int)(implicit val pos: Position) + extends Literal + sealed case class DoubleLiteral(value: Double)(implicit val pos: Position) extends Literal diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index b15f1ced8c..a944df20d8 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -1691,6 +1691,9 @@ private class FunctionEmitter private ( case Long_clz => fb += wa.I64Clz fb += wa.I32WrapI64 + + case UnsignedIntToLong => + fb += wa.I64ExtendI32U } tree.tpe @@ -1974,6 +1977,16 @@ private class FunctionEmitter private ( case Double_>= => wa.F64Ge case Class_newArray => wa.Call(genFunctionID.newArray) + + case Int_unsigned_< => wa.I32LtU + case Int_unsigned_<= => wa.I32LeU + case Int_unsigned_> => wa.I32GtU + case Int_unsigned_>= => wa.I32GeU + + case Long_unsigned_< => wa.I64LtU + case Long_unsigned_<= => wa.I64LeU + case Long_unsigned_> => wa.I64GtU + case Long_unsigned_>= => wa.I64GeU } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index c6aef8d11e..c25ae55672 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -539,7 +539,7 @@ private final class IRChecker(linkTimeProperties: LinkTimeProperties, case ShortToInt => ShortType case IntToLong | IntToDouble | IntToChar | IntToByte | IntToShort | - Float_fromBits | Int_clz => + Float_fromBits | Int_clz | UnsignedIntToLong => IntType case LongToInt | LongToDouble | LongToFloat | Double_fromBits | Long_clz => @@ -574,12 +574,14 @@ private final class IRChecker(linkTimeProperties: LinkTimeProperties, case Int_+ | Int_- | Int_* | Int_/ | Int_% | Int_| | Int_& | Int_^ | Int_<< | Int_>>> | Int_>> | Int_== | Int_!= | Int_< | Int_<= | Int_> | Int_>= | - Int_unsigned_/ | Int_unsigned_% => + Int_unsigned_/ | Int_unsigned_% | + Int_unsigned_< | Int_unsigned_<= | Int_unsigned_> | Int_unsigned_>= => IntType case Long_+ | Long_- | Long_* | Long_/ | Long_% | Long_| | Long_& | Long_^ | Long_<< | Long_>>> | Long_>> | Long_== | Long_!= | Long_< | Long_<= | Long_> | Long_>= | - Long_unsigned_/ | Long_unsigned_% => + Long_unsigned_/ | Long_unsigned_% | + Long_unsigned_< | Long_unsigned_<= | Long_unsigned_> | Long_unsigned_>= => LongType case Float_+ | Float_- | Float_* | Float_/ | Float_% => FloatType diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 08dbf1d1cf..16ac1a4122 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -3558,6 +3558,9 @@ private[optimizer] abstract class OptimizerCore( case Long_clz => expand(LongImpl.clz, arg) + case UnsignedIntToLong => + expand(LongImpl.fromUnsignedInt, arg) + case _ => cont(pretrans) } @@ -3590,6 +3593,11 @@ private[optimizer] abstract class OptimizerCore( case Long_unsigned_/ => expand(LongImpl.divideUnsigned, lhs, rhs) case Long_unsigned_% => expand(LongImpl.remainderUnsigned, lhs, rhs) + case Long_unsigned_< => expand(LongImpl.ltu, lhs, rhs) + case Long_unsigned_<= => expand(LongImpl.leu, lhs, rhs) + case Long_unsigned_> => expand(LongImpl.gtu, lhs, rhs) + case Long_unsigned_>= => expand(LongImpl.geu, lhs, rhs) + case _ => cont(pretrans) } @@ -3625,6 +3633,11 @@ private[optimizer] abstract class OptimizerCore( case BinaryOp.Int_> => BinaryOp.Int_<= case BinaryOp.Int_>= => BinaryOp.Int_< + case BinaryOp.Int_unsigned_< => BinaryOp.Int_unsigned_>= + case BinaryOp.Int_unsigned_<= => BinaryOp.Int_unsigned_> + case BinaryOp.Int_unsigned_> => BinaryOp.Int_unsigned_<= + case BinaryOp.Int_unsigned_>= => BinaryOp.Int_unsigned_< + case BinaryOp.Long_== => BinaryOp.Long_!= case BinaryOp.Long_!= => BinaryOp.Long_== case BinaryOp.Long_< => BinaryOp.Long_>= @@ -3632,6 +3645,11 @@ private[optimizer] abstract class OptimizerCore( case BinaryOp.Long_> => BinaryOp.Long_<= case BinaryOp.Long_>= => BinaryOp.Long_< + case BinaryOp.Long_unsigned_< => BinaryOp.Long_unsigned_>= + case BinaryOp.Long_unsigned_<= => BinaryOp.Long_unsigned_> + case BinaryOp.Long_unsigned_> => BinaryOp.Long_unsigned_<= + case BinaryOp.Long_unsigned_>= => BinaryOp.Long_unsigned_< + case BinaryOp.Double_== => BinaryOp.Double_!= case BinaryOp.Double_!= => BinaryOp.Double_== @@ -3910,6 +3928,16 @@ private[optimizer] abstract class OptimizerCore( default } + // Unsigned int to long + + case UnsignedIntToLong => + arg match { + case PreTransLit(IntLiteral(v)) => + PreTransLit(LongLiteral(Integer.toUnsignedLong(v))) + case _ => + default + } + case _ => default } @@ -4465,54 +4493,72 @@ private[optimizer] abstract class OptimizerCore( case _ => default } - case Int_< | Int_<= | Int_> | Int_>= => - def flippedOp = (op: @switch) match { - case Int_< => Int_> - case Int_<= => Int_>= - case Int_> => Int_< - case Int_>= => Int_<= + case Int_< | Int_<= | Int_> | Int_>= | + Int_unsigned_< | Int_unsigned_<= | Int_unsigned_> | Int_unsigned_>= => + val (isSigned, otherSignOp, flippedOp) = (op: @switch) match { + case Int_< => (true, Int_unsigned_<, Int_>) + case Int_<= => (true, Int_unsigned_<=, Int_>=) + case Int_> => (true, Int_unsigned_>, Int_<) + case Int_>= => (true, Int_unsigned_>=, Int_<=) + case Int_unsigned_< => (false, Int_<, Int_unsigned_>) + case Int_unsigned_<= => (false, Int_<=, Int_unsigned_>=) + case Int_unsigned_> => (false, Int_>, Int_unsigned_<) + case Int_unsigned_>= => (false, Int_>=, Int_unsigned_<=) } + val opMinValue = if (isSigned) Int.MinValue else 0 + val opMaxValue = if (isSigned) Int.MaxValue else -1 + val signedOp = if (isSigned) op else otherSignOp // for normalized tests + (lhs, rhs) match { case (PreTransLit(IntLiteral(l)), PreTransLit(IntLiteral(r))) => booleanLit((op: @switch) match { - case Int_< => l < r - case Int_<= => l <= r - case Int_> => l > r - case Int_>= => l >= r + case Int_< => l < r + case Int_<= => l <= r + case Int_> => l > r + case Int_>= => l >= r + case Int_unsigned_< => Integer.compareUnsigned(l, r) < 0 + case Int_unsigned_<= => Integer.compareUnsigned(l, r) <= 0 + case Int_unsigned_> => Integer.compareUnsigned(l, r) > 0 + case Int_unsigned_>= => Integer.compareUnsigned(l, r) >= 0 }) + case (IntFlipSign(x), PreTransLit(IntLiteral(r))) => + foldBinaryOp(otherSignOp, x, PreTransLit(IntLiteral(r ^ Int.MinValue)(rhs.pos))) + case (IntFlipSign(x), IntFlipSign(y)) => + foldBinaryOp(otherSignOp, x, y) + case (_, PreTransLit(IntLiteral(y))) => y match { - case Int.MinValue => - if (op == Int_< || op == Int_>=) { + case `opMinValue` => + if (signedOp == Int_< || signedOp == Int_>=) { Block(finishTransformStat(lhs), - BooleanLiteral(op == Int_>=)).toPreTransform + BooleanLiteral(signedOp == Int_>=)).toPreTransform } else { - foldBinaryOp(if (op == Int_<=) Int_== else Int_!=, lhs, rhs) + foldBinaryOp(if (signedOp == Int_<=) Int_== else Int_!=, lhs, rhs) } - case Int.MaxValue => - if (op == Int_> || op == Int_<=) { + case `opMaxValue` => + if (signedOp == Int_> || signedOp == Int_<=) { Block(finishTransformStat(lhs), - BooleanLiteral(op == Int_<=)).toPreTransform + BooleanLiteral(signedOp == Int_<=)).toPreTransform } else { - foldBinaryOp(if (op == Int_>=) Int_== else Int_!=, lhs, rhs) + foldBinaryOp(if (signedOp == Int_>=) Int_== else Int_!=, lhs, rhs) } - case _ if y == Int.MinValue + 1 && (op == Int_< || op == Int_>=) => - foldBinaryOp(if (op == Int_<) Int_== else Int_!=, lhs, - PreTransLit(IntLiteral(Int.MinValue))) + case _ if y == opMinValue + 1 && (signedOp == Int_< || signedOp == Int_>=) => + foldBinaryOp(if (signedOp == Int_<) Int_== else Int_!=, lhs, + PreTransLit(IntLiteral(opMinValue))) - case _ if y == Int.MaxValue - 1 && (op == Int_> || op == Int_<=) => - foldBinaryOp(if (op == Int_>) Int_== else Int_!=, lhs, - PreTransLit(IntLiteral(Int.MaxValue))) + case _ if y == opMaxValue - 1 && (signedOp == Int_> || signedOp == Int_<=) => + foldBinaryOp(if (signedOp == Int_>) Int_== else Int_!=, lhs, + PreTransLit(IntLiteral(opMaxValue))) case _ => default } case (PreTransLocalDef(l), PreTransLocalDef(r)) if l eq r => - booleanLit(op == Int_<= || op == Int_>=) + booleanLit(signedOp == Int_<= || signedOp == Int_>=) case (PreTransLit(IntLiteral(_)), _) => foldBinaryOp(flippedOp, rhs, lhs) @@ -4679,6 +4725,9 @@ private[optimizer] abstract class OptimizerCore( case (PreTransLit(LongLiteral(0)), _) => PreTransBlock(finishTransformStat(rhs), lhs) + case (PreTransLit(LongLiteral(0xffffffffL)), LongFromInt(intRhs)) => + foldUnaryOp(UnaryOp.UnsignedIntToLong, intRhs) + case (PreTransLit(LongLiteral(x)), PreTransBinaryOp(Long_&, PreTransLit(LongLiteral(y)), z)) => foldBinaryOp(Long_&, PreTransLit(LongLiteral(x & y)), z) @@ -4765,49 +4814,60 @@ private[optimizer] abstract class OptimizerCore( case _ => default } - case Long_< | Long_<= | Long_> | Long_>= => - def flippedOp = (op: @switch) match { - case Long_< => Long_> - case Long_<= => Long_>= - case Long_> => Long_< - case Long_>= => Long_<= + case Long_< | Long_<= | Long_> | Long_>= | + Long_unsigned_< | Long_unsigned_<= | Long_unsigned_> | Long_unsigned_>= => + val (isSigned, otherSignOp, flippedOp, intOp) = (op: @switch) match { + case Long_< => (true, Long_unsigned_<, Long_>, Int_<) + case Long_<= => (true, Long_unsigned_<=, Long_>=, Int_<=) + case Long_> => (true, Long_unsigned_>, Long_<, Int_>) + case Long_>= => (true, Long_unsigned_>=, Long_<=, Int_>=) + case Long_unsigned_< => (false, Long_<, Long_unsigned_>, Int_unsigned_<) + case Long_unsigned_<= => (false, Long_<=, Long_unsigned_>=, Int_unsigned_<=) + case Long_unsigned_> => (false, Long_>, Long_unsigned_<, Int_unsigned_>) + case Long_unsigned_>= => (false, Long_>=, Long_unsigned_<=, Int_unsigned_>=) } - def intOp = (op: @switch) match { - case Long_< => Int_< - case Long_<= => Int_<= - case Long_> => Int_> - case Long_>= => Int_>= - } + val opMinValue = if (isSigned) Long.MinValue else 0L + val opMaxValue = if (isSigned) Long.MaxValue else -1L + val signedOp = if (isSigned) op else otherSignOp // for normalized tests (lhs, rhs) match { case (PreTransLit(LongLiteral(l)), PreTransLit(LongLiteral(r))) => booleanLit((op: @switch) match { - case Long_< => l < r - case Long_<= => l <= r - case Long_> => l > r - case Long_>= => l >= r + case Long_< => l < r + case Long_<= => l <= r + case Long_> => l > r + case Long_>= => l >= r + case Long_unsigned_< => java.lang.Long.compareUnsigned(l, r) < 0 + case Long_unsigned_<= => java.lang.Long.compareUnsigned(l, r) <= 0 + case Long_unsigned_> => java.lang.Long.compareUnsigned(l, r) > 0 + case Long_unsigned_>= => java.lang.Long.compareUnsigned(l, r) >= 0 }) - case (_, PreTransLit(LongLiteral(Long.MinValue))) => - if (op == Long_< || op == Long_>=) { + case (LongFlipSign(x), PreTransLit(LongLiteral(r))) => + foldBinaryOp(otherSignOp, x, PreTransLit(LongLiteral(r ^ Long.MinValue)(rhs.pos))) + case (LongFlipSign(x), LongFlipSign(y)) => + foldBinaryOp(otherSignOp, x, y) + + case (_, PreTransLit(LongLiteral(`opMinValue`))) => + if (signedOp == Long_< || signedOp == Long_>=) { Block(finishTransformStat(lhs), - BooleanLiteral(op == Long_>=)).toPreTransform + BooleanLiteral(signedOp == Long_>=)).toPreTransform } else { - foldBinaryOp(if (op == Long_<=) Long_== else Long_!=, lhs, rhs) + foldBinaryOp(if (signedOp == Long_<=) Long_== else Long_!=, lhs, rhs) } - case (_, PreTransLit(LongLiteral(Long.MaxValue))) => - if (op == Long_> || op == Long_<=) { + case (_, PreTransLit(LongLiteral(`opMaxValue`))) => + if (signedOp == Long_> || signedOp == Long_<=) { Block(finishTransformStat(lhs), - BooleanLiteral(op == Long_<=)).toPreTransform + BooleanLiteral(signedOp == Long_<=)).toPreTransform } else { - foldBinaryOp(if (op == Long_>=) Long_== else Long_!=, lhs, rhs) + foldBinaryOp(if (signedOp == Long_>=) Long_== else Long_!=, lhs, rhs) } case (LongFromInt(x), LongFromInt(y)) => foldBinaryOp(intOp, x, y) - case (LongFromInt(x), PreTransLit(LongLiteral(y))) => + case (LongFromInt(x), PreTransLit(LongLiteral(y))) if isSigned => assert(y > Int.MaxValue || y < Int.MinValue) val result = if (y > Int.MaxValue) op == Long_< || op == Long_<= @@ -4821,7 +4881,8 @@ private[optimizer] abstract class OptimizerCore( */ case (PreTransBinaryOp(Long_+, PreTransLit(LongLiteral(x)), y @ LongFromInt(_)), PreTransLit(LongLiteral(z))) - if canAddLongs(x, Int.MinValue) && + if isSigned && + canAddLongs(x, Int.MinValue) && canAddLongs(x, Int.MaxValue) && canSubtractLongs(z, x) => foldBinaryOp(op, y, PreTransLit(LongLiteral(z-x))) @@ -4833,7 +4894,8 @@ private[optimizer] abstract class OptimizerCore( */ case (PreTransBinaryOp(Long_-, PreTransLit(LongLiteral(x)), y @ LongFromInt(_)), PreTransLit(LongLiteral(z))) - if canSubtractLongs(x, Int.MinValue) && + if isSigned && + canSubtractLongs(x, Int.MinValue) && canSubtractLongs(x, Int.MaxValue) && canSubtractLongs(z, x) => if (z-x != Long.MinValue) { @@ -4861,7 +4923,8 @@ private[optimizer] abstract class OptimizerCore( * This requires to evaluate x and y once. */ case (PreTransBinaryOp(Long_+, LongFromInt(x), LongFromInt(y)), - PreTransLit(LongLiteral(Int.MaxValue))) => + PreTransLit(LongLiteral(Int.MaxValue))) + if isSigned => trampoline { /* HACK: We use an empty scope here for `withNewLocalDefs`. * It's OKish to do that because we're only defining Ints, and @@ -4884,7 +4947,7 @@ private[optimizer] abstract class OptimizerCore( }.toPreTransform case (PreTransLocalDef(l), PreTransLocalDef(r)) if l eq r => - booleanLit(op == Long_<= || op == Long_>=) + booleanLit(signedOp == Long_<= || signedOp == Long_>=) case (PreTransLit(LongLiteral(_)), _) => foldBinaryOp(flippedOp, rhs, lhs) @@ -6540,6 +6603,24 @@ private[optimizer] object OptimizerCore { } } + private object IntFlipSign { + def unapply(tree: PreTransform): Option[PreTransform] = tree match { + case PreTransBinaryOp(BinaryOp.Int_^, PreTransLit(IntLiteral(Int.MinValue)), x) => + Some(x) + case _ => + None + } + } + + private object LongFlipSign { + def unapply(tree: PreTransform): Option[PreTransform] = tree match { + case PreTransBinaryOp(BinaryOp.Long_^, PreTransLit(LongLiteral(Long.MinValue)), x) => + Some(x) + case _ => + None + } + } + private object AndThen { def apply(lhs: Tree, rhs: Tree)(implicit pos: Position): Tree = If(lhs, rhs, BooleanLiteral(false))(BooleanType) 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 6fc9029bb0..d2a7b193e6 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 = 148312, - expectedFullLinkSizeWithoutClosure = 87480, - expectedFullLinkSizeWithClosure = 20659, + expectedFastLinkSize = 148960, + expectedFullLinkSizeWithoutClosure = 88111, + expectedFullLinkSizeWithClosure = 20704, classDefs, moduleInitializers = MainTestModuleInitializers ) diff --git a/project/Build.scala b/project/Build.scala index c73788331f..2141d5f3b3 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2061,8 +2061,8 @@ object Build { } else { Some(ExpectedSizes( fastLink = 425000 to 426000, - fullLink = 282000 to 283000, - fastLinkGz = 60000 to 61000, + fullLink = 283000 to 284000, + fastLinkGz = 61000 to 62000, fullLinkGz = 43000 to 44000, )) } @@ -2070,7 +2070,7 @@ object Build { case `default213Version` => if (!useMinifySizes) { Some(ExpectedSizes( - fastLink = 442000 to 443000, + fastLink = 443000 to 444000, fullLink = 90000 to 91000, fastLinkGz = 57000 to 58000, fullLinkGz = 24000 to 25000, @@ -2078,7 +2078,7 @@ object Build { } else { Some(ExpectedSizes( fastLink = 301000 to 302000, - fullLink = 258000 to 259000, + fullLink = 259000 to 260000, fastLinkGz = 47000 to 48000, fullLinkGz = 42000 to 43000, ))