Skip to content
This repository was archived by the owner on Jul 12, 2024. It is now read-only.

Commit a03e78b

Browse files
authored
Merge pull request #15 from sjrd/js-interop
Implement use-site JS interop.
2 parents 334446d + bc6d9e8 commit a03e78b

File tree

15 files changed

+580
-71
lines changed

15 files changed

+580
-71
lines changed

Diff for: build.sbt

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import org.scalajs.linker.interface.ESVersion
12
import org.scalajs.linker.interface.OutputPatterns
23

34
val scalaV = "2.13.12"
@@ -9,7 +10,10 @@ inThisBuild(Def.settings(
910
"-feature",
1011
"-deprecation",
1112
"-Xfatal-warnings",
12-
)
13+
),
14+
scalaJSLinkerConfig ~= {
15+
_.withESFeatures(_.withESVersion(ESVersion.ES2016))
16+
},
1317
))
1418

1519
lazy val cli = project

Diff for: cli/src/main/scala/TestSuites.scala

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ object TestSuites {
99
TestSuite("testsuite.core.virtualdispatch.VirtualDispatch", "virtualDispatch"),
1010
TestSuite("testsuite.core.interfacecall.InterfaceCall", "interfaceCall"),
1111
TestSuite("testsuite.core.asinstanceof.AsInstanceOfTest", "asInstanceOf"),
12+
TestSuite("testsuite.core.jsinterop.JSInteropTest", "jsInterop"),
1213
TestSuite("testsuite.core.hijackedclassesdispatch.HijackedClassesDispatchTest", "hijackedClassesDispatch"),
1314
TestSuite("testsuite.core.hijackedclassesmono.HijackedClassesMonoTest", "hijackedClassesMono"),
1415
TestSuite("testsuite.core.hijackedclassesupcast.HijackedClassesUpcastTest", "hijackedClassesUpcast"),

Diff for: loader.mjs

+56
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ function stringHashCode(s) {
1313
return res;
1414
}
1515

16+
const linkingInfo = Object.freeze({
17+
"esVersion": 6,
18+
"assumingES6": true,
19+
"productionMode": false,
20+
"linkerVersion": "1.15.0",
21+
"fileLevelThis": this
22+
});
23+
1624
const scalaJSHelpers = {
1725
// BinaryOp.===
1826
is: Object.is,
@@ -79,6 +87,54 @@ const scalaJSHelpers = {
7987
return 0;
8088
return 42; // for any JS object
8189
},
90+
91+
// JS interop
92+
jsGlobalRefGet: (globalRefName) => (new Function("return " + globalRefName))(),
93+
jsGlobalRefSet: (globalRefName, v) => {
94+
var argName = globalRefName === 'v' ? 'w' : 'v';
95+
(new Function(argName, globalRefName + " = " + argName))(v);
96+
},
97+
jsGlobalRefTypeof: (globalRefName) => (new Function("return typeof " + globalRefName))(),
98+
jsNewArray: () => [],
99+
jsArrayPush: (a, v) => (a.push(v), a),
100+
jsArraySpreadPush: (a, vs) => (a.push(...vs), a),
101+
jsNewObject: () => ({}),
102+
jsObjectPush: (o, p, v) => (o[p] = v, o),
103+
jsSelect: (o, p) => o[p],
104+
jsSelectSet: (o, p, v) => o[p] = v,
105+
jsNew: (constr, args) => new constr(...args),
106+
jsFunctionApply: (f, args) => f(...args),
107+
jsMethodApply: (o, m, args) => o[m](...args),
108+
jsDelete: (o, p) => { delete o[p]; },
109+
jsIsTruthy: (x) => !!x,
110+
jsLinkingInfo: () => linkingInfo,
111+
112+
// Excruciating list of all the JS operators
113+
jsUnaryPlus: (a) => +a,
114+
jsUnaryMinus: (a) => -a,
115+
jsUnaryTilde: (a) => ~a,
116+
jsUnaryBang: (a) => !a,
117+
jsUnaryTypeof: (a) => typeof a,
118+
jsStrictEquals: (a, b) => a === b,
119+
jsNotStrictEquals: (a, b) => a !== b,
120+
jsPlus: (a, b) => a + b,
121+
jsMinus: (a, b) => a - b,
122+
jsTimes: (a, b) => a * b,
123+
jsDivide: (a, b) => a / b,
124+
jsModulus: (a, b) => a % b,
125+
jsBinaryOr: (a, b) => a | b,
126+
jsBinaryAnd: (a, b) => a & b,
127+
jsBinaryXor: (a, b) => a ^ b,
128+
jsShiftLeft: (a, b) => a << b,
129+
jsArithmeticShiftRight: (a, b) => a >> b,
130+
jsLogicalShiftRight: (a, b) => a >>> b,
131+
jsLessThan: (a, b) => a < b,
132+
jsLessEqual: (a, b) => a <= b,
133+
jsGreaterThan: (a, b) => a > b,
134+
jsGreaterEqual: (a, b) => a >= b,
135+
jsIn: (a, b) => a in b,
136+
jsInstanceof: (a, b) => a instanceof b,
137+
jsExponent: (a, b) => a ** b,
82138
}
83139

84140
export async function load(wasmFileName) {

Diff for: sample/src/main/scala/Sample.scala

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package sample
22

33
import scala.annotation.tailrec
44

5+
import scala.scalajs.js
56
import scala.scalajs.js.annotation._
67

78
//
@@ -16,6 +17,9 @@ object Main {
1617
val loopFib = fib(new LoopFib {}, i)
1718
val recFib = fib(new RecFib {}, i)
1819
val tailrecFib = fib(new TailRecFib {}, i)
20+
js.Dynamic.global.console.log(s"loopFib: $loopFib -- recFib: $recFib -- tailrecFib: $tailrecFib")
21+
val date = new js.Date(0)
22+
js.Dynamic.global.console.log(date)
1923
loopFib == recFib && loopFib == tailrecFib
2024
}
2125
def fib(fib: Fib, n: Int): Int = fib.fib(n)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package testsuite.core.jsinterop
2+
3+
import scala.scalajs.js
4+
import scala.scalajs.js.annotation._
5+
6+
object JSInteropTest {
7+
def main(): Unit = { val _ = test() }
8+
9+
@JSExportTopLevel("jsInterop")
10+
def test(): Boolean = {
11+
testBasicTopLevel() &&
12+
testBasicStatic() &&
13+
testBasicInstance() &&
14+
testArray() &&
15+
testObject() &&
16+
testOperators() &&
17+
testLinkingInfo()
18+
}
19+
20+
def testBasicTopLevel(): Boolean = {
21+
("" + js.undefined) == "undefined" &&
22+
js.eval("3 + 4").asInstanceOf[Int] == 7 &&
23+
js.isUndefined(())
24+
}
25+
26+
def testBasicStatic(): Boolean = {
27+
js.Math.PI == 3.1415926535897932 &&
28+
js.Math.abs(-5.6) == 5.6 &&
29+
js.Math.clz32(6548) == 19 &&
30+
js.Math.min(5, 8, 2, 12, 3) == 2
31+
}
32+
33+
def testBasicInstance(): Boolean = {
34+
val d = new js.Date(1710190169564.0)
35+
d.getTime() == 1710190169564.0 &&
36+
d.getUTCFullYear() == 2024 && {
37+
d.setTime(0.0)
38+
d.getTime() == 0.0
39+
}
40+
}
41+
42+
def testArray(): Boolean = {
43+
val a = js.Array(1, 5, 3)
44+
a.length == 3 &&
45+
a(0) == 1 &&
46+
a(2) == 3 && {
47+
a(0) = 65
48+
a.push(78)
49+
a.length == 4 &&
50+
a(0) == 65 &&
51+
a(3) == 78
52+
}
53+
}
54+
55+
def testObject(): Boolean = {
56+
val o = new js.Object().asInstanceOf[js.Dynamic]
57+
js.isUndefined(o.foo) && {
58+
o.foo = 5
59+
o.hasOwnProperty("foo").asInstanceOf[Boolean] &&
60+
o.foo.asInstanceOf[Int] == 5 && {
61+
js.special.delete(o, "foo")
62+
!o.hasOwnProperty("foo").asInstanceOf[Boolean]
63+
}
64+
}
65+
}
66+
67+
def testOperators(): Boolean = {
68+
val x = 5.asInstanceOf[js.Dynamic]
69+
val y = 11.asInstanceOf[js.Dynamic]
70+
same(+x, 5) &&
71+
same(-x, -5) &&
72+
same(~x, -6) &&
73+
same(!x, false) &&
74+
same(js.typeOf(x), "number") && // regular typeof
75+
same(js.typeOf(js.Dynamic.global.Date), "function") && // typeof global ref
76+
same(x + y, 16) &&
77+
same(x - y, -6) &&
78+
same(x * y, 55) &&
79+
same(x / y, 0.45454545454545453) &&
80+
same(y % x, 1) &&
81+
same(x << 3.asInstanceOf[js.Dynamic], 40) &&
82+
same(x >> 1.asInstanceOf[js.Dynamic], 2) &&
83+
same(x >>> 2.asInstanceOf[js.Dynamic], 1) &&
84+
same(x & y, 1) &&
85+
same(x | y, 15) &&
86+
same(x ^ y, 14) &&
87+
same(x < y, true) &&
88+
same(x <= y, true) &&
89+
same(x > y, false) &&
90+
same(x >= y, false) &&
91+
same(x && y, 11) &&
92+
same(x || y, 5) &&
93+
same(x ** 3.asInstanceOf[js.Dynamic], 125)
94+
}
95+
96+
def testLinkingInfo(): Boolean = {
97+
val linkingInfo = scala.scalajs.runtime.linkingInfo
98+
linkingInfo.esVersion >= 6 &&
99+
linkingInfo.assumingES6 == true
100+
}
101+
102+
def same(a: js.Any, b: js.Any): Boolean = js.special.strictEquals(a, b)
103+
}

Diff for: wasm/src/main/scala/Compiler.scala

+10-6
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ import org.scalajs.ir
77
import org.scalajs.ir.Trees._
88
import org.scalajs.ir.Types._
99

10-
import org.scalajs.linker.frontend.LinkerFrontendImpl
11-
import org.scalajs.linker.interface.{IRFile, ModuleInitializer}
12-
import org.scalajs.linker.standard.{LinkedClass, SymbolRequirement}
10+
import org.scalajs.linker.interface._
11+
import org.scalajs.linker.standard._
1312

1413
import org.scalajs.logging.{Level, ScalaConsoleLogger}
1514

@@ -30,10 +29,12 @@ object Compiler {
3029
implicit val context: WasmContext = new WasmContext(module)
3130
println("compiling... ")
3231

33-
val config = LinkerFrontendImpl
34-
.Config()
32+
val config = StandardConfig()
33+
.withESFeatures(_.withESVersion(ESVersion.ES2016)) // to be able to link `**`
34+
.withSemantics(_.optimized) // because that's the only thing we actually support at the moment
3535
.withOptimizer(false)
36-
val linkerFrontend = LinkerFrontendImpl(config)
36+
37+
val linkerFrontend = StandardLinkerFrontend(config)
3738

3839
/* The symbol requirements of our back-end.
3940
* The symbol requirements tell the LinkerFrontend that we need these
@@ -75,6 +76,9 @@ object Compiler {
7576
onlyModule.topLevelExports.foreach { tle =>
7677
builder.transformTopLevelExport(tle)
7778
}
79+
80+
context.complete()
81+
7882
val textOutput = new converters.WasmTextWriter().write(module)
7983
FS.writeFileSync(s"./target/$outputName.wat", textOutput.getBytes().toTypedArray)
8084

Diff for: wasm/src/main/scala/converters/WasmBinaryWriter.scala

+6
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ final class WasmBinaryWriter(module: WasmModule) {
6767
writeSection(fullOutput, SectionFunction)(writeFunctionSection(_))
6868
writeSection(fullOutput, SectionGlobal)(writeGlobalSection(_))
6969
writeSection(fullOutput, SectionExport)(writeExportSection(_))
70+
if (module.startFunction.isDefined)
71+
writeSection(fullOutput, SectionStart)(writeStartSection(_))
7072
writeSection(fullOutput, SectionCode)(writeCodeSection(_))
7173

7274
fullOutput.result()
@@ -146,6 +148,10 @@ final class WasmBinaryWriter(module: WasmModule) {
146148
}
147149
}
148150

151+
private def writeStartSection(buf: Buffer): Unit = {
152+
writeFuncIdx(buf, module.startFunction.get)
153+
}
154+
149155
private def writeCodeSection(buf: Buffer): Unit = {
150156
buf.vec(module.definedFunctions) { func =>
151157
buf.byteLengthSubSection(writeFunc(_, func))

Diff for: wasm/src/main/scala/converters/WasmTextWriter.scala

+9
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class WasmTextWriter {
2929
module.definedFunctions.foreach(writeFunction)
3030
module.globals.foreach(writeGlobal)
3131
module.exports.foreach(writeExport)
32+
module.startFunction.foreach(writeStart)
3233
}
3334
)
3435
// context.gcTypes
@@ -194,6 +195,14 @@ class WasmTextWriter {
194195
}
195196
)
196197

198+
private def writeStart(startFunction: WasmFunctionName)(implicit b: WatBuilder): Unit = {
199+
b.newLineList(
200+
"start", {
201+
b.appendElement(startFunction.show)
202+
}
203+
)
204+
}
205+
197206
private def writeImmediate(i: WasmImmediate, instr: WasmInstr)(implicit b: WatBuilder): Unit = {
198207
val str = i match {
199208
case WasmImmediate.I64(v) => v.toString()

Diff for: wasm/src/main/scala/ir2wasm/Preprocessor.scala

+4-13
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,10 @@ object Preprocessor {
2222
}
2323

2424
private def preprocess(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = {
25-
clazz.kind match {
26-
case ClassKind.ModuleClass | ClassKind.Class | ClassKind.Interface |
27-
ClassKind.HijackedClass =>
28-
collectMethods(clazz)
29-
case ClassKind.JSClass | ClassKind.JSModuleClass | ClassKind.NativeJSModuleClass |
30-
ClassKind.AbstractJSType | ClassKind.NativeJSClass =>
31-
println(s"${clazz.kind} ${clazz.fullName}")
32-
???
33-
}
34-
}
35-
36-
private def collectMethods(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = {
3725
val infos = clazz.methods.filterNot(_.flags.namespace.isConstructor).map { method =>
3826
makeWasmFunctionInfo(clazz, method)
3927
}
28+
4029
ctx.putClassInfo(
4130
clazz.name.name,
4231
new WasmClassInfo(
@@ -46,7 +35,9 @@ object Preprocessor {
4635
clazz.fields.collect { case f: IRTrees.FieldDef => Names.WasmFieldName(f.name.name) },
4736
clazz.superClass.map(_.name),
4837
clazz.interfaces.map(_.name),
49-
clazz.ancestors
38+
clazz.ancestors,
39+
clazz.jsNativeLoadSpec,
40+
clazz.jsNativeMembers.map(m => m.name.name -> m.jsNativeLoadSpec).toMap
5041
)
5142
)
5243
}

Diff for: wasm/src/main/scala/ir2wasm/WasmBuilder.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class WasmBuilder {
2828
case ClassKind.Class => transformClass(clazz)
2929
case ClassKind.HijackedClass => transformHijackedClass(clazz)
3030
case ClassKind.Interface => transformInterface(clazz)
31-
case _ => ???
31+
case _ => ()
3232
}
3333
}
3434

0 commit comments

Comments
 (0)