diff --git a/javalib/src/main/scala/java/lang/Character.scala b/javalib/src/main/scala/java/lang/Character.scala index 4aa1d49f37..e7c75b09ae 100644 --- a/javalib/src/main/scala/java/lang/Character.scala +++ b/javalib/src/main/scala/java/lang/Character.scala @@ -128,7 +128,7 @@ object Character { if (!isValidCodePoint(codePoint)) throw new IllegalArgumentException() - if (LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf (LinkingInfo.targetPureWasm) { if (isBmpCodePoint(codePoint)) { Character.toString(codePoint.toChar) } else { @@ -136,7 +136,8 @@ object Character { toSurrogate(codePoint, dst, 0) new String(dst) } - } else if (LinkingInfo.esVersion >= ESVersion.ES2015) { + } { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { js.Dynamic.global.String.fromCodePoint(codePoint).asInstanceOf[String] } else { if (codePoint < MIN_SUPPLEMENTARY_CODE_POINT) { @@ -148,7 +149,7 @@ object Character { .fromCharCode(highSurrogate(codePoint).toInt, lowSurrogate(codePoint).toInt) .asInstanceOf[String] } - } + }} } // Low-level code point and code unit manipulations ------------------------- @@ -706,10 +707,10 @@ object Character { case _ => // In WASI implementation, we cannot use String#toUpperCase // since it uses Character#toUpperCase. - if (LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { import CaseUtil._ toCase(codePoint, a, z, lowerBeta, lowerRanges, lowerDeltas, lowerSteps) - } else { + } { val upperChars = toString(codePoint).toUpperCase() upperChars.length match { case 1 => @@ -735,12 +736,12 @@ object Character { case 0x0130 => 0x0069 // İ => i case _ => - if (LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { // in pure Wasm implementation, we cannot use String#toLowerCase // since it uses Character$toLowerCase import CaseUtil._ toCase(codePoint, A, Z, upperMu, upperRanges, upperDeltas, upperSteps) - } else { + } { val lowerChars = toString(codePoint).toLowerCase() lowerChars.length match { case 1 => diff --git a/javalib/src/main/scala/java/lang/Double.scala b/javalib/src/main/scala/java/lang/Double.scala index b8c1ffc779..034391c982 100644 --- a/javalib/src/main/scala/java/lang/Double.scala +++ b/javalib/src/main/scala/java/lang/Double.scala @@ -16,6 +16,7 @@ import java.lang.constant.{Constable, ConstantDesc} import scala.scalajs.js import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.linkTimeIf import Utils._ @@ -380,10 +381,11 @@ object Double { * The two implementations compute the same results. */ @inline def hashCode(value: scala.Double): Int = { - if (LinkingInfo.isWebAssembly) + linkTimeIf(LinkingInfo.isWebAssembly) { hashCodeForWasm(value) - else + } { hashCodeForJS(value) + } } @inline diff --git a/javalib/src/main/scala/java/lang/Integer.scala b/javalib/src/main/scala/java/lang/Integer.scala index 52d6c88baa..61f1fc4a0f 100644 --- a/javalib/src/main/scala/java/lang/Integer.scala +++ b/javalib/src/main/scala/java/lang/Integer.scala @@ -17,7 +17,7 @@ import java.util.function._ import scala.scalajs.js import scala.scalajs.LinkingInfo -import scala.scalajs.LinkingInfo.ESVersion +import scala.scalajs.LinkingInfo.{ESVersion, linkTimeIf} /* This is a hijacked class. Its instances are primitive numbers. * Constructors are not emitted. @@ -103,7 +103,7 @@ object Integer { if (i >= s.length) fail() - if (LinkingInfo.targetPureWasm) { + linkTimeIf(LinkingInfo.targetPureWasm) { val maxAbsValue: scala.Long = { if (!signed) 0xffffffffL else if (negative) 0x80000000L @@ -123,7 +123,7 @@ object Integer { -result.toInt else result.toInt - } else { + } { val maxAbsValue: scala.Double = { if (!signed) 0xffffffffL.toDouble else if (negative) 0x80000000L.toDouble @@ -298,29 +298,42 @@ object Integer { // Intrinsic, fallback on actual code for non-literal in JS @inline def numberOfLeadingZeros(i: scala.Int): scala.Int = { - if (LinkingInfo.esVersion >= ESVersion.ES2015) js.Math.clz32(i) - else clz32Dynamic(i) + linkTimeIf(LinkingInfo.targetPureWasm) { + clz32Dynamic(i) + } { + if (LinkingInfo.esVersion >= ESVersion.ES2015) js.Math.clz32(i) + else clz32Dynamic(i) + } + } private def clz32Dynamic(i: scala.Int) = { - if (js.typeOf(js.Dynamic.global.Math.clz32) == "function") { - js.Math.clz32(i) - } else { - // See Hacker's Delight, Section 5-3 - var x = i - if (x == 0) { - 32 + linkTimeIf(LinkingInfo.targetPureWasm) { + clz32Dynamic0(i) + } { + if (js.typeOf(js.Dynamic.global.Math.clz32) == "function") { + js.Math.clz32(i) } else { - var r = 1 - if ((x & 0xffff0000) == 0) { x <<= 16; r += 16 } - if ((x & 0xff000000) == 0) { x <<= 8; r += 8 } - if ((x & 0xf0000000) == 0) { x <<= 4; r += 4 } - if ((x & 0xc0000000) == 0) { x <<= 2; r += 2 } - r + (x >> 31) + clz32Dynamic0(i) } } } + @inline private def clz32Dynamic0(i: scala.Int) = { + // See Hacker's Delight, Section 5-3 + var x = i + if (x == 0) { + 32 + } else { + var r = 1 + if ((x & 0xffff0000) == 0) { x <<= 16; r += 16 } + if ((x & 0xff000000) == 0) { x <<= 8; r += 8 } + if ((x & 0xf0000000) == 0) { x <<= 4; r += 4 } + if ((x & 0xc0000000) == 0) { x <<= 2; r += 2 } + r + (x >> 31) + } + } + // Wasm intrinsic @inline def numberOfTrailingZeros(i: scala.Int): scala.Int = if (i == 0) 32 diff --git a/javalib/src/main/scala/java/lang/Math.scala b/javalib/src/main/scala/java/lang/Math.scala index a0a45f2b4d..3e118d3884 100644 --- a/javalib/src/main/scala/java/lang/Math.scala +++ b/javalib/src/main/scala/java/lang/Math.scala @@ -17,7 +17,7 @@ import scala.scalajs.js import js.Dynamic.{ global => g } import scala.scalajs.LinkingInfo -import scala.scalajs.LinkingInfo.ESVersion +import scala.scalajs.LinkingInfo.{ESVersion, linkTimeIf} object Math { final val E = 2.718281828459045 @@ -30,8 +30,19 @@ object Math { @inline def abs(a: scala.Long): scala.Long = if (a < 0) -a else a // Wasm intrinsics - @inline def abs(a: scala.Float): scala.Float = js.Math.abs(a).toFloat - @inline def abs(a: scala.Double): scala.Double = js.Math.abs(a) + @inline def abs(a: scala.Float): scala.Float = + linkTimeIf(LinkingInfo.targetPureWasm) { + Float.intBitsToFloat(Float.floatToIntBits(a) & ~Int.MinValue) + } { + js.Math.abs(a).toFloat + } + + @inline def abs(a: scala.Double): scala.Double = + linkTimeIf(LinkingInfo.targetPureWasm) { + Double.longBitsToDouble(Double.doubleToLongBits(a) & ~scala.Long.MinValue) + } { + js.Math.abs(a) + } @inline def max(a: scala.Int, b: scala.Int): scala.Int = if (a > b) a else b @inline def max(a: scala.Long, b: scala.Long): scala.Long = if (a > b) a else b @@ -154,8 +165,11 @@ object Math { @inline def atan2(y: scala.Double, x: scala.Double): scala.Double = js.Math.atan2(y, x) @inline def random(): scala.Double = - if (LinkingInfo.targetPureWasm) WasmSystem.random() - else js.Math.random() + linkTimeIf(LinkingInfo.targetPureWasm) { + WasmSystem.random() + } { + js.Math.random() + } @inline def toDegrees(a: scala.Double): scala.Double = a * 180.0 / PI @inline def toRadians(a: scala.Double): scala.Double = a / 180.0 * PI diff --git a/javalib/src/main/scala/java/lang/System.scala b/javalib/src/main/scala/java/lang/System.scala index c714a9abd4..5690d63500 100644 --- a/javalib/src/main/scala/java/lang/System.scala +++ b/javalib/src/main/scala/java/lang/System.scala @@ -68,8 +68,11 @@ object System { @inline def currentTimeMillis(): scala.Long = - if (LinkingInfo.targetPureWasm) WasmSystem.currentTimeMillis() - else js.Date.now().toLong + LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + WasmSystem.currentTimeMillis() + } { + js.Date.now().toLong + } private object NanoTime { val getHighPrecisionTime: js.Function0[scala.Double] = { @@ -91,8 +94,11 @@ object System { @inline def nanoTime(): scala.Long = - if (LinkingInfo.targetPureWasm) WasmSystem.nanoTime() - else (NanoTime.getHighPrecisionTime() * 1000000).toLong + LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + WasmSystem.nanoTime() + } { + (NanoTime.getHighPrecisionTime() * 1000000).toLong + } // arraycopy ---------------------------------------------------------------- @@ -385,9 +391,9 @@ private final class JSConsoleBasedPrintStream(isErr: scala.Boolean) override def close(): Unit = () private def doWriteLine(line: String): Unit = { - if (LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf (LinkingInfo.targetPureWasm) { WasmSystem.print(line) - } else { + } { import js.DynamicImplicits.truthValue if (js.typeOf(global.console) != "undefined") { diff --git a/javalib/src/main/scala/java/lang/Throwables.scala b/javalib/src/main/scala/java/lang/Throwables.scala index 3c2d48b0ac..e0c52ee52d 100644 --- a/javalib/src/main/scala/java/lang/Throwables.scala +++ b/javalib/src/main/scala/java/lang/Throwables.scala @@ -35,7 +35,7 @@ class Throwable protected (s: String, private var e: Throwable, */ private[this] var suppressed: Array[Throwable] = _ - if (writableStackTrace && !LinkingInfo.targetPureWasm) + if (writableStackTrace) fillInStackTrace() def initCause(cause: Throwable): Throwable = { @@ -48,30 +48,41 @@ class Throwable protected (s: String, private var e: Throwable, def getLocalizedMessage(): String = getMessage() def fillInStackTrace(): Throwable = { - if (!LinkingInfo.targetPureWasm) jsErrorForStackTrace = StackTrace.captureJSError(this) - this + LinkingInfo.linkTimeIf(!LinkingInfo.targetPureWasm) { + jsErrorForStackTrace = StackTrace.captureJSError(this) + this + } { + this + } } def getStackTrace(): Array[StackTraceElement] = { if (stackTrace eq null) { - if (!LinkingInfo.targetPureWasm && writableStackTrace) - stackTrace = StackTrace.extract(jsErrorForStackTrace) - else + LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { stackTrace = new Array[StackTraceElement](0) + } { + if (writableStackTrace) + stackTrace = StackTrace.extract(jsErrorForStackTrace) + else + stackTrace = new Array[StackTraceElement](0) + } } stackTrace } def setStackTrace(stackTrace: Array[StackTraceElement]): Unit = { - if (writableStackTrace && !LinkingInfo.targetPureWasm) { - var i = 0 - while (i < stackTrace.length) { - if (stackTrace(i) eq null) - throw new NullPointerException() - i += 1 - } + LinkingInfo.linkTimeIf(!LinkingInfo.targetPureWasm) { + if (writableStackTrace) { + var i = 0 + while (i < stackTrace.length) { + if (stackTrace(i) eq null) + throw new NullPointerException() + i += 1 + } - this.stackTrace = stackTrace.clone() + this.stackTrace = stackTrace.clone() + } + } { } } diff --git a/javalib/src/main/scala/java/lang/_String.scala b/javalib/src/main/scala/java/lang/_String.scala index d2599c17e1..e9acc5f13b 100644 --- a/javalib/src/main/scala/java/lang/_String.scala +++ b/javalib/src/main/scala/java/lang/_String.scala @@ -59,11 +59,15 @@ final class _String private () // scalastyle:ignore // Wasm intrinsic def codePointAt(index: Int): Int = { - if (LinkingInfo.esVersion >= ESVersion.ES2015 && !LinkingInfo.targetPureWasm) { - charAt(index) // bounds check - this.asInstanceOf[js.Dynamic].codePointAt(index).asInstanceOf[Int] - } else { + LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { Character.codePointAtImpl(this, index) + } { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { + charAt(index) // bounds check + this.asInstanceOf[js.Dynamic].codePointAt(index).asInstanceOf[Int] + } else { + Character.codePointAtImpl(this, index) + } } } @@ -167,13 +171,15 @@ final class _String private () // scalastyle:ignore @inline def endsWith(suffix: String): scala.Boolean = { - if (LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { regionMatches(thisString.length() - suffix.length, suffix, 0, suffix.length) - } else if (LinkingInfo.esVersion >= ESVersion.ES2015) { - suffix.getClass() // null check - thisString.asInstanceOf[js.Dynamic].endsWith(suffix).asInstanceOf[scala.Boolean] - } else { - thisString.jsSubstring(this.length() - suffix.length()) == suffix + } { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { + suffix.getClass() // null check + thisString.asInstanceOf[js.Dynamic].endsWith(suffix).asInstanceOf[scala.Boolean] + } else { + thisString.jsSubstring(this.length() - suffix.length()) == suffix + } } } @@ -210,15 +216,15 @@ final class _String private () // scalastyle:ignore indexOf(Character.toString(ch), fromIndex) def indexOf(str: String): Int = { - if (LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { indexOf(str, 0) - } else { + } { thisString.jsIndexOf(str) } } def indexOf(str: String, fromIndex: Int): Int = { - if (LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { val thisLen = thisString.length() val strLen = str.length() @@ -243,7 +249,7 @@ final class _String private () // scalastyle:ignore found } } - } else { + } { thisString.jsIndexOf(str, fromIndex) } } @@ -267,10 +273,10 @@ final class _String private () // scalastyle:ignore @inline def lastIndexOf(str: String): Int = { - if (LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { val thisLen = thisString.length() lastIndexOf(str, thisLen) - } else { + } { thisString.jsLastIndexOf(str) } @@ -279,42 +285,44 @@ final class _String private () // scalastyle:ignore @inline def lastIndexOf(str: String, fromIndex: Int): Int = if (fromIndex < 0) -1 - else if (LinkingInfo.targetPureWasm) { - val thisLen = thisString.length() - val strLen = str.length() + else { + LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + val thisLen = thisString.length() + val strLen = str.length() + + if (fromIndex < 0) { + -1 + } else if (strLen == 0) { + Math.min(fromIndex, thisLen) + } else { + val maxStartIndex = Math.min(fromIndex, thisLen - strLen) + var i = maxStartIndex + var found = -1 - if (fromIndex < 0) { - -1 - } else if (strLen == 0) { - Math.min(fromIndex, thisLen) - } else { - val maxStartIndex = Math.min(fromIndex, thisLen - strLen) - var i = maxStartIndex - var found = -1 + while (i >= 0) { + var j = 0 + var matches = true - while (i >= 0) { - var j = 0 - var matches = true + while (j < strLen && matches) { + if (thisString.charAt(i + j) != str.charAt(j)) { + matches = false + } + j += 1 + } - while (j < strLen && matches) { - if (thisString.charAt(i + j) != str.charAt(j)) { - matches = false + if (matches) { + found = i + i = -1 // exit the loop + } else { + i -= 1 } - j += 1 } - if (matches) { - found = i - i = -1 // exit the loop - } else { - i -= 1 - } + found } - - found + } { + thisString.jsLastIndexOf(str, fromIndex) } - } else { - thisString.jsLastIndexOf(str, fromIndex) } @inline @@ -349,26 +357,30 @@ final class _String private () // scalastyle:ignore def repeat(count: Int): String = { if (count < 0) { throw new IllegalArgumentException - } else if (LinkingInfo.esVersion >= ESVersion.ES2015 && !LinkingInfo.targetPureWasm) { - /* This will throw a `js.RangeError` if `count` is too large, instead of - * an `OutOfMemoryError`. That's fine because the behavior of `repeat` is - * not specified for `count` too large. - */ - this.asInstanceOf[js.Dynamic].repeat(count).asInstanceOf[String] - } else if (thisString == "" || count == 0) { - "" - } else if (thisString.length > (Int.MaxValue / count)) { - throw new OutOfMemoryError } else { - var str = thisString - val resultLength = thisString.length * count - var remainingIters = 31 - Integer.numberOfLeadingZeros(count) - while (remainingIters > 0) { - str += str - remainingIters -= 1 + LinkingInfo.linkTimeIf(LinkingInfo.esVersion >= ESVersion.ES2015 && !LinkingInfo.targetPureWasm) { + /* This will throw a `js.RangeError` if `count` is too large, instead of + * an `OutOfMemoryError`. That's fine because the behavior of `repeat` is + * not specified for `count` too large. + */ + this.asInstanceOf[js.Dynamic].repeat(count).asInstanceOf[String] + } { + if (thisString == "" || count == 0) { + "" + } else if (thisString.length > (Int.MaxValue / count)) { + throw new OutOfMemoryError + } else { + var str = thisString + val resultLength = thisString.length * count + var remainingIters = 31 - Integer.numberOfLeadingZeros(count) + while (remainingIters > 0) { + str += str + remainingIters -= 1 + } + str += str.jsSubstring(0, resultLength - str.length) + str + } } - str += str.jsSubstring(0, resultLength - str.length) - str } } @@ -395,27 +407,31 @@ final class _String private () // scalastyle:ignore @inline def startsWith(prefix: String): scala.Boolean = { - if (LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { regionMatches(0, prefix, 0, prefix.length()) - } else if (LinkingInfo.esVersion >= ESVersion.ES2015) { - prefix.getClass() // null check - thisString.asInstanceOf[js.Dynamic].startsWith(prefix).asInstanceOf[scala.Boolean] - } else { - thisString.jsSubstring(0, prefix.length()) == prefix + } { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { + prefix.getClass() // null check + thisString.asInstanceOf[js.Dynamic].startsWith(prefix).asInstanceOf[scala.Boolean] + } else { + thisString.jsSubstring(0, prefix.length()) == prefix + } } } @inline def startsWith(prefix: String, toffset: Int): scala.Boolean = { - if (LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { regionMatches(toffset, prefix, 0, prefix.length()) - } else if (LinkingInfo.esVersion >= ESVersion.ES2015) { - prefix.getClass() // null check - (toffset <= length() && toffset >= 0 && - thisString.asInstanceOf[js.Dynamic].startsWith(prefix, toffset).asInstanceOf[scala.Boolean]) - } else { - (toffset <= length() && toffset >= 0 && - thisString.jsSubstring(toffset, toffset + prefix.length()) == prefix) + } { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { + prefix.getClass() // null check + (toffset <= length() && toffset >= 0 && + thisString.asInstanceOf[js.Dynamic].startsWith(prefix, toffset).asInstanceOf[scala.Boolean]) + } else { + (toffset <= length() && toffset >= 0 && + thisString.jsSubstring(toffset, toffset + prefix.length()) == prefix) + } } } @@ -430,8 +446,11 @@ final class _String private () // scalastyle:ignore if (beginIndex < 0 || beginIndex > length()) charAt(beginIndex) - if (LinkingInfo.targetPureWasm) this.substring(beginIndex, thisString.length) - else thisString.jsSubstring(beginIndex) + LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + this.substring(beginIndex, thisString.length) + } { + thisString.jsSubstring(beginIndex) + } } // Wasm intrinsic @@ -445,7 +464,7 @@ final class _String private () // scalastyle:ignore if (endIndex < beginIndex) charAt(-1) - if (LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { val length = thisString.length val builder = new StringBuilder(endIndex - beginIndex) var i = beginIndex @@ -454,7 +473,7 @@ final class _String private () // scalastyle:ignore i += 1 } builder.toString - } else { + } { thisString.jsSubstring(beginIndex, endIndex) } } @@ -637,10 +656,11 @@ final class _String private () // scalastyle:ignore @inline def toLowerCase(): String = - if (LinkingInfo.targetPureWasm) + LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { this.asInstanceOf[_String].toLowerCaseImpl() - else + } { this.asInstanceOf[js.Dynamic].toLowerCase().asInstanceOf[String] + } private def toLowerCaseImpl(): String = { replaceCharsAtIndex { i => @@ -735,13 +755,13 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { @inline def toUpperCase(): String = - if (LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { replaceCharsAtIndex { i => val c = this.charAt(i) if (c < 0x80) null // fast-forward ASCII characters else StringSpecialCasing.toUpperCase.get(c) }.asInstanceOf[_String].toCase(true) - } else { + } { this.asInstanceOf[js.Dynamic].toUpperCase().asInstanceOf[String] } diff --git a/javalib/src/main/scala/java/nio/charset/CoderResult.scala b/javalib/src/main/scala/java/nio/charset/CoderResult.scala index 4aa8969d73..26ab912cc0 100644 --- a/javalib/src/main/scala/java/nio/charset/CoderResult.scala +++ b/javalib/src/main/scala/java/nio/charset/CoderResult.scala @@ -18,7 +18,7 @@ import java.lang.Utils._ import java.nio._ import scala.scalajs.js -import scala.scalajs.LinkingInfo.targetPureWasm +import scala.scalajs.LinkingInfo.{targetPureWasm, linkTimeIf} class CoderResult private (kind: Int, _length: Int) { import CoderResult._ @@ -61,11 +61,17 @@ object CoderResult { // This is a sparse array private val uniqueMalformedJS = - if (targetPureWasm) null - else js.Array[js.UndefOr[CoderResult]]() + linkTimeIf(!targetPureWasm) { + js.Array[js.UndefOr[CoderResult]]() + } { + null + } private val uniqueMalformedWasm = - if (targetPureWasm) new java.util.HashMap[Int, CoderResult]() - else null + linkTimeIf(targetPureWasm) { + new java.util.HashMap[Int, CoderResult]() + } { + null + } private val Unmappable1 = new CoderResult(Unmappable, 1) private val Unmappable2 = new CoderResult(Unmappable, 2) @@ -74,11 +80,17 @@ object CoderResult { // This is a sparse array private val uniqueUnmappableJS = - if (targetPureWasm) null - else js.Array[js.UndefOr[CoderResult]]() + linkTimeIf(!targetPureWasm) { + js.Array[js.UndefOr[CoderResult]]() + } { + null + } private val uniqueUnmappableWasm = - if (targetPureWasm) new java.util.HashMap[Int, CoderResult]() - else null + linkTimeIf(targetPureWasm) { + new java.util.HashMap[Int, CoderResult]() + } { + null + } @inline def malformedForLength(length: Int): CoderResult = (length: @switch) match { case 1 => Malformed1 @@ -89,9 +101,9 @@ object CoderResult { } private def malformedForLengthImpl(length: Int): CoderResult = { - if (targetPureWasm) { + linkTimeIf(targetPureWasm) { uniqueMalformedWasm.computeIfAbsent(length, _ => new CoderResult(Malformed, length)) - } else { + } { undefOrFold(uniqueMalformedJS(length)) { () => val result = new CoderResult(Malformed, length) uniqueMalformedJS(length) = result @@ -111,9 +123,9 @@ object CoderResult { } private def unmappableForLengthImpl(length: Int): CoderResult = { - if (targetPureWasm) { + linkTimeIf(targetPureWasm) { uniqueUnmappableWasm.computeIfAbsent(length, _ => new CoderResult(Unmappable, length)) - } else { + } { undefOrFold(uniqueUnmappableJS(length)) { () => val result = new CoderResult(Unmappable, length) uniqueUnmappableJS(length) = result diff --git a/javalib/src/main/scala/java/util/ArrayList.scala b/javalib/src/main/scala/java/util/ArrayList.scala index 1c67de682b..96a3e36af7 100644 --- a/javalib/src/main/scala/java/util/ArrayList.scala +++ b/javalib/src/main/scala/java/util/ArrayList.scala @@ -17,7 +17,8 @@ import java.lang.Utils._ import java.util.ScalaOps._ import scala.scalajs._ -import scala.scalajs.LinkingInfo.isWebAssembly +import scala.scalajs.LinkingInfo.{isWebAssembly, linkTimeIf} +import scala.scalajs.js.Object.is class ArrayList[E] private (innerInit: AnyRef, private var _size: Int) extends AbstractList[E] with RandomAccess with Cloneable with Serializable { @@ -32,20 +33,29 @@ class ArrayList[E] private (innerInit: AnyRef, private var _size: Int) */ private val innerJS: js.Array[E] = - if (isWebAssembly) null - else innerInit.asInstanceOf[js.Array[E]] + linkTimeIf(!isWebAssembly) { + innerInit.asInstanceOf[js.Array[E]] + } { + null + } private var innerWasm: Array[AnyRef] = - if (!isWebAssembly) null - else innerInit.asInstanceOf[Array[AnyRef]] + linkTimeIf(isWebAssembly) { + innerInit.asInstanceOf[Array[AnyRef]] + } { + null + } def this(initialCapacity: Int) = { this( { if (initialCapacity < 0) throw new IllegalArgumentException - if (isWebAssembly) new Array[AnyRef](initialCapacity) - else new js.Array[E] + linkTimeIf(isWebAssembly){ + (new Array[AnyRef](initialCapacity)).asInstanceOf[AnyRef] + } { + new js.Array[E] + } }, 0 ) @@ -59,58 +69,66 @@ class ArrayList[E] private (innerInit: AnyRef, private var _size: Int) } def trimToSize(): Unit = { - if (isWebAssembly) + linkTimeIf(isWebAssembly) { resizeTo(size()) + } { + } // We ignore this in JS as js.Array doesn't support explicit pre-allocation } def ensureCapacity(minCapacity: Int): Unit = { - if (isWebAssembly) { + linkTimeIf(isWebAssembly) { if (innerWasm.length < minCapacity) { if (minCapacity > (1 << 30)) resizeTo(minCapacity) else resizeTo(((1 << 31) >>> (Integer.numberOfLeadingZeros(minCapacity - 1)) - 1)) } - } + } {} // We ignore this in JS as js.Array doesn't support explicit pre-allocation } def size(): Int = - if (isWebAssembly) _size - else innerJS.length + linkTimeIf(isWebAssembly) { + _size + } { + innerJS.length + } override def clone(): AnyRef = { - if (isWebAssembly) + linkTimeIf(isWebAssembly) { new ArrayList(innerWasm.clone(), size()) - else + } { new ArrayList(innerJS.jsSlice(0), 0) + } } def get(index: Int): E = { checkIndexInBounds(index) - if (isWebAssembly) + linkTimeIf(isWebAssembly) { innerWasm(index).asInstanceOf[E] - else + } { innerJS(index) + } } override def set(index: Int, element: E): E = { val e = get(index) - if (isWebAssembly) + linkTimeIf(isWebAssembly) { innerWasm(index) = element.asInstanceOf[AnyRef] - else + } { innerJS(index) = element + } e } override def add(e: E): Boolean = { - if (isWebAssembly) { + linkTimeIf(isWebAssembly) { if (size() >= innerWasm.length) expand() innerWasm(size()) = e.asInstanceOf[AnyRef] _size += 1 - } else { + } { innerJS.push(e) } true @@ -118,35 +136,35 @@ class ArrayList[E] private (innerInit: AnyRef, private var _size: Int) override def add(index: Int, element: E): Unit = { checkIndexOnBounds(index) - if (isWebAssembly) { + linkTimeIf(isWebAssembly) { if (size() >= innerWasm.length) expand() System.arraycopy(innerWasm, index, innerWasm, index + 1, size() - index) innerWasm(index) = element.asInstanceOf[AnyRef] _size += 1 - } else { + } { innerJS.splice(index, 0, element) } } override def remove(index: Int): E = { checkIndexInBounds(index) - if (isWebAssembly) { + linkTimeIf(isWebAssembly) { val removed = innerWasm(index).asInstanceOf[E] System.arraycopy(innerWasm, index + 1, innerWasm, index, size() - index - 1) innerWasm(size - 1) = null // free reference for GC _size -= 1 removed - } else { + } { arrayRemoveAndGet(innerJS, index) } } override def clear(): Unit = - if (isWebAssembly) { + linkTimeIf(isWebAssembly) { Arrays.fill(innerWasm, null) // free references for GC _size = 0 - } else { + } { innerJS.length = 0 } @@ -154,12 +172,12 @@ class ArrayList[E] private (innerInit: AnyRef, private var _size: Int) c match { case other: ArrayList[_] => checkIndexOnBounds(index) - if (isWebAssembly) { + linkTimeIf(isWebAssembly) { ensureCapacity(size() + other.size()) System.arraycopy(innerWasm, index, innerWasm, index + other.size(), size() - index) System.arraycopy(other.innerWasm, 0, innerWasm, index, other.size()) _size += c.size() - } else { + } { innerJS.splice(index, 0, other.innerJS.toSeq: _*) } other.size() > 0 @@ -170,14 +188,14 @@ class ArrayList[E] private (innerInit: AnyRef, private var _size: Int) override protected def removeRange(fromIndex: Int, toIndex: Int): Unit = { if (fromIndex < 0 || toIndex > size() || toIndex < fromIndex) throw new IndexOutOfBoundsException() - if (isWebAssembly) { + linkTimeIf(isWebAssembly) { if (fromIndex != toIndex) { System.arraycopy(innerWasm, toIndex, innerWasm, fromIndex, size() - toIndex) val newSize = size() - toIndex + fromIndex Arrays.fill(innerWasm, newSize, size(), null) // free references for GC _size = newSize } - } else { + } { innerJS.splice(fromIndex, toIndex - fromIndex) } } diff --git a/javalib/src/main/scala/java/util/Random.scala b/javalib/src/main/scala/java/util/Random.scala index 54a6197d0e..f556161600 100644 --- a/javalib/src/main/scala/java/util/Random.scala +++ b/javalib/src/main/scala/java/util/Random.scala @@ -16,6 +16,7 @@ import scala.annotation.tailrec import scala.scalajs.js import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.linkTimeIf import java.util.random.RandomGenerator @@ -43,9 +44,9 @@ class Random(seed_in: Long) def setSeed(seed_in: Long): Unit = { val seed = ((seed_in ^ 0x5DEECE66DL) & ((1L << 48) - 1)) // as documented - if (LinkingInfo.isWebAssembly) { + linkTimeIf(LinkingInfo.isWebAssembly) { this.seed = seed - } else { + } { seedHi = (seed >>> 24).toInt seedLo = seed.toInt & ((1 << 24) - 1) } @@ -54,8 +55,11 @@ class Random(seed_in: Long) @noinline protected def next(bits: Int): Int = - if (LinkingInfo.isWebAssembly) nextWasm(bits) - else nextJS(bits) + linkTimeIf(LinkingInfo.isWebAssembly) { + nextWasm(bits) + } { + nextJS(bits) + } @inline private def nextWasm(bits: Int): Int = { diff --git a/junit-runtime/src/main/scala/org/junit/Assert.scala b/junit-runtime/src/main/scala/org/junit/Assert.scala index bf430d0cb6..a86f2dadca 100644 --- a/junit-runtime/src/main/scala/org/junit/Assert.scala +++ b/junit-runtime/src/main/scala/org/junit/Assert.scala @@ -407,12 +407,12 @@ object Assert { actualThrown) } - if (LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { throw new AssertionError( buildPrefix + "expecte " + formatClass(expectedThrowable) + " to be thrown, but nothing was thrown" ) - } else { + } { throw new AssertionError( buildPrefix + String.format("expected %s to be thrown, but nothing was thrown", formatClass(expectedThrowable))) diff --git a/library/src/main/scala/scala/scalajs/LinkingInfo.scala b/library/src/main/scala/scala/scalajs/LinkingInfo.scala index fee8947623..57802c797b 100644 --- a/library/src/main/scala/scala/scalajs/LinkingInfo.scala +++ b/library/src/main/scala/scala/scalajs/LinkingInfo.scala @@ -258,7 +258,7 @@ object LinkingInfo { def isWebAssembly: Boolean = linkTimePropertyBoolean("core/isWebAssembly") - @inline + @inline @linkTimeProperty("core/targetPureWasm") def targetPureWasm: Boolean = linkTimePropertyBoolean("core/targetPureWasm") diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala index 7cf25119fc..49ed095a7a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala @@ -227,6 +227,8 @@ object Analysis { from: From ) extends Error + final case class JSInteropInPureWasm(from: From) extends Error + sealed trait From final case class FromMethod(methodInfo: MethodInfo) extends From final case class FromDispatch(classInfo: ClassInfo, methodName: MethodName) extends From @@ -291,6 +293,8 @@ object Analysis { "Uses an orphan await (outside of an async block) without targeting WebAssembly" case InvalidLinkTimeProperty(name, tpe, _) => s"Uses invalid link-time property ${name} of type ${tpe}" + case JSInteropInPureWasm(_) => + s"Uses JS interop with targetPureWasm = true" } logger.log(level, headMsg) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala index 429efc2051..f8be23f853 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala @@ -51,7 +51,8 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean, private val linkTimeProperties = LinkTimeProperties.fromCoreSpec(config.coreSpec) private val infoLoader: InfoLoader = - new InfoLoader(irLoader, checkIRFor, linkTimeProperties) + new InfoLoader(irLoader, checkIRFor, linkTimeProperties, + config.coreSpec.wasmFeatures.targetPureWasm) def computeReachability(moduleInitializers: Seq[ModuleInitializer], symbolRequirements: SymbolRequirement, logger: Logger)(implicit ec: ExecutionContext): Future[Analysis] = { @@ -1369,6 +1370,9 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean, def needsDesugaring: Boolean = (data.globalFlags & ReachabilityInfo.FlagNeedsDesugaring) != 0 + private val usedJSInPureWasm: Boolean = + (data.globalFlags & ReachabilityInfo.FlagUsedJSInPureWasm) != 0 + /** Throws MatchError if `!isDefaultBridge`. */ def defaultBridgeTarget: ClassName = (syntheticKind: @unchecked) match { case MethodSyntheticKind.DefaultBridge(target) => target @@ -1382,6 +1386,9 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean, _calledFrom ::= from if (!_isReachable.getAndSet(true)) { + if (usedJSInPureWasm) + _errors ::= JSInteropInPureWasm(from) + _isAbstractReachable.set(true) doReach() } @@ -1391,6 +1398,9 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean, assert(namespace == MemberNamespace.Public) if (!_isAbstractReachable.getAndSet(true)) { + if (usedJSInPureWasm) + _errors ::= JSInteropInPureWasm(from) + checkExistent() _calledFrom ::= from } @@ -1410,6 +1420,9 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean, _instantiatedSubclasses ::= inClass if (!_isReachable.getAndSet(true)) { + if (usedJSInPureWasm) + _errors ::= JSInteropInPureWasm(from) + _isAbstractReachable.set(true) doReach() } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/InfoLoader.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/InfoLoader.scala index 92a2e35a8d..152a7097fb 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/InfoLoader.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/InfoLoader.scala @@ -30,9 +30,10 @@ import org.scalajs.linker.CollectionsCompat.MutableMapCompatOps import Platform.emptyThreadSafeMap private[analyzer] final class InfoLoader(irLoader: IRLoader, - checkIRFor: Option[CheckingPhase], linkTimeProperties: LinkTimeProperties) { + checkIRFor: Option[CheckingPhase], linkTimeProperties: LinkTimeProperties, + targetPureWasm: Boolean) { - private val generator = new Infos.InfoGenerator(linkTimeProperties) + private val generator = new Infos.InfoGenerator(linkTimeProperties, targetPureWasm) private var logger: Logger = _ private val cache = emptyThreadSafeMap[ClassName, InfoLoader.ClassInfoCache] diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala index a07dcc8f5c..0335cbcce6 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala @@ -125,6 +125,7 @@ object Infos { final val FlagUsedOrphanAwait = 1 << 5 final val FlagUsedClassSuperClass = 1 << 6 final val FlagNeedsDesugaring = 1 << 7 + final val FlagUsedJSInPureWasm = 1 << 8 } /** Things from a given class that are reached by one method. */ @@ -408,6 +409,9 @@ object Infos { def markNeedsDesugaring(): this.type = setFlag(ReachabilityInfo.FlagNeedsDesugaring) + def markUsedJSInPureWasm(): this.type = + setFlag(ReachabilityInfo.FlagUsedJSInPureWasm) + def addReferencedLinkTimeProperty(linkTimeProperty: LinkTimeProperty): this.type = { markNeedsDesugaring() linkTimeProperties.append((linkTimeProperty.name, linkTimeProperty.tpe)) @@ -539,7 +543,8 @@ object Infos { } } - final class InfoGenerator(linkTimeProperties: LinkTimeProperties) { + final class InfoGenerator(linkTimeProperties: LinkTimeProperties, + targetPureWasm: Boolean) { def genReferencedFieldClasses(fields: List[AnyFieldDef]): Map[FieldName, ClassName] = { val builder = Map.newBuilder[FieldName, ClassName] @@ -565,25 +570,29 @@ object Infos { * [[org.scalajs.ir.Trees.MethodDef Trees.MethodDef]]. */ def generateMethodInfo(methodDef: MethodDef): MethodInfo = - new GenInfoTraverser(methodDef.version, linkTimeProperties).generateMethodInfo(methodDef) + new GenInfoTraverser(methodDef.version, linkTimeProperties, targetPureWasm) + .generateMethodInfo(methodDef) /** Generates the [[ReachabilityInfo]] of a * [[org.scalajs.ir.Trees.JSConstructorDef Trees.JSConstructorDef]]. */ def generateJSConstructorInfo(ctorDef: JSConstructorDef): ReachabilityInfo = - new GenInfoTraverser(ctorDef.version, linkTimeProperties).generateJSConstructorInfo(ctorDef) + new GenInfoTraverser(ctorDef.version, linkTimeProperties, targetPureWasm) + .generateJSConstructorInfo(ctorDef) /** Generates the [[ReachabilityInfo]] of a * [[org.scalajs.ir.Trees.JSMethodDef Trees.JSMethodDef]]. */ def generateJSMethodInfo(methodDef: JSMethodDef): ReachabilityInfo = - new GenInfoTraverser(methodDef.version, linkTimeProperties).generateJSMethodInfo(methodDef) + new GenInfoTraverser(methodDef.version, linkTimeProperties, targetPureWasm) + .generateJSMethodInfo(methodDef) /** Generates the [[ReachabilityInfo]] of a * [[org.scalajs.ir.Trees.JSPropertyDef Trees.JSPropertyDef]]. */ def generateJSPropertyInfo(propertyDef: JSPropertyDef): ReachabilityInfo = - new GenInfoTraverser(propertyDef.version, linkTimeProperties).generateJSPropertyInfo(propertyDef) + new GenInfoTraverser(propertyDef.version, linkTimeProperties, targetPureWasm) + .generateJSPropertyInfo(propertyDef) def generateJSMethodPropDefInfo(member: JSMethodPropDef): ReachabilityInfo = member match { case methodDef: JSMethodDef => generateJSMethodInfo(methodDef) @@ -593,7 +602,7 @@ object Infos { /** Generates the [[MethodInfo]] for the top-level exports. */ def generateTopLevelExportInfo(enclosingClass: ClassName, topLevelExportDef: TopLevelExportDef): TopLevelExportInfo = { - val info = new GenInfoTraverser(Version.Unversioned, linkTimeProperties) + val info = new GenInfoTraverser(Version.Unversioned, linkTimeProperties, targetPureWasm) .generateTopLevelExportInfo(enclosingClass, topLevelExportDef) new TopLevelExportInfo(info, ModuleID(topLevelExportDef.moduleID), @@ -601,11 +610,12 @@ object Infos { } def generateComponentNativeMember(member: ComponentNativeMemberDef): MethodInfo = - new GenInfoTraverser(Version.Unversioned, linkTimeProperties).generateComponentNativeMember(member) + new GenInfoTraverser(Version.Unversioned, linkTimeProperties, targetPureWasm) + .generateComponentNativeMember(member) } private final class GenInfoTraverser(version: Version, - linkTimeProperties: LinkTimeProperties) extends Traverser { + linkTimeProperties: LinkTimeProperties, targetPureWasm: Boolean) extends Traverser { private val builder = new ReachabilityInfoBuilder(version) @@ -778,6 +788,9 @@ object Infos { override def traverse(tree: Tree): Unit = { builder.maybeAddReferencedClass(tree.tpe) + if (targetPureWasm) + checkJSInterop(tree) + tree match { /* Do not call super.traverse() so that fields are not also marked as * read. @@ -989,6 +1002,21 @@ object Infos { super.traverse(tree) } } - } + private def checkJSInterop(tree: Tree): Unit = { + tree match { + case _:JSNew | _:JSSelect | _:JSFunctionApply | _:JSMethodApply | + _:JSImportCall | _:JSImportMeta | _:LoadJSConstructor | + _:LoadJSModule | _:SelectJSNativeMember | _:JSDelete | + _:JSUnaryOp | _:JSBinaryOp | _:JSArrayConstr | _:JSObjectConstr | + _:JSGlobalRef | _: JSTypeOfGlobalRef | _:CreateJSClass | + _:JSPrivateSelect | _:JSSuperSelect | _:JSSuperMethodCall | + _:JSNewTarget | _:JSSuperConstructorCall => + builder.markUsedJSInPureWasm() + case closure: Closure if !closure.flags.typed => + builder.markUsedJSInPureWasm() + case _ => + } + } + } } diff --git a/scalalib/overrides-2.12/scala/collection/mutable/ArrayBuilder.scala b/scalalib/overrides-2.12/scala/collection/mutable/ArrayBuilder.scala index fc3e0ee4a6..15f334ca0f 100644 --- a/scalalib/overrides-2.12/scala/collection/mutable/ArrayBuilder.scala +++ b/scalalib/overrides-2.12/scala/collection/mutable/ArrayBuilder.scala @@ -15,6 +15,7 @@ import scala.runtime.BoxedUnit import scala.scalajs.js import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.linkTimeIf /** A builder class for arrays. * @@ -37,8 +38,11 @@ object ArrayBuilder { */ @inline def make[T: ClassTag](): ArrayBuilder[T] = - if (LinkingInfo.isWebAssembly) makeForWasm() - else makeForJS() + linkTimeIf(LinkingInfo.isWebAssembly) { + makeForWasm() + } { + makeForJS() + } /** Implementation of `make` for JS. */ @inline diff --git a/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala b/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala index 531a6438a2..1117fb6578 100644 --- a/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala +++ b/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala @@ -18,6 +18,7 @@ import scala.runtime.BoxedUnit import scala.scalajs.js import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.linkTimeIf /** A builder class for arrays. * @@ -90,8 +91,11 @@ object ArrayBuilder { */ @inline def make[T: ClassTag]: ArrayBuilder[T] = - if (LinkingInfo.isWebAssembly) makeForWasm - else makeForJS + linkTimeIf(LinkingInfo.isWebAssembly) { + makeForWasm + } { + makeForJS + } /** Implementation of `make` for JS. */ @inline diff --git a/scalalib/overrides/scala/runtime/BoxesRunTime.scala b/scalalib/overrides/scala/runtime/BoxesRunTime.scala index b4f5285440..e1a4f43fa6 100644 --- a/scalalib/overrides/scala/runtime/BoxesRunTime.scala +++ b/scalalib/overrides/scala/runtime/BoxesRunTime.scala @@ -2,6 +2,9 @@ package scala.runtime import scala.math.ScalaNumber +import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.linkTimeIf + /* The declaration of the class is only to make the JVM back-end happy when * compiling the scalalib. */ @@ -49,9 +52,10 @@ object BoxesRunTime { def unboxToDouble(d: Any): Double = d.asInstanceOf[Double] def equals(x: Object, y: Object): Boolean = - if (scala.scalajs.LinkingInfo.targetPureWasm) { - equals2(x, y) - } else { + linkTimeIf(LinkingInfo.targetPureWasm) { + if (x eq y) true + else equals2(x, y) + } { if (scala.scalajs.js.special.strictEquals(x, y)) true else equals2(x, y) }