8000 Wasm: Implement fmod on the Scala side. · sjrd/scala-js@b5e2700 · GitHub
[go: up one dir, main page]

Skip to content

Commit b5e2700

Browse files
committed
Wasm: Implement fmod on the Scala side.
This avoids the JS call overhead for that "primitive" operation. As can be seen in the implementation, `fmod` is far from a primitive, though. It requires an involved algorithm in software. We took an MIT implemention of `fmod` written in Rust, generic in the bit-width, and translated it to Scala. The "generic" aspect is turned into a big copy-pasted blob between the `Float` and `Double` versions, with some "configuration" at the top. We put that implementation in the linker private library, in a new object `WasmRuntime`. The Wasm backend compiles `Float_%` and `Double_%` as calls to those runtime functions. Since our new implementation is sensitive to the normal/subnormal distinction, we add new tests for subnormal values.
1 parent 01ee94a commit b5e2700

File tree

10 files changed

+549
-35
lines changed

10 files changed

+549
-35
lines changed

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

Lines changed: 455 additions & 0 deletions
Large diffs are not rendered by default.

linker/jvm/src/main/scala/org/scalajs/linker/backend/emitter/PrivateLibHolder.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ object PrivateLibHolder {
2727
"org/scalajs/linker/runtime/RuntimeLong.sjsir",
2828
"org/scalajs/linker/runtime/RuntimeLong$.sjsir",
2929
"org/scalajs/linker/runtime/UndefinedBehaviorError.sjsir",
30+
"org/scalajs/linker/runtime/WasmRuntime.sjsir",
31+
"org/scalajs/linker/runtime/WasmRuntime$.sjsir",
3032
"scala/scalajs/js/JavaScriptException.sjsir"
3133
)
3234

linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,8 +377,6 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) {
377377
addHelperImport(genFunctionID.uIFallback, List(anyref), List(Int32))
378378
addHelperImport(genFunctionID.typeTest(IntRef), List(anyref), List(Int32))
379379

380-
addHelperImport(genFunctionID.fmod, List(Float64, Float64), List(Float64))
381-
382380
addHelperImport(genFunctionID.jsValueToString, List(RefType.any), List(RefType.extern))
383381
addHelperImport(genFunctionID.jsValueToStringForConcat, List(anyref), List(RefType.extern))
384382
addHelperImport(genFunctionID.booleanToString, List(Int32), List(RefType.extern))

linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,11 @@ object Emitter {
446446

447447
// See genIdentityHashCode in HelperFunctions
448448
callMethodStatically(BoxedDoubleClass, hashCodeMethodName),
449-
callMethodStatically(BoxedStringClass, hashCodeMethodName)
449+
callMethodStatically(BoxedStringClass, hashCodeMethodName),
450+
451+
// Implementation of Float_% and Double_%
452+
callStaticMethod(WasmRuntimeClass, fmodfMethodName),
453+
callStaticMethod(WasmRuntimeClass, fmoddMethodName)
450454
)
451455
}
452456

linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1721,31 +1721,6 @@ private class FunctionEmitter private (
17211721
case Long_>> =>
17221722
genLongShiftOp(wa.I64ShrS)
17231723

1724-
/* Floating point remainders are specified by
1725-
* https://262.ecma-international.org/#sec-numeric-types-number-remainder
1726-
* which says that it is equivalent to the C library function `fmod`.
1727-
* For `Float`s, we promote and demote to `Double`s.
1728-
* `fmod` seems quite hard to correctly implement, so we delegate to a
1729-
* JavaScript Helper.
1730-
* (The naive function `x - trunc(x / y) * y` that we can find on the
1731-
* Web does not work.)
1732-
*/
1733-
case Float_% =>
1734-
genTree(lhs, FloatType)
1735-
fb += wa.F64PromoteF32
1736-
genTree(rhs, FloatType)
1737-
fb += wa.F64PromoteF32
1738-
markPosition(tree)
1739-
fb += wa.Call(genFunctionID.fmod)
1740-
fb += wa.F32DemoteF64
1741-
FloatType
1742-
case Double_% =>
1743-
genTree(lhs, DoubleType)
1744-
genTree(rhs, DoubleType)
1745-
markPosition(tree)
1746-
fb += wa.Call(genFunctionID.fmod)
1747-
DoubleType
1748-
17491724
case String_charAt =>
17501725
genTree(lhs, StringType)
17511726
genTree(rhs, IntType)
@@ -1884,6 +1859,9 @@ private class FunctionEmitter private (
18841859
private def getElementaryBinaryOpInstr(op: BinaryOp.Code): wa.Instr = {
18851860
import BinaryOp._
18861861

1862+
def fmodFunctionID(methodName: MethodName): wanme.FunctionID =
1863+
genFunctionID.forMethod(MemberNamespace.PublicStatic, SpecialNames.WasmRuntimeClass, methodName)
1864+
18871865
(op: @switch) match {
18881866
case Boolean_== => wa.I32Eq
18891867
case Boolean_!= => wa.I32Ne
@@ -1924,11 +1902,13 @@ private class FunctionEmitter private (
19241902
case Float_- => wa.F32Sub
19251903
case Float_* => wa.F32Mul
19261904
case Float_/ => wa.F32Div
1905+
case Float_% => wa.Call(fmodFunctionID(SpecialNames.fmodfMethodName))
19271906

19281907
case Double_+ => wa.F64Add
19291908
case Double_- => wa.F64Sub
19301909
case Double_* => wa.F64Mul
19311910
case Double_/ => wa.F64Div
1911+
case Double_% => wa.Call(fmodFunctionID(SpecialNames.fmoddMethodName))
19321912

19331913
case Double_== => wa.F64Eq
19341914
case Double_!= => wa.F64Ne

linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,6 @@ const scalaJSHelpers = {
100100
tF: (x) => typeof x === 'number' && (Math.fround(x) === x || x !== x),
101101
tD: (x) => typeof x === 'number',
102102

103-
// fmod, to implement Float_% and Double_% (it is apparently quite hard to implement fmod otherwise)
104-
fmod: (x, y) => x % y,
105-
106103
// Strings
107104
emptyString: "",
108105
jsValueToString: (x) => (x === void 0) ? "undefined" : x.toString(),

linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/SpecialNames.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ object SpecialNames {
3737
val UndefinedBehaviorErrorClass =
3838
ClassName("org.scalajs.linker.runtime.UndefinedBehaviorError")
3939

40+
val WasmRuntimeClass =
41+
ClassName("org.scalajs.linker.runtime.WasmRuntime")
42+
4043
// Field names
4144

4245
val valueFieldSimpleName = SimpleFieldName("value")
@@ -52,6 +55,9 @@ object SpecialNames {
5255

5356
val hashCodeMethodName = MethodName("hashCode", Nil, IntRef)
5457

58+
val fmodfMethodName = MethodName("fmodf", List(FloatRef, FloatRef), FloatRef)
59+
val fmoddMethodName = MethodName("fmodd", List(DoubleRef, DoubleRef), DoubleRef)
60+
5561
/** A unique simple method name to map all method *signatures* into `MethodName`s. */
5662
val normalizedSimpleMethodName = SimpleMethodName("m")
5763
}

linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,6 @@ object VarGen {
130130
case object bIFallback extends JSHelperFunctionID
131131
case object uIFallback extends JSHelperFunctionID
132132

133-
case object fmod extends JSHelperFunctionID
134-
135133
case object jsValueToString extends JSHelperFunctionID // for actual toString() call
136134
case object jsValueToStringForConcat extends JSHelperFunctionID
137135
case object booleanToString extends JSHelperFunctionID

test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/DoubleTest.scala

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ class DoubleTest {
221221
test(Double.NaN, Double.NaN, 2.1)
222222
test(Double.NaN, Double.NaN, 5.5)
223223
test(Double.NaN, Double.NaN, -151.189)
224+
test(Double.NaN, Double.NaN, 3.123e-320)
225+
test(Double.NaN, Double.NaN, 2.253547e-318)
224226

225227
// If d is NaN, return NaN
226228
test(Double.NaN, Double.NaN, Double.NaN)
@@ -231,6 +233,8 @@ class DoubleTest {
231233
test(Double.NaN, 2.1, Double.NaN)
232234
test(Double.NaN, 5.5, Double.NaN)
233235
test(Double.NaN, -151.189, Double.NaN)
236+
test(Double.NaN, 3.123e-320, Double.NaN)
237+
test(Double.NaN, 2.253547e-318, Double.NaN)
234238

235239
// If n is PositiveInfinity, return NaN
236240
test(Double.NaN, Double.PositiveInfinity, Double.PositiveInfinity)
@@ -240,6 +244,8 @@ class DoubleTest {
240244
test(Double.NaN, Double.PositiveInfinity, 2.1)
241245
test(Double.NaN, Double.PositiveInfinity, 5.5)
242246
test(Double.NaN, Double.PositiveInfinity, -151.189)
247+
test(Double.NaN, Double.PositiveInfinity, 3.123e-320)
248+
test(Double.NaN, Double.PositiveInfinity, 2.253547e-318)
243249

244250
// If n is NegativeInfinity, return NaN
245251
test(Double.NaN, Double.NegativeInfinity, Double.PositiveInfinity)
@@ -249,56 +255,87 @@ class DoubleTest {
249255
test(Double.NaN, Double.NegativeInfinity, 2.1)
250256
test(Double.NaN, Double.NegativeInfinity, 5.5)
251257
test(Double.NaN, Double.NegativeInfinity, -151.189)
258+
test(Double.NaN, Double.NegativeInfinity, 3.123e-320)
259+
test(Double.NaN, Double.NegativeInfinity, 2.253547e-318)
252260

253261
// If d is PositiveInfinity, return n
254262
test(+0.0, +0.0, Double.PositiveInfinity)
255263
test(-0.0, -0.0, Double.PositiveInfinity)
256264
test(2.1, 2.1, Double.PositiveInfinity)
257265
test(5.5, 5.5, Double.PositiveInfinity)
258266
test(-151.189, -151.189, Double.PositiveInfinity)
267+
test(3.123e-320, 3.123e-320, Double.PositiveInfinity)
268+
test(2.253547e-318, 2.253547e-318, Double.PositiveInfinity)
259269

260270
// If d is NegativeInfinity, return n
261271
test(+0.0, +0.0, Double.NegativeInfinity)
262272
test(-0.0, -0.0, Double.NegativeInfinity)
263273
test(2.1, 2.1, Double.NegativeInfinity)
264274
test(5.5, 5.5, Double.NegativeInfinity)
265275
test(-151.189, -151.189, Double.NegativeInfinity)
276+
test(3.123e-320, 3.123e-320, Double.NegativeInfinity)
277+
test(2.253547e-318, 2.253547e-318, Double.NegativeInfinity)
266278

267279
// If d is +0.0, return NaN
268280
test(Double.NaN, +0.0, +0.0)
269281
test(Double.NaN, -0.0, +0.0)
270282
test(Double.NaN, 2.1, +0.0)
271283
test(Double.NaN, 5.5, +0.0)
272284
test(Double.NaN, -151.189, +0.0)
285+
test(Double.NaN, 3.123e-320, +0.0)
286+
test(Double.NaN, 2.253547e-318, +0.0)
273287

274288
// If d is -0.0, return NaN
275289
test(Double.NaN, +0.0, -0.0)
276290
test(Double.NaN, -0.0, -0.0)
277291
test(Double.NaN, 2.1, -0.0)
278292
test(Double.NaN, 5.5, -0.0)
279293
test(Double.NaN, -151.189, -0.0)
294+
test(Double.NaN, 3.123e-320, -0.0)
295+
test(Double.NaN, 2.253547e-318, -0.0)
280296

281297
// If n is +0.0, return n
282298
test(+0.0, +0.0, 2.1)
283299
test(+0.0, +0.0, 5.5)
284300
test(+0.0, +0.0, -151.189)
301+
test(+0.0, +0.0, 3.123e-320)
302+
test(+0.0, +0.0, 2.253547e-318)
285303

286304
// If n is -0.0, return n
287305
test(-0.0, -0.0, 2.1)
288306
test(-0.0, -0.0, 5.5)
289307
test(-0.0, -0.0, -151.189)
308+
test(-0.0, -0.0, 3.123e-320)
309+
test(-0.0, -0.0, 2.253547e-318)
290310

291311
// Non-special values
292-
// { val l = List(2.1, 5.5, -151.189); for (n <- l; d <- l) println(s" test(${n % d}, $n, $d)") }
312+
// { val l = List(2.1, 5.5, -151.189, 3.123e-320, 2.253547e-318);
313+
// for (n <- l; d <- l) println(s" test(${n % d}, $n, $d)".toLowerCase()) }
293314
test(0.0, 2.1, 2.1)
294315
test(2.1, 2.1, 5.5)
295316
test(2.1, 2.1, -151.189)
317+
test(2.0696e-320, 2.1, 3.123e-320)
318+
test(1.772535e-318, 2.1, 2.253547e-318)
296319
test(1.2999999999999998, 5.5, 2.1)
297320
test(0.0, 5.5, 5.5)
298321
test(5.5, 5.5, -151.189)
322+
test(3.607e-321, 5.5, 3.123e-320)
323+
test(6.8103e-319, 5.5, 2.253547e-318)
299324
test(-2.0889999999999866, -151.189, 2.1)
300325
test(-2.688999999999993, -151.189, 5.5)
301326
test(-0.0, -151.189, -151.189)
327+
test(-1.94e-320, -151.189, 3.123e-320)
328+
test(-4.1349e-319, -151.189, 2.253547e-318)
329+
test(3.123e-320, 3.123e-320, 2.1)
330+
test(3.123e-320, 3.123e-320, 5.5)
331+
test(3.123e-320, 3.123e-320, -151.189)
332+
test(0.0, 3.123e-320, 3.123e-320)
333+
test(3.123e-320, 3.123e-320, 2.253547e-318)
334+
test(2.253547e-318, 2.253547e-318, 2.1)
335+
test(2.253547e-318, 2.253547e-318, 5.5)
336+
test(2.253547e-318, 2.253547e-318, -151.189)
337+
test(4.995e-321, 2.253547e-318, 3.123e-320)
338+
test(0.0, 2.253547e-318, 2.253547e-318)
302339
}
303340

304341
@Test

test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/FloatTest.scala

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ class FloatTest {
7373
test(Float.NaN, Float.NaN, 2.1f)
7474
test(Float.NaN, Float.NaN, 5.5f)
7575
test(Float.NaN, Float.NaN, -151.189f)
76+
test(Float.NaN, Float.NaN, 8.858e-42f)
77+
test(Float.NaN, Float.NaN, 6.39164e-40f)
7678

7779
// If d is NaN, return NaN
7880
test(Float.NaN, Float.NaN, Float.NaN)
@@ -83,6 +85,8 @@ class FloatTest {
8385
test(Float.NaN, 2.1f, Float.NaN)
8486
test(Float.NaN, 5.5f, Float.NaN)
8587
test(Float.NaN, -151.189f, Float.NaN)
88+
test(Float.NaN, 8.858e-42f, Float.NaN)
89+
test(Float.NaN, 6.39164e-40f, Float.NaN)
8690

8791
// If n is PositiveInfinity, return NaN
8892
test(Float.NaN, Float.PositiveInfinity, Float.PositiveInfinity)
@@ -92,6 +96,8 @@ class FloatTest {
9296
test(Float.NaN, Float.PositiveInfinity, 2.1f)
9397
test(Float.NaN, Float.PositiveInfinity, 5.5f)
9498
test(Float.NaN, Float.PositiveInfinity, -151.189f)
99+
test(Float.NaN, Float.PositiveInfinity, 8.858e-42f)
100+
test(Float.NaN, Float.PositiveInfinity, 6.39164e-40f)
95101

96102
// If n is NegativeInfinity, return NaN
97103
test(Float.NaN, Float.NegativeInfinity, Float.PositiveInfinity)
@@ -101,56 +107,87 @@ class FloatTest {
101107
test(Float.NaN, Float.NegativeInfinity, 2.1f)
102108
test(Float.NaN, Float.NegativeInfinity, 5.5f)
103109
test(Float.NaN, Float.NegativeInfinity, -151.189f)
110+
test(Float.NaN, Float.NegativeInfinity, 8.858e-42f)
111+
test(Float.NaN, Float.NegativeInfinity, 6.39164e-40f)
104112

105113
// If d is PositiveInfinity, return n
106114
test(+0.0f, +0.0f, Float.PositiveInfinity)
107115
test(-0.0f, -0.0f, Float.PositiveInfinity)
108116
test(2.1f, 2.1f, Float.PositiveInfinity)
109117
test(5.5f, 5.5f, Float.PositiveInfinity)
110118
test(-151.189f, -151.189f, Float.PositiveInfinity)
119+
test(8.858e-42f, 8.858e-42f, Float.PositiveInfinity)
120+
test(6.39164e-40f, 6.39164e-40f, Float.PositiveInfinity)
111121

112122
// If d is NegativeInfinity, return n
113123
test(+0.0f, +0.0f, Float.NegativeInfinity)
114124
test(-0.0f, -0.0f, Float.NegativeInfinity)
115125
test(2.1f, 2.1f, Float.NegativeInfinity)
116126
test(5.5f, 5.5f, Float.NegativeInfinity)
117127
test(-151.189f, -151.189f, Float.NegativeInfinity)
128+
test(8.858e-42f, 8.858e-42f, Float.NegativeInfinity)
129+
test(6.39164e-40f, 6.39164e-40f, Float.NegativeInfinity)
118130

119131
// If d is +0.0, return NaN
120132
test(Float.NaN, +0.0f, +0.0f)
121133
test(Float.NaN, -0.0f, +0.0f)
122134
test(Float.NaN, 2.1f, +0.0f)
123135
test(Float.NaN, 5.5f, +0.0f)
124136
test(Float.NaN, -151.189f, +0.0f)
137+
test(Float.NaN, 8.858e-42f, +0.0f)
138+
test(Float.NaN, 6.39164e-40f, +0.0f)
125139

126140
// If d is -0.0, return NaN
127141
test(Float.NaN, +0.0f, -0.0f)
128142
test(Float.NaN, -0.0f, -0.0f)
129143
test(Float.NaN, 2.1f, -0.0f)
130144
test(Float.NaN, 5.5f, -0.0f)
131145
test(Float.NaN, -151.189f, -0.0f)
146+
test(Float.NaN, 8.858e-42f, -0.0f)
147+
test(Float.NaN, 6.39164e-40f, -0.0f)
132148

133149
// If n is +0.0, return n
134150
test(+0.0f, +0.0f, 2.1f)
135151
test(+0.0f, +0.0f, 5.5f)
136152
test(+0.0f, +0.0f, -151.189f)
153+
test(+0.0f, +0.0f, 8.858e-42f)
154+
test(+0.0f, +0.0f, 6.39164e-40f)
137155

138156
// If n is -0.0, return n
139157
test(-0.0f, -0.0f, 2.1f)
140158
test(-0.0f, -0.0f, 5.5f)
141159
test(-0.0f, -0.0f, -151.189f)
160+
test(-0.0f, -0.0f, 8.858e-42f)
161+
test(-0.0f, -0.0f, 6.39164e-40f)
142162

143163
// Non-special values
144-
// { val l = List(2.1f, 5.5f, -151.189f); for (n <- l; d <- l) println(s" test(${n % d}f, ${n}f, ${d}f)") }
164+
// { val l = List(2.1f, 5.5f, -151.189f, 8.858e-42f, 6.39164e-40f);
165+
// for (n <- l; d <- l) println(s" test(${n % d}f, ${n}f, ${d}f)".toLowerCase()) }
145166
test(0.0f, 2.1f, 2.1f)
146167
test(2.1f, 2.1f, 5.5f)
147168
test(2.1f, 2.1f, -151.189f)
169+
test(8.085e-42f, 2.1f, 8.858e-42f)
170+
test(6.1636e-40f, 2.1f, 6.39164e-40f)
148171
test(1.3000002f, 5.5f, 2.1f)
149172
test(0.0f, 5.5f, 5.5f)
150173
test(5.5f, 5.5f, -151.189f)
174+
test(5.11e-43f, 5.5f, 8.858e-42f)
175+
test(4.77036e-40f, 5.5f, 6.39164e-40f)
151176
test(-2.0890021f, -151.189f, 2.1f)
152177
test(-2.6889954f, -151.189f, 5.5f)
153178
test(-0.0f, -151.189f, -151.189f)
179+
test(-1.139e-42f, -151.189f, 8.858e-42f)
180+
test(-5.64734e-40f, -151.189f, 6.39164e-40f)
181+
test(8.858e-42f, 8.858e-42f, 2.1f)
182+
test(8.858e-42f, 8.858e-42f, 5.5f)
183+
test(8.858e-42f, 8.858e-42f, -151.189f)
184+
test(0.0f, 8.858e-42f, 8.858e-42f)
185+
test(8.858e-42f, 8.858e-42f, 6.39164e-40f)
186+
test(6.39164e-40f, 6.39164e-40f, 2.1f)
187+
test(6.39164e-40f, 6.39164e-40f, 5.5f)
188+
test(6.39164e-40f, 6.39164e-40f, -151.189f)
189+
test(1.417e-42f, 6.39164e-40f, 8.858e-42f)
190+
test(0.0f, 6.39164e-40f, 6.39164e-40f)
154191
}
155192

156193
@Test

0 commit comments

Comments
 (0)
0