8000 fixup! Use linkTimeIf for targetPureWasm · scala-wasm/scala-wasm@528f905 · GitHub
[go: up one dir, main page]

8000 Skip to content

Commit 528f905

Browse files
committed
fixup! Use linkTimeIf for targetPureWasm
1 parent 0d28710 commit 528f905

File tree

8 files changed

+304
-40
lines changed

8 files changed

+304
-40
lines changed

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -387,18 +387,15 @@ object Double {
387387
// Wasm intrinsic
388388
@inline def longBitsToDouble(bits: scala.Long): scala.Double =
389389
linkTimeIf(LinkingInfo.targetPureWasm) {
390-
// TODO: implement for Wasm without intrinsic?
391-
throw new AssertionError("Not implemented.")
390+
FloatingPointBitsWasm.longBitsToDouble(bits)
392391
} {
393392
FloatingPointBits.longBitsToDouble(bits)
394393
}
395394

396-
397395
// Wasm intrinsic
398396
@inline def doubleToLongBits(value: scala.Double): scala.Long =
399397
linkTimeIf(LinkingInfo.targetPureWasm) {
400-
// TODO: implement for Wasm without intrinsic?
401-
throw new AssertionError("Not implemented.")
398+
FloatingPointBitsWasm.doubleToLongBits(value)
402399
} {
403400
FloatingPointBits.doubleToLongBits(value)
404401
}
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+
}

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,8 @@ object Integer {
224224
// Wasm intrinsic
225225
@inline def divideUnsigned(dividend: Int, divisor: Int): Int =
226226
linkTimeIf(LinkingInfo.targetPureWasm) {
227-
// TODO: implement for Wasm without intrinsic?
228-
throw new AssertionError("Not implemented.")
227+
if (divisor == 0) 0 / 0
228+
else (toUnsignedLong(dividend) / toUnsignedLong(divisor)).toInt
229229
} {
230230
if (divisor == 0) 0 / 0
231231
else asInt(asUint(dividend) / asUint(divisor))
@@ -235,7 +235,8 @@ object Integer {
235235
@inline def remainderUnsigned(dividend: Int, divisor: Int): Int =
236236
linkTimeIf(LinkingInfo.targetPureWasm) {
237237
// TODO: implement for Wasm without intrinsic?
238-
throw new AssertionError("Not implemented.")
238+
if (divisor == 0) 0 % 0
239+
else (toUnsignedLong(dividend) % toUnsignedLong(divisor)).toInt
239240
} {
240241
if (divisor == 0) 0 % 0
241242
else asInt(asUint(dividend) % asUint(divisor))
@@ -291,8 +292,19 @@ object Integer {
291292
// Intrinsic, fallback on actual code for non-literal in JS
292293
@inline def numberOfLeadingZeros(i: scala.Int): scala.Int = {
293294
linkTimeIf(LinkingInfo.targetPureWasm) {
294-
// TODO: implement for Wasm without intrinsic?
295-
throw new AssertionError("Not implemented.")
295+
// Copied from clz32Dynamic
296+
// See Hacker's Delight, Section 5-3
297+
var x = i
298+
if (x == 0) {
299+
32
300+
} else {
301+
var r = 1
302+
if ((x & 0xffff0000) == 0) { x <<= 16; r += 16 }
303+
if ((x & 0xff000000) == 0) { x <<= 8; r += 8 }
304+
if ((x & 0xf0000000) == 0) { x <<= 4; r += 4 }
305+
if ((x & 0xc0000000) == 0) { x <<= 2; r += 2 }
306+
r + (x >> 31)
307+
}
296308
} {
297309
if (LinkingInfo.esVersion >= ESVersion.ES2015) js.Math.clz32(i)
298310
else clz32Dynamic(i)

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

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -460,27 +460,17 @@ object Long {
460460
// Wasm intrinsic
461461
@inline
462462
def numberOfLeadingZeros(l: scala.Long): Int = {
463-
linkTimeIf(LinkingInfo.targetPureWasm) {
464-
// TODO: implement for Wasm without intrinsic?
465-
throw new AssertionError("Not implemented.")
466-
} {
467-
val hi = (l >>> 32).toInt
468-
if (hi != 0) Integer.numberOfLeadingZeros(hi)
469-
else Integer.numberOfLeadingZeros(l.toInt) + 32
470-
}
463+
val hi = (l >>> 32).toInt
464+
if (hi != 0) Integer.numberOfLeadingZeros(hi)
465+
else Integer.numberOfLeadingZeros(l.toInt) + 32
471466
}
472467

473468
// Wasm intrinsic
474469
@inline
475470
def numberOfTrailingZeros(l: scala.Long): Int = {
476-
linkTimeIf(LinkingInfo.targetPureWasm) {
477-
// TODO: implement for Wasm without intrinsic?
478-
throw new AssertionError("Not implemented.")
479-
} {
480-
val lo = l.toInt
481-
if (lo != 0) Integer.numberOfTrailingZeros(lo)
482-
else Integer.numberOfTrailingZeros((l >>> 32).toInt) + 32
483-
}
471+
val lo = l.toInt
472+
if (lo != 0) Integer.numberOfTrailingZeros(lo)
473+
else Integer.numberOfTrailingZeros((l >>> 32).toInt) + 32
484474
}
485475

486476
@inline def toBinaryString(l: scala.Long): String =

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,21 @@ object Math {
3131
@inline def abs(a: scala.Long): scala.Long = if (a < 0) -a else a
3232

3333
// Wasm intrinsics
34-
@inline def abs(a: scala.Float): scala.Float = linkTimeIf(LinkingInfo.targetPureWasm) {
35-
throw new AssertionError("Not implemented.")
34+
@inline def abs(a: scala.Float): scala.Float =
35+
linkTimeIf(LinkingInfo.targetPureWasm) {
36+
if (a == -0.0) 0.0f
37+
else if (a < 0.0) -a
38+
else a
3639
} {
3740
js.Math.abs(a).toFloat
3841
}
39-
@inline def abs(a: scala.Double): scala.Double = linkTimeIf(LinkingInfo.targetPureWasm) {
40-
throw new AssertionError("Not implemented.")
42+
@inline def abs(a: scala.Double): scala.Double =
43+
linkTimeIf(LinkingInfo.targetPureWasm) {
44+
if (a == -0.0) 0.0
45+
else if (a < 0.0) -a
46+
else a
4147
} {
42-
js.Math.abs(a).toFloat
48+
js.Math.abs(a)
4349
}
4450

4551
@inline def max(a: scala.Int, b: scala.Int): scala.Int = if (a > b) a else b

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,8 @@ class Throwable protected (s: String, private var e: Throwable,
3636
*/
3737
private[this] var suppressed: Array[Throwable] = _
3838

39-
40-
linkTimeIf(!LinkingInfo.targetPureWasm) {
41-
if (writableStackTrace)
42-
fillInStackTrace()
43-
} {}
39+
if (writableStackTrace)
40+
fillInStackTrace()
4441

4542
def initCause(cause: Throwable): Throwable = {
4643
e = cause
@@ -52,7 +49,9 @@ class Throwable protected (s: String, private var e: Throwable,
5249
def getLocalizedMessage(): String = getMessage()
5350

5451
def fillInStackTrace(): Throwable = {
55-
if (!LinkingInfo.targetPureWasm) jsErrorForStackTrace = StackTrace.captureJSError(this)
52+
linkTimeIf(!LinkingInfo.targetPureWasm) {
53+
jsErrorForStackTrace = StackTrace.captureJSError(this)
54+
} {}
5655
this
5756
}
5857

0 commit comments

Comments
 (0)
0