From 7d78e4e0fcb513ab290cf53687e81461394a3354 Mon Sep 17 00:00:00 2001 From: Rikito Taniguchi Date: Fri, 15 Mar 2024 15:57:11 +0900 Subject: [PATCH] test: Remove `@JSExportTopLevel` from test-suites functions Previously, the resulting Wasm module contained all the code for every test-suite package, which made debugging test results harder because (1) the resulting Wasm module contained a lot of irrelevant code, and (2) if one of the test-suites was compiled to invalid Wasm code, all the tests would be affected, rendering all modules unable to instantiate. This issue caused because each Scala.js package in the test-suites containing `@JSExportTopLevel`, which served as a root node for reachability analysis. Thus, all the code under test-suites remained alive after linking. To address this, this commit removes all `@JSExportTopLevel` from the test-suites. Instead, we added a function call to the `ModuleInitializer` methods into the start function of Wasm If a test fails, Wasm will execute unreachable, causing instantiation to fail and the test to fail accordingly. --- cli/src/main/scala/Main.scala | 10 +- cli/src/main/scala/TestSuites.scala | 23 ++-- .../src/main/scala/testsuite/Assert.scala | 17 +++ .../src/main/scala/testsuite/core/Add.scala | 10 +- .../testsuite/core/AsInstanceOfTest.scala | 17 ++- .../core/HijackedClassesDispatchTest.scala | 67 +++++----- .../core/HijackedClassesMonoTest.scala | 17 ++- .../core/HijackedClassesUpcastTest.scala | 32 ++--- .../scala/testsuite/core/InterfaceCall.scala | 11 +- .../scala/testsuite/core/JSInteropTest.scala | 115 +++++++++--------- .../main/scala/testsuite/core/Simple.scala | 10 +- .../scala/testsuite/core/ToStringTest.scala | 77 ++++++------ .../testsuite/core/VirtualDispatch.scala | 21 ++-- tests/src/test/scala/tests/CoreTests.scala | 19 ++- wasm/src/main/scala/Compiler.scala | 2 +- wasm/src/main/scala/wasm4s/WasmContext.scala | 53 ++++++-- 16 files changed, 272 insertions(+), 229 deletions(-) create mode 100644 test-suite/src/main/scala/testsuite/Assert.scala diff --git a/cli/src/main/scala/Main.scala b/cli/src/main/scala/Main.scala index 708a41b6..a0d84d68 100644 --- a/cli/src/main/scala/Main.scala +++ b/cli/src/main/scala/Main.scala @@ -28,17 +28,11 @@ object Main { val result = if (mode == "testsuite") { - val className = TestSuites.suites.map(_.className) - val moduleInitializers = className - .map { clazz => - ModuleInitializer.mainMethod(clazz, "main") - } - .zip(className) - for { irFiles <- new CliReader(classpath).irFiles _ <- Future.sequence { - moduleInitializers.map { case (moduleInitializer, className) => + TestSuites.suites.map { case TestSuites.TestSuite(className, methodName) => + val moduleInitializer = ModuleInitializer.mainMethod(className, methodName) Compiler.compileIRFiles( irFiles, List(moduleInitializer), diff --git a/cli/src/main/scala/TestSuites.scala b/cli/src/main/scala/TestSuites.scala index cdd45a3c..86631719 100644 --- a/cli/src/main/scala/TestSuites.scala +++ b/cli/src/main/scala/TestSuites.scala @@ -1,18 +1,17 @@ package cli object TestSuites { - case class TestSuite(className: String, methodName: String) + case class TestSuite(className: String, methodName: String = "main") val suites = List( - TestSuite("testsuite.core.simple.Simple", "simple"), - TestSuite("testsuite.core.add.Add", "add"), - TestSuite("testsuite.core.add.Add", "add"), - TestSuite("testsuite.core.virtualdispatch.VirtualDispatch", "virtualDispatch"), - TestSuite("testsuite.core.interfacecall.InterfaceCall", "interfaceCall"), - TestSuite("testsuite.core.asinstanceof.AsInstanceOfTest", "asInstanceOf"), - TestSuite("testsuite.core.jsinterop.JSInteropTest", "jsInterop"), - TestSuite("testsuite.core.hijackedclassesdispatch.HijackedClassesDispatchTest", "hijackedClassesDispatch"), - TestSuite("testsuite.core.hijackedclassesmono.HijackedClassesMonoTest", "hijackedClassesMono"), - TestSuite("testsuite.core.hijackedclassesupcast.HijackedClassesUpcastTest", "hijackedClassesUpcast"), - TestSuite("testsuite.core.tostring.ToStringTest", "toStringConversions") + TestSuite("testsuite.core.Simple"), + TestSuite("testsuite.core.Add"), + TestSuite("testsuite.core.VirtualDispatch"), + TestSuite("testsuite.core.InterfaceCall"), + TestSuite("testsuite.core.AsInstanceOfTest"), + TestSuite("testsuite.core.JSInteropTest"), + TestSuite("testsuite.core.HijackedClassesDispatchTest"), + TestSuite("testsuite.core.HijackedClassesMonoTest"), + TestSuite("testsuite.core.HijackedClassesUpcastTest"), + TestSuite("testsuite.core.ToStringTest") ) } diff --git a/test-suite/src/main/scala/testsuite/Assert.scala b/test-suite/src/main/scala/testsuite/Assert.scala new file mode 100644 index 00000000..fb0467fe --- /dev/null +++ b/test-suite/src/main/scala/testsuite/Assert.scala @@ -0,0 +1,17 @@ +package testsuite + +/** Temporary assertion method on Scala for Wasm. `ok` method generates `unreachable` if the given + * condition is false, trapping at runtime. + * + * While it's desirable to eventually utilize Scala's assertion, it's currently unavailable because + * we cannot compile Throwable to wasm yet, thus throw new (Throwable) is unusable. and making + * assert unavailable as well. + * + * Using JS's assert isn't feasible either; `console.assert` merely displays a message when + * assertion failure, and Node's assert module is unsupported for Wasm due to current + * unavailability of `JSImport` and module. + */ +object Assert { + def ok(cond: Boolean): Unit = + if (!cond) null.toString() // Apply to Null should compile to unreachable +} diff --git a/test-suite/src/main/scala/testsuite/core/Add.scala b/test-suite/src/main/scala/testsuite/core/Add.scala index 57fcc238..53e6399a 100644 --- a/test-suite/src/main/scala/testsuite/core/Add.scala +++ b/test-suite/src/main/scala/testsuite/core/Add.scala @@ -1,11 +1,9 @@ -package testsuite.core.add +package testsuite.core -import scala.scalajs.js.annotation._ +import testsuite.Assert.ok object Add { - def main(): Unit = { val _ = test() } - @JSExportTopLevel("add") - def test(): Boolean = { - 1 + 1 == 2 + def main(): Unit = { + ok(1 + 1 == 2) } } diff --git a/test-suite/src/main/scala/testsuite/core/AsInstanceOfTest.scala b/test-suite/src/main/scala/testsuite/core/AsInstanceOfTest.scala index a3f0c06e..f48ba454 100644 --- a/test-suite/src/main/scala/testsuite/core/AsInstanceOfTest.scala +++ b/test-suite/src/main/scala/testsuite/core/AsInstanceOfTest.scala @@ -1,15 +1,14 @@ -package testsuite.core.asinstanceof +package testsuite.core -import scala.scalajs.js.annotation._ +import testsuite.Assert.ok object AsInstanceOfTest { - def main(): Unit = { val _ = test() } - - @JSExportTopLevel("asInstanceOf") - def test(): Boolean = { - testInt(5) && - testClasses(new Child()) && - testString("foo", true) + def main(): Unit = { + ok( + testInt(5) && + testClasses(new Child()) && + testString("foo", true) + ) } def testClasses(c: Child): Boolean = { diff --git a/test-suite/src/main/scala/testsuite/core/HijackedClassesDispatchTest.scala b/test-suite/src/main/scala/testsuite/core/HijackedClassesDispatchTest.scala index 91f1ce3f..9f273b7b 100644 --- a/test-suite/src/main/scala/testsuite/core/HijackedClassesDispatchTest.scala +++ b/test-suite/src/main/scala/testsuite/core/HijackedClassesDispatchTest.scala @@ -1,44 +1,43 @@ -package testsuite.core.hijackedclassesdispatch +package testsuite.core -import scala.scalajs.js.annotation._ +import testsuite.Assert.ok object HijackedClassesDispatchTest { - def main(): Unit = { val _ = test() } - - @JSExportTopLevel("hijackedClassesDispatch") - def test(): Boolean = { + def main(): Unit = { val obj = new Test() val otherObj = new Test() val obj2 = new Test2() val otherObj2 = new Test2() - testToString(true, "true") && - testToString(54321, "54321") && - testToString(obj, "Test class") && - testToString(obj2, "[object]") && - testToString('A', "A") && - testHashCode(true, 1231) && - testHashCode(54321, 54321) && - testHashCode("foo", 101574) && - testHashCode(obj, 123) && - testHashCode(obj2, 42) && - testHashCode('A', 65) && - testIntValue(Int.box(5), 5) && - testIntValue(Long.box(6L), 6) && - testIntValue(Double.box(7.5), 7) && - testIntValue(new CustomNumber(), 789) && - testLength("foo", 3) && - testLength(new CustomCharSeq(), 54) && - testCharAt("foobar", 3, 'b') && - testCharAt(new CustomCharSeq(), 3, 'A') && - testEquals(true, 1, false) && - testEquals(1.0, 1, true) && - testEquals("foo", "foo", true) && - testEquals("foo", "bar", false) && - testEquals(obj, obj2, false) && - testEquals(obj, otherObj, true) && - testEquals(obj2, otherObj2, false) && - testNotifyAll(true) && - testNotifyAll(obj) + ok( + testToString(true, "true") && + testToString(54321, "54321") && + testToString(obj, "Test class") && + testToString(obj2, "[object]") && + testToString('A', "A") && + testHashCode(true, 1231) && + testHashCode(54321, 54321) && + testHashCode("foo", 101574) && + testHashCode(obj, 123) && + testHashCode(obj2, 42) && + testHashCode('A', 65) && + testIntValue(Int.box(5), 5) && + testIntValue(Long.box(6L), 6) && + testIntValue(Double.box(7.5), 7) && + testIntValue(new CustomNumber(), 789) && + testLength("foo", 3) && + testLength(new CustomCharSeq(), 54) && + testCharAt("foobar", 3, 'b') && + testCharAt(new CustomCharSeq(), 3, 'A') && + testEquals(true, 1, false) && + testEquals(1.0, 1, true) && + testEquals("foo", "foo", true) && + testEquals("foo", "bar", false) && + testEquals(obj, obj2, false) && + testEquals(obj, otherObj, true) && + testEquals(obj2, otherObj2, false) && + testNotifyAll(true) && + testNotifyAll(obj) + ) } def testToString(x: Any, expected: String): Boolean = diff --git a/test-suite/src/main/scala/testsuite/core/HijackedClassesMonoTest.scala b/test-suite/src/main/scala/testsuite/core/HijackedClassesMonoTest.scala index 45f2106b..a915ea4c 100644 --- a/test-suite/src/main/scala/testsuite/core/HijackedClassesMonoTest.scala +++ b/test-suite/src/main/scala/testsuite/core/HijackedClassesMonoTest.scala @@ -1,14 +1,13 @@ -package testsuite.core.hijackedclassesmono +package testsuite.core -import scala.scalajs.js.annotation._ +import testsuite.Assert.ok object HijackedClassesMonoTest { - def main(): Unit = { val _ = test() } - - @JSExportTopLevel("hijackedClassesMono") - def test(): Boolean = { - testInteger(5) && - testString("foo") + def main(): Unit = { + ok( + testInteger(5) && + testString("foo") + ) } def testInteger(x: Int): Boolean = { @@ -17,6 +16,6 @@ object HijackedClassesMonoTest { def testString(foo: String): Boolean = { foo.length() == 3 && - foo.hashCode() == 101574 + foo.hashCode() == 101574 } } diff --git a/test-suite/src/main/scala/testsuite/core/HijackedClassesUpcastTest.scala b/test-suite/src/main/scala/testsuite/core/HijackedClassesUpcastTest.scala index 09830703..f6015a98 100644 --- a/test-suite/src/main/scala/testsuite/core/HijackedClassesUpcastTest.scala +++ b/test-suite/src/main/scala/testsuite/core/HijackedClassesUpcastTest.scala @@ -1,18 +1,18 @@ -package testsuite.core.hijackedclassesupcast +package testsuite.core -import scala.scalajs.js.annotation._ +import testsuite.Assert.ok object HijackedClassesUpcastTest { - def main(): Unit = { val _ = test() } + def main(): Unit = { + ok( + testBoolean(true) && + testInteger(5) && + testIntegerNull(null) && + testString("foo") && + testStringNull(null) && + testCharacter('A') + ) - @JSExportTopLevel("hijackedClassesUpcast") - def test(): Boolean = { - testBoolean(true) && - testInteger(5) && - testIntegerNull(null) && - testString("foo") && - testStringNull(null) && - testCharacter('A') } def testBoolean(x: Boolean): Boolean = { @@ -39,11 +39,11 @@ object HijackedClassesUpcastTest { def testIntegerNull(x: Any): Boolean = { !x.isInstanceOf[Int] && - !x.isInstanceOf[java.lang.Integer] && - (x.asInstanceOf[Int] == 0) && { - val x2 = x.asInstanceOf[java.lang.Integer] - x2 == null - } + !x.isInstanceOf[java.lang.Integer] && + (x.asInstanceOf[Int] == 0) && { + val x2 = x.asInstanceOf[java.lang.Integer] + x2 == null + } } def testString(x: String): Boolean = { diff --git a/test-suite/src/main/scala/testsuite/core/InterfaceCall.scala b/test-suite/src/main/scala/testsuite/core/InterfaceCall.scala index 609ae970..ffda1505 100644 --- a/test-suite/src/main/scala/testsuite/core/InterfaceCall.scala +++ b/test-suite/src/main/scala/testsuite/core/InterfaceCall.scala @@ -1,14 +1,11 @@ -package testsuite.core.interfacecall +package testsuite.core -import scala.scalajs.js.annotation._ +import testsuite.Assert.ok object InterfaceCall { - def main(): Unit = { val _ = test() } - - @JSExportTopLevel("interfaceCall") - def test(): Boolean = { + def main(): Unit = { val c = new Concrete() - c.plus(c.zero, 1) == 1 && c.minus(1, c.zero) == 1 + ok(c.plus(c.zero, 1) == 1 && c.minus(1, c.zero) == 1) } class Concrete extends AddSub with Zero { diff --git a/test-suite/src/main/scala/testsuite/core/JSInteropTest.scala b/test-suite/src/main/scala/testsuite/core/JSInteropTest.scala index 26754794..a4d3a5a8 100644 --- a/test-suite/src/main/scala/testsuite/core/JSInteropTest.scala +++ b/test-suite/src/main/scala/testsuite/core/JSInteropTest.scala @@ -1,55 +1,54 @@ -package testsuite.core.jsinterop +package testsuite.core import scala.scalajs.js -import scala.scalajs.js.annotation._ +import testsuite.Assert.ok object JSInteropTest { - def main(): Unit = { val _ = test() } - - @JSExportTopLevel("jsInterop") - def test(): Boolean = { - testBasicTopLevel() && - testBasicStatic() && - testBasicInstance() && - testArray() && - testObject() && - testOperators() && - testLinkingInfo() + def main(): Unit = { + ok( + testBasicTopLevel() && + testBasicStatic() && + testBasicInstance() && + testArray() && + testObject() && + testOperators() && + testLinkingInfo() + ) } def testBasicTopLevel(): Boolean = { ("" + js.undefined) == "undefined" && - js.eval("3 + 4").asInstanceOf[Int] == 7 && - js.isUndefined(()) + js.eval("3 + 4").asInstanceOf[Int] == 7 && + js.isUndefined(()) } def testBasicStatic(): Boolean = { js.Math.PI == 3.1415926535897932 && - js.Math.abs(-5.6) == 5.6 && - js.Math.clz32(6548) == 19 && - js.Math.min(5, 8, 2, 12, 3) == 2 + js.Math.abs(-5.6) == 5.6 && + js.Math.clz32(6548) == 19 && + js.Math.min(5, 8, 2, 12, 3) == 2 } def testBasicInstance(): Boolean = { val d = new js.Date(1710190169564.0) d.getTime() == 1710190169564.0 && - d.getUTCFullYear() == 2024 && { - d.setTime(0.0) - d.getTime() == 0.0 - } + d.getUTCFullYear() == 2024 && { + d.setTime(0.0) + d.getTime() == 0.0 + } } def testArray(): Boolean = { val a = js.Array(1, 5, 3) a.length == 3 && - a(0) == 1 && - a(2) == 3 && { - a(0) = 65 - a.push(78) - a.length == 4 && - a(0) == 65 && - a(3) == 78 - } + a(0) == 1 && + a(2) == 3 && { + a(0) = 65 + a.push(78) + a.length == 4 && + a(0) == 65 && + a(3) == 78 + } } def testObject(): Boolean = { @@ -57,10 +56,10 @@ object JSInteropTest { js.isUndefined(o.foo) && { o.foo = 5 o.hasOwnProperty("foo").asInstanceOf[Boolean] && - o.foo.asInstanceOf[Int] == 5 && { - js.special.delete(o, "foo") - !o.hasOwnProperty("foo").asInstanceOf[Boolean] - } + o.foo.asInstanceOf[Int] == 5 && { + js.special.delete(o, "foo") + !o.hasOwnProperty("foo").asInstanceOf[Boolean] + } } } @@ -68,35 +67,35 @@ object JSInteropTest { val x = 5.asInstanceOf[js.Dynamic] val y = 11.asInstanceOf[js.Dynamic] same(+x, 5) && - same(-x, -5) && - same(~x, -6) && - same(!x, false) && - same(js.typeOf(x), "number") && // regular typeof - same(js.typeOf(js.Dynamic.global.Date), "function") && // typeof global ref - same(x + y, 16) && - same(x - y, -6) && - same(x * y, 55) && - same(x / y, 0.45454545454545453) && - same(y % x, 1) && - same(x << 3.asInstanceOf[js.Dynamic], 40) && - same(x >> 1.asInstanceOf[js.Dynamic], 2) && - same(x >>> 2.asInstanceOf[js.Dynamic], 1) && - same(x & y, 1) && - same(x | y, 15) && - same(x ^ y, 14) && - same(x < y, true) && - same(x <= y, true) && - same(x > y, false) && - same(x >= y, false) && - same(x && y, 11) && - same(x || y, 5) && - same(x ** 3.asInstanceOf[js.Dynamic], 125) + same(-x, -5) && + same(~x, -6) && + same(!x, false) && + same(js.typeOf(x), "number") && // regular typeof + same(js.typeOf(js.Dynamic.global.Date), "function") && // typeof global ref + same(x + y, 16) && + same(x - y, -6) && + same(x * y, 55) && + same(x / y, 0.45454545454545453) && + same(y % x, 1) && + same(x << 3.asInstanceOf[js.Dynamic], 40) && + same(x >> 1.asInstanceOf[js.Dynamic], 2) && + same(x >>> 2.asInstanceOf[js.Dynamic], 1) && + same(x & y, 1) && + same(x | y, 15) && + same(x ^ y, 14) && + same(x < y, true) && + same(x <= y, true) && + same(x > y, false) && + same(x >= y, false) && + same(x && y, 11) && + same(x || y, 5) && + same(x ** 3.asInstanceOf[js.Dynamic], 125) } def testLinkingInfo(): Boolean = { val linkingInfo = scala.scalajs.runtime.linkingInfo linkingInfo.esVersion >= 6 && - linkingInfo.assumingES6 == true + linkingInfo.assumingES6 == true } def same(a: js.Any, b: js.Any): Boolean = js.special.strictEquals(a, b) diff --git a/test-suite/src/main/scala/testsuite/core/Simple.scala b/test-suite/src/main/scala/testsuite/core/Simple.scala index 5c46855e..1668bdc7 100644 --- a/test-suite/src/main/scala/testsuite/core/Simple.scala +++ b/test-suite/src/main/scala/testsuite/core/Simple.scala @@ -1,11 +1,9 @@ -package testsuite.core.simple +package testsuite.core -import scala.scalajs.js.annotation._ +import testsuite.Assert.ok object Simple { - def main(): Unit = { val _ = test() } - @JSExportTopLevel("simple") - def test(): Boolean = { - true + def main(): Unit = { + ok(true) } } diff --git a/test-suite/src/main/scala/testsuite/core/ToStringTest.scala b/test-suite/src/main/scala/testsuite/core/ToStringTest.scala index 434de3fb..90cefa99 100644 --- a/test-suite/src/main/scala/testsuite/core/ToStringTest.scala +++ b/test-suite/src/main/scala/testsuite/core/ToStringTest.scala @@ -1,45 +1,44 @@ -package testsuite.core.tostring +package testsuite.core -import scala.scalajs.js.annotation._ +import testsuite.Assert.ok object ToStringTest { - def main(): Unit = { val _ = test() } - - @JSExportTopLevel("toStringConversions") - def test(): Boolean = { - testBoolean(true, "true") && - testBoolean(false, "false") && - !testBoolean(true, "tru") && // confidence test - testAny(true, "true") && - testAny(false, "false") && - testChar('A', "A") && - testAny('A', "A") && - testByte(54.toByte, "54") && - testAny(54.toByte, "54") && - testShort(6543.toShort, "6543") && - testAny(6543.toShort, "6543") && - testInt(-3423456, "-3423456") && - testAny(-3423456, "-3423456") && - testLong(1234567891011L, "1234567891011") && - testAny(1234567891011L, "1234567891011") && - testFloat(1.5f, "1.5") && - testAny(1.5f, "1.5") && - testDouble(1.4, "1.4") && - testAny(1.4, "1.4") && - testString("foo", "foo") && - testAny("foo", "foo") && - testString(null, "null") && - testAny(null, "null") && - testUndef((), "undefined") && - testAny((), "undefined") && - testMyToString(new MyToString(), "my toString") && - testMyToString(null, "null") && - testAny(new MyToString(), "my toString") && - testToStringNull(new ToStringNull(), "null") && - testToStringNull(null, "null") && - testAny(new ToStringNull(), "null") && - testConcat(1, "foo", "1foo") && - testConcat(2, null, "2null") + def main(): Unit = { + ok( + testBoolean(true, "true") && + testBoolean(false, "false") && + !testBoolean(true, "tru") && // confidence test + testAny(true, "true") && + testAny(false, "false") && + testChar('A', "A") && + testAny('A', "A") && + testByte(54.toByte, "54") && + testAny(54.toByte, "54") && + testShort(6543.toShort, "6543") && + testAny(6543.toShort, "6543") && + testInt(-3423456, "-3423456") && + testAny(-3423456, "-3423456") && + testLong(1234567891011L, "1234567891011") && + testAny(1234567891011L, "1234567891011") && + testFloat(1.5f, "1.5") && + testAny(1.5f, "1.5") && + testDouble(1.4, "1.4") && + testAny(1.4, "1.4") && + testString("foo", "foo") && + testAny("foo", "foo") && + testString(null, "null") && + testAny(null, "null") && + testUndef((), "undefined") && + testAny((), "undefined") && + testMyToString(new MyToString(), "my toString") && + testMyToString(null, "null") && + testAny(new MyToString(), "my toString") && + testToStringNull(new ToStringNull(), "null") && + testToStringNull(null, "null") && + testAny(new ToStringNull(), "null") && + testConcat(1, "foo", "1foo") && + testConcat(2, null, "2null") + ) } def testBoolean(x: Boolean, expected: String): Boolean = diff --git a/test-suite/src/main/scala/testsuite/core/VirtualDispatch.scala b/test-suite/src/main/scala/testsuite/core/VirtualDispatch.scala index 9eecc100..47c90598 100644 --- a/test-suite/src/main/scala/testsuite/core/VirtualDispatch.scala +++ b/test-suite/src/main/scala/testsuite/core/VirtualDispatch.scala @@ -1,20 +1,19 @@ -package testsuite.core.virtualdispatch +package testsuite.core -import scala.scalajs.js.annotation._ +import testsuite.Assert.ok object VirtualDispatch { - def main(): Unit = { val _ = test() } - - @JSExportTopLevel("virtualDispatch") - def test(): Boolean = { + def main(): Unit = { val a = new A val b = new B - testA(a) && - testB(a, isInstanceOfA = true) && - testB(b, isInstanceOfA = false) && - testC(a, isInstanceOfA = true) && - testC(b, isInstanceOfA = false) + ok( + testA(a) && + testB(a, isInstanceOfA = true) && + testB(b, isInstanceOfA = false) && + testC(a, isInstanceOfA = true) && + testC(b, isInstanceOfA = false) + ) } def testA(a: A): Boolean = { diff --git a/tests/src/test/scala/tests/CoreTests.scala b/tests/src/test/scala/tests/CoreTests.scala index 0638f99e..fd4e8467 100644 --- a/tests/src/test/scala/tests/CoreTests.scala +++ b/tests/src/test/scala/tests/CoreTests.scala @@ -4,15 +4,22 @@ import scala.scalajs.js import scala.scalajs.js.annotation._ import org.scalajs.macrotaskexecutor.MacrotaskExecutor.Implicits._ +/** The `tests/test` command compiles the Scala sources under `test-suite` into Wasm with static + * initializers (see `cli/src/main/scala/Main.scala`), where these static initializers should point + * to each main method in the classes of test-suites. + * + * These static initializers will be invoked from the start function in Wasm, which in turn will be + * invoked on instantiation of the Wasm module. + * + * Once we compile the test-suites into Wasm and put them under the `./target` directory, + * `tests/test` will instantiate those Wasm modules. If the test suites have an assertion failure, + * the Wasm module will execute `unreachable` and fail during instantiation. + */ class CoreTests extends munit.FunSuite { cli.TestSuites.suites.map { suite => test(suite.className) { - CoreTests.load(s"./target/${suite.className}.wasm").toFuture.map { exports => - val testFunction = - exports - .selectDynamic(suite.methodName) - .asInstanceOf[js.Function0[Int]] - assert(testFunction() == 1) + CoreTests.load(s"./target/${suite.className}.wasm").toFuture.map { _ => + () } } } diff --git a/wasm/src/main/scala/Compiler.scala b/wasm/src/main/scala/Compiler.scala index 63c81b1b..df280a88 100644 --- a/wasm/src/main/scala/Compiler.scala +++ b/wasm/src/main/scala/Compiler.scala @@ -77,7 +77,7 @@ object Compiler { builder.transformTopLevelExport(tle) } - context.complete() + context.complete(moduleInitializers) val textOutput = new converters.WasmTextWriter().write(module) FS.writeFileSync(s"./target/$outputName.wat", textOutput.getBytes().toTypedArray) diff --git a/wasm/src/main/scala/wasm4s/WasmContext.scala b/wasm/src/main/scala/wasm4s/WasmContext.scala index b4350c91..4fa24f94 100644 --- a/wasm/src/main/scala/wasm4s/WasmContext.scala +++ b/wasm/src/main/scala/wasm4s/WasmContext.scala @@ -14,6 +14,9 @@ import org.scalajs.ir.ClassKind import scala.collection.mutable.LinkedHashMap import wasm.ir2wasm.TypeTransformer +import org.scalajs.linker.interface.ModuleInitializer +import org.scalajs.linker.interface.unstable.ModuleInitializerImpl + trait ReadOnlyWasmContext { import WasmContext._ protected val gcTypes = new WasmSymbolTable[WasmTypeName, WasmGCTypeDefinition]() @@ -173,7 +176,11 @@ class WasmContext(val module: WasmModule) extends FunctionTypeWriterWasmContext def putClassInfo(name: IRNames.ClassName, info: WasmClassInfo): Unit = classInfo.put(name, info) - private def addHelperImport(name: WasmFunctionName, params: List[WasmType], results: List[WasmType]): Unit = { + private def addHelperImport( + name: WasmFunctionName, + params: List[WasmType], + results: List[WasmType] + ): Unit = { val sig = WasmFunctionSignature(params, results) val typ = WasmFunctionType(addFunctionType(sig), sig) module.addImport(WasmImport(name.className, name.methodName, WasmImportDesc.Func(name, typ))) @@ -208,7 +215,11 @@ class WasmContext(val module: WasmModule) extends FunctionTypeWriterWasmContext addHelperImport(WasmFunctionName.intToString, List(WasmInt32), List(WasmRefType.any)) addHelperImport(WasmFunctionName.longToString, List(WasmInt64), List(WasmRefType.any)) addHelperImport(WasmFunctionName.doubleToString, List(WasmFloat64), List(WasmRefType.any)) - addHelperImport(WasmFunctionName.stringConcat, List(WasmRefType.any, WasmRefType.any), List(WasmRefType.any)) + addHelperImport( + WasmFunctionName.stringConcat, + List(WasmRefType.any, WasmRefType.any), + List(WasmRefType.any) + ) addHelperImport(WasmFunctionName.isString, List(WasmAnyRef), List(WasmInt32)) addHelperImport(WasmFunctionName.jsValueHashCode, List(WasmRefType.any), List(WasmInt32)) @@ -218,14 +229,26 @@ class WasmContext(val module: WasmModule) extends FunctionTypeWriterWasmContext addHelperImport(WasmFunctionName.jsGlobalRefTypeof, List(WasmRefType.any), List(WasmRefType.any)) addHelperImport(WasmFunctionName.jsNewArray, Nil, List(WasmAnyRef)) addHelperImport(WasmFunctionName.jsArrayPush, List(WasmAnyRef, WasmAnyRef), List(WasmAnyRef)) - addHelperImport(WasmFunctionName.jsArraySpreadPush, List(WasmAnyRef, WasmAnyRef), List(WasmAnyRef)) + addHelperImport( + WasmFunctionName.jsArraySpreadPush, + List(WasmAnyRef, WasmAnyRef), + List(WasmAnyRef) + ) addHelperImport(WasmFunctionName.jsNewObject, Nil, List(WasmAnyRef)) - addHelperImport(WasmFunctionName.jsObjectPush, List(WasmAnyRef, WasmAnyRef, WasmAnyRef), List(WasmAnyRef)) + addHelperImport( + WasmFunctionName.jsObjectPush, + List(WasmAnyRef, WasmAnyRef, WasmAnyRef), + List(WasmAnyRef) + ) addHelperImport(WasmFunctionName.jsSelect, List(WasmAnyRef, WasmAnyRef), List(WasmAnyRef)) addHelperImport(WasmFunctionName.jsSelectSet, List(WasmAnyRef, WasmAnyRef, WasmAnyRef), Nil) addHelperImport(WasmFunctionName.jsNew, List(WasmAnyRef, WasmAnyRef), List(WasmAnyRef)) addHelperImport(WasmFunctionName.jsFunctionApply, List(WasmAnyRef, WasmAnyRef), List(WasmAnyRef)) - addHelperImport(WasmFunctionName.jsMethodApply, List(WasmAnyRef, WasmAnyRef, WasmAnyRef), List(WasmAnyRef)) + addHelperImport( + WasmFunctionName.jsMethodApply, + List(WasmAnyRef, WasmAnyRef, WasmAnyRef), + List(WasmAnyRef) + ) addHelperImport(WasmFunctionName.jsDelete, List(WasmAnyRef, WasmAnyRef), Nil) addHelperImport(WasmFunctionName.jsIsTruthy, List(WasmAnyRef), List(WasmInt32)) addHelperImport(WasmFunctionName.jsLinkingInfo, Nil, List(WasmAnyRef)) @@ -243,7 +266,7 @@ class WasmContext(val module: WasmModule) extends FunctionTypeWriterWasmContext def addStartInstructions(instrs: List[WasmInstr]): Unit = _startInstructions ++= instrs - def complete(): Unit = { + def complete(moduleInitializers: List[ModuleInitializer]): Unit = { val instrs = _startInstructions for ((str, globalName) <- constantStringGlobals) { @@ -255,6 +278,21 @@ class WasmContext(val module: WasmModule) extends FunctionTypeWriterWasmContext } instrs += WasmInstr.GLOBAL_SET(WasmImmediate.GlobalIdx(globalName)) } + moduleInitializers.foreach { init => + ModuleInitializerImpl.fromInitializer(init.initializer) match { + case ModuleInitializerImpl.MainMethodWithArgs(className, encodedMainMethodName, args) => + () // TODO: but we don't use args yet in scala-wasm + case ModuleInitializerImpl.VoidMainMethod(className, encodedMainMethodName) => + val name = className.withSuffix("$") + instrs += + WasmInstr.CALL(WasmImmediate.FuncIdx(Names.WasmFunctionName.loadModule(name))) + instrs += WasmInstr.REF_AS_NOT_NULL + instrs += + WasmInstr.CALL( + WasmImmediate.FuncIdx(WasmFunctionName(name, encodedMainMethodName)) + ) + } + } if (_startInstructions.nonEmpty) { val sig = WasmFunctionSignature(Nil, Nil) @@ -381,7 +419,8 @@ object WasmContext { .getOrElse(throw new Error(s"Function not found: $name")) def resolveWithIdx(name: WasmFunctionName): (Int, WasmFunctionInfo) = { val idx = functions.indexWhere(_.name.methodName == name.methodName) - if (idx < 0) throw new Error(s"Function not found: $name among ${functions.map(_.name.methodName)}") + if (idx < 0) + throw new Error(s"Function not found: $name among ${functions.map(_.name.methodName)}") else (idx, functions(idx)) } }