8000 Use linkTimeIf for targetPureWasm · scala-wasm/scala-wasm@16522a3 · GitHub
[go: up one dir, main page]

Skip to content

Commit 16522a3

Browse files
committed
Use linkTimeIf for targetPureWasm
Otherwise, Analyzer reports the `JSInteropInPureWasm` error
1 parent 38683aa commit 16522a3

File tree

13 files changed

+448
-102
lines changed

13 files changed

+448
-102
lines changed

javalib/src/main/scala/java/lang/Character.scala

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import scala.annotation.{tailrec, switch}
1616

1717
import scala.scalajs.js
1818
import scala.scalajs.LinkingInfo
19-
import scala.scalajs.LinkingInfo.ESVersion
19+
import scala.scalajs.LinkingInfo.{ESVersion, linkTimeIf}
2020

2121
import java.lang.constant.Constable
2222
import java.util.{ArrayList, Arrays, HashMap}
@@ -128,25 +128,27 @@ object Character {
128128
if (!isValidCodePoint(codePoint))
129129
throw new IllegalArgumentException()
130130

131-
if (LinkingInfo.targetPureWasm) {
131+
linkTimeIf (LinkingInfo.targetPureWasm) {
132132
if (isBmpCodePoint(codePoint)) {
133133
Character.toString(codePoint.toChar)
134134
} else {
135135
val dst = new Array[Char](2)
136136
toSurrogate(codePoint, dst, 0)
137137
new String(dst)
138138
}
139-
} else if (LinkingInfo.esVersion >= ESVersion.ES2015) {
140-
js.Dynamic.global.String.fromCodePoint(codePoint).asInstanceOf[String]
141-
} else {
142-
if (codePoint < MIN_SUPPLEMENTARY_CODE_POINT) {
143-
js.Dynamic.global.String
144-
.fromCharCode(codePoint)
145-
.asInstanceOf[String]
139+
} {
140+
if (LinkingInfo.esVersion >= ESVersion.ES2015) {
141+
js.Dynamic.global.String.fromCodePoint(codePoint).asInstanceOf[String]
146142
} else {
147-
js.Dynamic.global.String
148-
.fromCharCode(highSurrogate(codePoint).toInt, lowSurrogate(codePoint).toInt)
149-
.asInstanceOf[String]
143+
if (codePoint < MIN_SUPPLEMENTARY_CODE_POINT) {
144+
js.Dynamic.global.String
145+
.fromCharCode(codePoint)
146+
.asInstanceOf[String]
147+
} else {
148+
js.Dynamic.global.String
149+
.fromCharCode(highSurrogate(codePoint).toInt, lowSurrogate(codePoint).toInt)
150+
.asInstanceOf[String]
151+
}
150152
}
151153
}
152154
}

javalib/src/main/scala/java/lang/Double.scala

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import java.lang.constant.{Constable, ConstantDesc}
1616

1717
import scala.scalajs.js
1818
import scala.scalajs.LinkingInfo
19+
import scala.scalajs.LinkingInfo.linkTimeIf
1920

2021
import Utils._
2122

@@ -365,10 +366,11 @@ object Double {
365366
!isNaN(d) && !isInfinite(d)
366367

367368
@inline def hashCode(value: scala.Double): Int = {
368-
if (LinkingInfo.isWebAssembly)
369+
linkTimeIf(LinkingInfo.isWebAssembly) {
369370
hashCodeForWasm(value)
370-
else
371+
} {
371372
FloatingPointBits.numberHashCode(value)
373+
}
372374
}
373375

374376
// See FloatingPointBits for the spec of this computation
@@ -384,11 +386,19 @@ object Double {
384386

385387
// Wasm intrinsic
386388
@inline def longBitsToDouble(bits: scala.Long): scala.Double =
387-
FloatingPointBits.longBitsToDouble(bits)
389+
linkTimeIf(LinkingInfo.targetPureWasm) {
390+
FloatingPointBitsWasm.longBitsToDouble(bits)
391+
} {
392+
FloatingPointBits.longBitsToDouble(bits)
393+
}
388394

389395
// Wasm intrinsic
390396
@inline def doubleToLongBits(value: scala.Double): scala.Long =
391-
FloatingPointBits.doubleToLongBits(value)
397+
linkTimeIf(LinkingInfo.targetPureWasm) {
398+
FloatingPointBitsWasm.doubleToLongBits(value)
399+
} {
400+
FloatingPointBits.doubleToLongBits(value)
401+
}
392402

393403
@inline def sum(a: scala.Double, b: scala.Double): scala.Double =
394404
a + b

javalib/src/main/scala/java/lang/Float.scala

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import java.lang.constant.{Constable, ConstantDesc}
1616
import java.math.BigInteger
1717

1818
import scala.scalajs.js
19+
import scala.scalajs.LinkingInfo
20+
import scala.scalajs.LinkingInfo.linkTimeIf
1921

2022
/* This is a hijacked class. Its instances are primitive numbers.
2123
* Constructors are not emitted.
@@ -373,11 +375,21 @@ object Float {
373375

374376
// Wasm intrinsic
375377
@inline def intBitsToFloat(bits: scala.Int): scala.Float =
376-
FloatingPointBits.intBitsToFloat(bits)
378+
linkTimeIf(LinkingInfo.targetPureWasm) {
379+
// TODO: implement for Wasm without intrinsic?
380+
throw new AssertionError("Not implemented.")
381+
} {
382+
FloatingPointBits.intBitsToFloat(bits)
383+
}
377384

378385
// Wasm intrinsic
379386
@inline def floatToIntBits(value: scala.Float): scala.Int =
380-
FloatingPointBits.floatToIntBits(value)
387+
linkTimeIf(LinkingInfo.targetPureWasm) {
388+
// TODO: implement for Wasm without intrinsic?
389+
throw new AssertionError("Not implemented.")
390+
} {
391+
FloatingPointBits.floatToIntBits(value)
392+
}
381393

382394
@inline def sum(a: scala.Float, b: scala.Float): scala.Float =
383395
a + b
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
/*
2+
* Scala.js (https://www.scala-js.org/)
3+
*
4+
* Copyright EPFL.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (https://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package java.lang
14+
15+
import scala.scalajs.LinkingInfo.ESVersion
16+
17+
// Copy of FloatingPointBits without JS interop
18+
// Most of the functions depending on powsOf2,
19+
// such as decodeIEEE754 and encodeIEEE754Exponent, have been copied.
20+
// The difference is that whether js.Array or Array is used for powsOf2.
21+
// If we could virtualize powsOf2, much of the implementation could be commonized.
22+
// However, the issue is slightly complicated by the fact that many of these
23+
// powsOf2-using functions have generic implementations for both
24+
// binary32 and binary64 formats, and
25+
// by the inability to depend on Function1 within javalib.
26+
// Couldn't come up with a good way to extract the common part of functions.
27+
28+
/** Manipulating the bits of floating point numbers. */
29+
private[lang] object FloatingPointBitsWasm {
30+
31+
import scala.scalajs.LinkingInfo
32+
33+
private val floatPowsOf2: Array[scala.Double] =
34+
makePowsOf2(len = 1 << 8, java.lang.Float.MIN_NORMAL.toDouble)
35+
36+
private val doublePowsOf2: Array[scala.Double] =
37+
makePowsOf2(len = 1 << 11, java.lang.Double.MIN_NORMAL)
38+
39+
private def makePowsOf2(len: Int, minNormal: scala.Double): Array[scala.Double] = {
40+
val r = new Array[scala.Double](len)
41+
r(0) = 0.0
42+
var i = 1
43+
var next = minNormal
44+
while (i != len - 1) {
45+
r(i) = next
46+
i += 1
47+
next *= 2
48+
}
49+
r(len - 1) = scala.Double.PositiveInfinity
50+
r
51+
}
52+
53+
@noinline
54+
private def doubleHashCodePolyfill(value: scala.Double): Int =
55+
Long.hashCode(doubleToLongBitsPolyfillInline(value))
56+
57+
def intBitsToFloat(bits: Int): scala.Float = {
58+
intBitsToFloatPolyfill(bits).toFloat
59+
}
60+
61+
def floatToIntBits(value: scala.Float): Int = {
62+
floatToIntBitsPolyfill(value)
63+
}
64+
65+
def longBitsToDouble(bits: scala.Long): scala.Double = {
66+
longBitsToDoublePolyfill(bits)
67+
}
68+
69+
def doubleToLongBits(value: scala.Double): scala.Long = {
70+
doubleToLongBitsPolyfill(value)
71+
}
72+
73+
/* --- Polyfills for floating point bit manipulations ---
74+
*
75+
* Originally inspired by
76+
* https://github.com/inexorabletash/polyfill/blob/3447582628b6e3ea81959c4d5987aa332c22d1ca/typedarray.js#L150-L264
77+
*
78+
* Note that if typed arrays are not supported, it is almost certain that
79+
* fround is not supported natively, so Float operations are extremely slow.
80+
*
81+
* We therefore do all computations in Doubles here.
82+
*/
83+
84+
private def intBitsToFloatPolyfill(bits: Int): scala.Double = {
85+
val ebits = 8
86+
val fbits = 23
87+
val sign = (bits >> 31) | 1 // -1 or 1
88+
val e = (bits >> fbits) & ((1 << ebits) - 1)
89+
val f = bits & ((1 << fbits) - 1)
90+
decodeIEEE754(ebits, fbits, floatPowsOf2, scala.Float.MinPositiveValue, sign, e, f)
91+
}
92+
93+
private def floatToIntBitsPolyfill(floatValue: scala.Float): Int = {
94+
// Some constants
95+
val ebits = 8
96+
val fbits = 23
97+
98+
// Force computations to be on Doubles
99+
val value = floatValue.toDouble
100+
101+
// Determine sign bit and compute the absolute value av
102+
val sign = if (value < 0.0 || (value == 0.0 && 1.0 / value < 0.0)) -1 else 1
103+
val s = sign & scala.Int.MinValue
104+
val av = sign * value
105+
106+
// Compute e and f
107+
val powsOf2 = this.floatPowsOf2 // local cache
108+
val e = encodeIEEE754Exponent(ebits, powsOf2, av)
109+
val f = encodeIEEE754MantissaBits(ebits, fbits, powsOf2, scala.Float.MinPositiveValue.toDouble, av, e)
110+
111+
// Encode
112+
s | (e << fbits) | rawToInt(f)
113+
}
114+
115+
private def longBitsToDoublePolyfill(bits: scala.Long): scala.Double = {
116+
val ebits = 11
117+
val fbits = 52
118+
val hifbits = fbits - 32
119+
val hi = (bits >>> 32).toInt
120+
val lo = Utils.toUint(bits.toInt)
121+
val sign = (hi >> 31) | 1 // -1 or 1
122+
val e = (hi >> hifbits) & ((1 << ebits) - 1)
123+
val f = (hi & ((1 << hifbits) - 1)).toDouble * 0x100000000L.toDouble + lo
124+
decodeIEEE754(ebits, fbits, doublePowsOf2, scala.Double.MinPositiveValue, sign, e, f)
125+
}
126+
127+
@noinline
128+
private def doubleToLongBitsPolyfill(value: scala.Double): scala.Long =
129+
doubleToLongBitsPolyfillInline(value)
130+
131+
@inline
132+
private def doubleToLongBitsPolyfillInline(value: scala.Double): scala.Long = {
133+
// Some constants
134+
val ebits = 11
135+
val fbits = 52
136+
val hifbits = fbits - 32
137+
138+
// Determine sign bit and compute the absolute value av
139+
val sign = if (value < 0.0 || (value == 0.0 && 1.0 / value < 0.0)) -1 else 1
140+
val s = sign & scala.Int.MinValue
141+
val av = sign * value
142+
143+
// Compute e and f
144+
val powsOf2 = this.doublePowsOf2 // local cache
145+
val e = encodeIEEE754Exponent(ebits, powsOf2, av)
146+
val f = encodeIEEE754MantissaBits(ebits, fbits, powsOf2, scala.Double.MinPositiveValue, av, e)
147+
148+
// Encode
149+
val hi = s | (e << hifbits) | rawToInt(f / 0x100000000L.toDouble)
150+
val lo = rawToInt(f)
151+
(hi.toLong << 32) | (lo.toLong & 0xffffffffL)
152+
}
153+
154+
@inline
155+
private def decodeIEEE754(ebits: Int, fbits: Int,
156+
powsOf2: Array[scala.Double], minPositiveValue: scala.Double,
157+
sign: scala.Int, e: Int, f: scala.Double): scala.Double = {
158+
159+
// Some constants
160+
val specialExponent = (1 << ebits) - 1
161+
val twoPowFbits = (1L << fbits).toDouble
162+
163+
if (e == specialExponent) {
164+
// Special
165+
if (f == 0.0)
166+
sign * scala.Double.PositiveInfinity
167+
else
168+
scala.Double.NaN
169+
} else if (e > 0) {
170+
// Normalized
171+
sign * powsOf2(e) * (1 + f / twoPowFbits)
172+
} else {
173+
// Subnormal
174+
sign * f * minPositiveValue
175+
}
176+
}
177+
178+
private def encodeIEEE754Exponent(ebits: Int,
179+
powsOf2: Array[scala.Double], av: scala.Double): Int = {
180+
181+
/* Binary search of `av` inside `powsOf2`.
182+
* There are exactly `ebits` iterations of this loop (11 for Double, 8 for Float).
183+
*/
184+
var eMin = 0
185+
var eMax = 1 << ebits
186+
while (eMin + 1 < eMax) {
187+
val e = (eMin + eMax) >> 1
188+
if (av < powsOf2(e)) // false when av is NaN
189+
eMax = e
190+
else
191+
eMin = e
192+
}
193+
eMin
194+
}
195+
196+
@inline
197+
private def encodeIEEE754MantissaBits(ebits: Int, fbits: Int,
198+
powsOf2: Array[scala.Double], minPositiveValue: scala.Double,
199+
av: scala.Double, e: Int): scala.Double = {
200+
201+
// Some constants
202+
val specialExponent = (1 << ebits) - 1
203+
val twoPowFbits = (1L << fbits).toDouble
204+
205+
if (e == specialExponent) {
206+
if (av != av)
207+
(1L << (fbits - 1)).toDouble // NaN
208+
else
209+
0.0 // Infinity
210+
} else {
211+
if (e == 0)
212+
av / minPositiveValue // Subnormal
213+
else
214+
((av / powsOf2(e)) - 1.0) * twoPowFbits // Normal
215+
}
216+
}
217+
218+
private def encodeIEEE754MantissaBitsCore(ebits: Int, fbits: Int,
219+
powsOf2: Array[scala.Double], minPositiveValue: scala.Double,
220+
av: scala.Double, e: Int): scala.Double = {
221+
222+
// Some constants
223+
val specialExponent = (1 << ebits) - 1
224+
val twoPowFbits = (1L << fbits).toDouble
225+
226+
if (e == specialExponent) {
227+
if (av != av)
228+
(1L << (fbits - 1)).toDouble // NaN
229+
else
230+
0.0 // Infinity
231+
} else {
232+
if (e == 0)
233+
av / minPositiveValue // Subnormal
234+
else
235+
((av / powsOf2(e)) - 1.0) * twoPowFbits // Normal
236+
}
237+
}
238+
239+
// https://tc39.es/ecma262/#sec-toint32
240+
// https://github.com/scala-js/scala-js/pull/1676
241+
private def rawToInt(x: scala.Double): Int = {
242+
if (Double.isNaN(x) || Double.isInfinite(x)) 0
243+
else {
244+
val number = x.toLong
245+
(number & 0xFFFFFFFFL).toInt
246+
}
247+
}
248+
249+
}

0 commit comments

Comments
 (0)
0