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

Commit be00828

Browse files
committed
Fix #76: Implement jl.Class.isInstance for JS classes.
1 parent 953adf2 commit be00828

File tree

8 files changed

+182
-64
lines changed

8 files changed

+182
-64
lines changed

Diff for: build.sbt

+3-4
Original file line numberDiff line numberDiff line change
@@ -232,16 +232,13 @@ lazy val IgnoredTestNames: Set[String] = {
232232
"org.scalajs.testsuite.compiler.RuntimeTypeTestsTest",
233233
// Various run-time errors and JS exceptions
234234
"org.scalajs.testsuite.compiler.InteroperabilityTest",
235+
"org.scalajs.testsuite.compiler.ReflectionTest",
235236
"org.scalajs.testsuite.compiler.RegressionJSTest",
236237
"org.scalajs.testsuite.jsinterop.FunctionTest",
237238
"org.scalajs.testsuite.jsinterop.MiscInteropTest",
238239
"org.scalajs.testsuite.jsinterop.NonNativeJSTypeTest",
239240
"org.scalajs.testsuite.jsinterop.SpecialTest",
240241
"org.scalajs.testsuite.jsinterop.SymbolTest",
241-
// RuntimeError: unreachable (in the `isInstance` helper)
242-
"org.scalajs.testsuite.compiler.ReflectionTest",
243-
"org.scalajs.testsuite.compiler.RuntimeTypeTestsJSTest",
244-
"org.scalajs.testsuite.jsinterop.ModulesTest",
245242
// getClass for Box classes
246243
"org.scalajs.testsuite.javalib.lang.ClassTest",
247244
"org.scalajs.testsuite.javalib.lang.ObjectTest",
@@ -252,6 +249,8 @@ lazy val IgnoredTestNames: Set[String] = {
252249
// throwablesAreTrueErrors failed: org.junit.ComparisonFailure: expected:<[object [Error]]> but was:<[object [Object]]>
253250
// throwablesAreJSErrors failed: java.lang.AssertionError: null
254251
"org.scalajs.testsuite.javalib.lang.ThrowableJSTest",
252+
// jsError/jsObject failed: AssertionError because Wasm objects are not instanceof Error/Object
253+
"org.scalajs.testsuite.compiler.RuntimeTypeTestsJSTest",
255254
// keepBreakToLabelWithinFinallyBlock_Issue2689 failed: java.lang.AssertionError: expected:<2> but was:<1>
256255
"org.scalajs.testsuite.compiler.OptimizerTest",
257256
// nonUnitBoxedPrimitiveValuesAreSerializable failed: java.lang.AssertionError: Boolean

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

+52-23
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,9 @@ object HelperFunctions {
534534
)
535535
}
536536

537+
// isJSClassInstance
538+
instrs += REF_NULL(WasmHeapType.NoFunc)
539+
537540
instrs ++= ctx
538541
.calculateGlobalVTable(IRNames.ObjectClass)
539542
.map(method => WasmInstr.REF_FUNC(method.name))
@@ -599,15 +602,6 @@ object HelperFunctions {
599602
val valueNonNullLocal = fctx.addLocal("valueNonNull", WasmRefType.any)
600603
val specialInstanceTypesLocal = fctx.addLocal("specialInstanceTypes", WasmInt32)
601604

602-
// valueNonNull := as_non_null value; return false if null
603-
fctx.block(WasmRefType.any) { nonNullLabel =>
604-
instrs += LOCAL_GET(valueParam)
605-
instrs += BR_ON_NON_NULL(nonNullLabel)
606-
instrs += I32_CONST(0)
607-
instrs += RETURN
608-
}
609-
instrs += LOCAL_SET(valueNonNullLocal)
610-
611605
// switch (typeData.kind)
612606
fctx.switch(WasmInt32) { () =>
613607
instrs += LOCAL_GET(typeDataParam)
@@ -617,60 +611,95 @@ object HelperFunctions {
617611
(KindVoid to KindLastPrimitive).toList -> { () =>
618612
instrs += I32_CONST(0)
619613
},
620-
// case KindObject => true
614+
// case KindObject => value ne null
621615
List(KindObject) -> { () =>
622-
instrs += I32_CONST(1)
616+
instrs += LOCAL_GET(valueParam)
617+
instrs += REF_IS_NULL
618+
instrs += I32_EQZ
623619
},
624620
// for each boxed class, the corresponding primitive type test
625621
List(KindBoxedUnit) -> { () =>
626-
instrs += LOCAL_GET(valueNonNullLocal)
622+
instrs += LOCAL_GET(valueParam)
627623
instrs += CALL(WasmFunctionName.isUndef)
628624
},
629625
List(KindBoxedBoolean) -> { () =>
630-
instrs += LOCAL_GET(valueNonNullLocal)
626+
instrs += LOCAL_GET(valueParam)
631627
instrs += CALL(WasmFunctionName.typeTest(IRTypes.BooleanRef))
632628
},
633629
List(KindBoxedCharacter) -> { () =>
634-
instrs += LOCAL_GET(valueNonNullLocal)
630+
instrs += LOCAL_GET(valueParam)
635631
val structTypeName = WasmStructTypeName.forClass(SpecialNames.CharBoxClass)
636632
instrs += REF_TEST(WasmRefType(structTypeName))
637633
},
638634
List(KindBoxedByte) -> { () =>
639-
instrs += LOCAL_GET(valueNonNullLocal)
635+
instrs += LOCAL_GET(valueParam)
640636
instrs += CALL(WasmFunctionName.typeTest(IRTypes.ByteRef))
641637
},
642638
List(KindBoxedShort) -> { () =>
643-
instrs += LOCAL_GET(valueNonNullLocal)
639+
instrs += LOCAL_GET(valueParam)
644640
instrs += CALL(WasmFunctionName.typeTest(IRTypes.ShortRef))
645641
},
646642
List(KindBoxedInteger) -> { () =>
647-
instrs += LOCAL_GET(valueNonNullLocal)
643+
instrs += LOCAL_GET(valueParam)
648644
instrs += CALL(WasmFunctionName.typeTest(IRTypes.IntRef))
649645
},
650646
List(KindBoxedLong) -> { () =>
651-
instrs += LOCAL_GET(valueNonNullLocal)
647+
instrs += LOCAL_GET(valueParam)
652648
val structTypeName = WasmStructTypeName.forClass(SpecialNames.LongBoxClass)
653649
instrs += REF_TEST(WasmRefType(structTypeName))
654650
},
655651
List(KindBoxedFloat) -> { () =>
656-
instrs += LOCAL_GET(valueNonNullLocal)
652+
instrs += LOCAL_GET(valueParam)
657653
instrs += CALL(WasmFunctionName.typeTest(IRTypes.FloatRef))
658654
},
659655
List(KindBoxedDouble) -> { () =>
660-
instrs += LOCAL_GET(valueNonNullLocal)
656+
instrs += LOCAL_GET(valueParam)
661657
instrs += CALL(WasmFunctionName.typeTest(IRTypes.DoubleRef))
662658
},
663659
List(KindBoxedString) -> { () =>
664-
instrs += LOCAL_GET(valueNonNullLocal)
660+
instrs += LOCAL_GET(valueParam)
665661
instrs += CALL(WasmFunctionName.isString)
666662
},
667-
// case KindJSType => trap (TODO: don't trap for JS *class*es)
663+
// case KindJSType => call typeData.isJSClassInstance(value) or throw if it is null
668664
List(KindJSType) -> { () =>
669-
instrs += UNREACHABLE
665+
fctx.block(WasmRefType.anyref) { isJSClassInstanceIsNull =>
666+
// Load value as the argument to the function
667+
instrs += LOCAL_GET(valueParam)
668+
669+
// Load the function reference; break if null
670+
instrs += LOCAL_GET(typeDataParam)
671+
instrs += STRUCT_GET(WasmStructTypeName.typeData, isJSClassInstanceIdx)
672+
instrs += BR_ON_NULL(isJSClassInstanceIsNull)
673+
674+
// Call the function
675+
instrs += CALL_REF(ctx.isJSClassInstanceFuncTypeName)
676+
instrs += RETURN
677+
}
678+
instrs += DROP // drop `value` which was left on the stack
679+
680+
// throw new TypeError("...")
681+
instrs ++= ctx.getConstantStringInstr("TypeError")
682+
instrs += CALL(WasmFunctionName.jsGlobalRefGet)
683+
instrs += CALL(WasmFunctionName.jsNewArray)
684+
instrs ++= ctx.getConstantStringInstr(
685+
"Cannot call isInstance() on a Class representing a JS trait/object"
686+
)
687+
instrs += CALL(WasmFunctionName.jsArrayPush)
688+
instrs += CALL(WasmFunctionName.jsNew)
689+
instrs += THROW(ctx.exceptionTagName)
670690
}
671691
) { () =>
672692
// case _ =>
673693

694+
// valueNonNull := as_non_null value; return false if null
695+
fctx.block(WasmRefType.any) { nonNullLabel =>
696+
instrs += LOCAL_GET(valueParam)
697+
instrs += BR_ON_NON_NULL(nonNullLabel)
698+
instrs += I32_CONST(0)
699+
instrs += RETURN
700+
}
701+
instrs += LOCAL_SET(valueNonNullLocal)
702+
674703
/* If `typeData` represents an ancestor of a hijacked classes, we have to
675704
* answer `true` if `valueNonNull` is a primitive instance of any of the
676705
* hijacked classes that ancestor class/interface. For example, for

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

+78-6
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ class WasmBuilder {
4040
)
4141

4242
for ((primRef, kind) <- primRefsWithTypeData) {
43-
val typeDataFieldValues = genTypeDataFieldValues(kind, specialInstanceTypes = 0, primRef)
43+
val typeDataFieldValues =
44+
genTypeDataFieldValues(kind, specialInstanceTypes = 0, primRef, None)
4445
val typeDataGlobal =
4546
genTypeDataGlobal(primRef, WasmStructType.typeData, typeDataFieldValues, Nil)
4647
ctx.addGlobal(typeDataGlobal)
@@ -207,13 +208,78 @@ class WasmBuilder {
207208
}
208209
}
209210

210-
genTypeDataFieldValues(kind, specialInstanceTypes, IRTypes.ClassRef(clazz.className))
211+
val isJSClassInstanceFuncOpt = genIsJSClassInstanceFunction(clazz)
212+
213+
genTypeDataFieldValues(
214+
kind,
215+
specialInstanceTypes,
216+
IRTypes.ClassRef(clazz.className),
217+
isJSClassInstanceFuncOpt
218+
)
219+
}
220+
221+
private def genIsJSClassInstanceFunction(clazz: LinkedClass)(implicit
222+
ctx: WasmContext
223+
): Option[WasmFunctionName] = {
224+
import org.scalajs.ir.OriginalName.NoOriginalName
225+
226+
implicit val noPos: Position = Position.NoPosition
227+
228+
def build(loadJSClass: (WasmFunctionContext) => Unit): WasmFunctionName = {
229+
implicit val fctx = WasmFunctionContext(
230+
WasmFunctionName.isJSClassInstance(clazz.className),
231+
List("x" -> WasmRefType.anyref),
232+
List(WasmInt32)
233+
)
234+
235+
val List(xParam) = fctx.paramIndices
236+
237+
import fctx.instrs
238+
239+
if (clazz.kind == ClassKind.JSClass && !clazz.hasInstances) {
240+
/* We need to constant-fold the instance test, to avoid trying to
241+
* call $loadJSClass.className, since it will not exist at all.
242+
*/
243+
fctx.instrs += I32_CONST(0) // false
244+
} else {
245+
instrs += LOCAL_GET(xParam)
246+
loadJSClass(fctx)
247+
instrs += CALL(WasmFunctionName.jsBinaryOps(IRTrees.JSBinaryOp.instanceof))
248+
instrs += CALL(WasmFunctionName.unbox(IRTypes.BooleanRef))
249+
}
250+
251+
val func = fctx.buildAndAddToContext()
252+
func.name
253+
}
254+
255+
clazz.kind match {
256+
case ClassKind.NativeJSClass =>
257+
clazz.jsNativeLoadSpec.map { jsNativeLoadSpec =>
258+
build { fctx =>
259+
WasmExpressionBuilder.genLoadJSNativeLoadSpec(fctx, jsNativeLoadSpec)
260+
}
261+
}
262+
263+
case ClassKind.JSClass =>
264+
if (clazz.jsClassCaptures.isEmpty) {
265+
val funcName = build { fctx =>
266+
fctx.instrs += CALL(WasmFunctionName.loadJSClass(clazz.className))
267+
}
268+
Some(funcName)
269+
} else {
270+
None
271+
}
272+
273+
case _ =>
274+
None
275+
}
211276
}
212277

213278
private def genTypeDataFieldValues(
214279
kind: Int,
215280
specialInstanceTypes: Int,
216-
typeRef: IRTypes.NonArrayTypeRef
281+
typeRef: IRTypes.NonArrayTypeRef,
282+
isJSClassInstanceFuncOpt: Option[WasmFunctionName]
217283
)(implicit
218284
ctx: WasmContext
219285
): List[WasmInstr] = {
@@ -258,8 +324,7 @@ class WasmBuilder {
258324
}
259325

260326
val cloneFunction = {
261-
val nullref =
262-
REF_NULL(WasmHeapType(ctx.cloneFunctionTypeName))
327+
val nullref = REF_NULL(WasmHeapType.NoFunc)
263328
typeRef match {
264329
case IRTypes.ClassRef(className) =>
265330
val classInfo = ctx.getClassInfo(className)
@@ -272,6 +337,11 @@ class WasmBuilder {
272337
}
273338
}
274339

340+
val isJSClassInstance = isJSClassInstanceFuncOpt match {
341+
case None => REF_NULL(WasmHeapType.NoFunc)
342+
case Some(funcName) => REF_FUNC(funcName)
343+
}
344+
275345
nameDataValue :::
276346
List(
277347
// kind
@@ -292,7 +362,9 @@ class WasmBuilder {
292362
// arrayOf, the typeData of an array of this type - initially `null`; filled in by the `arrayTypeData` helper
293363
REF_NULL(WasmHeapType(WasmTypeName.WasmStructTypeName.ObjectVTable)),
294364
// clonefFunction - will be invoked from `clone()` method invokaion on the class
295-
cloneFunction
365+
cloneFunction,
366+
// isJSClassInstance - invoked from the `isInstance()` helper for JS types
367+
isJSClassInstance
296368
)
297369
}
298370

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

+32-30
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,35 @@ object WasmExpressionBuilder {
3737
builder.genBlockStats(stats)(inner)
3838
}
3939

40+
def genLoadJSNativeLoadSpec(fctx: WasmFunctionContext, loadSpec: IRTrees.JSNativeLoadSpec)(
41+
implicit ctx: TypeDefinableWasmContext
42+
): IRTypes.Type = {
43+
import IRTrees.JSNativeLoadSpec._
44+
45+
import fctx.instrs
46+
47+
def genFollowPath(path: List[String]): Unit = {
48+
for (prop <- path) {
49+
instrs ++= ctx.getConstantStringInstr(prop)
50+
instrs += CALL(WasmFunctionName.jsSelect)
51+
}
52+
}
53+
54+
loadSpec match {
55+
case Global(globalRef, path) =>
56+
instrs ++= ctx.getConstantStringInstr(globalRef)
57+
instrs += CALL(WasmFunctionName.jsGlobalRefGet)
58+
genFollowPath(path)
59+
IRTypes.AnyType
60+
case Import(module, path) =>
61+
instrs += GLOBAL_GET(ctx.getImportedModuleGlobal(module))
62+
genFollowPath(path)
63+
IRTypes.AnyType
64+
case ImportWithGlobalFallback(importSpec, globalSpec) =>
65+
genLoadJSNativeLoadSpec(fctx, importSpec)
66+
}
67+
}
68+
4069
private val ObjectRef = IRTypes.ClassRef(IRNames.ObjectClass)
4170
private val BoxedStringRef = IRTypes.ClassRef(IRNames.BoxedStringClass)
4271
private val toStringMethodName = IRNames.MethodName("toString", Nil, BoxedStringRef)
@@ -1897,7 +1926,7 @@ private class WasmExpressionBuilder private (
18971926
val jsNativeLoadSpec = info.jsNativeLoadSpec.getOrElse {
18981927
throw new AssertionError(s"Found $tree for class without jsNativeLoadSpec at ${tree.pos}")
18991928
}
1900-
genLoadJSNativeLoadSpec(jsNativeLoadSpec)(tree.pos)
1929+
genLoadJSNativeLoadSpec(fctx, jsNativeLoadSpec)(ctx)
19011930

19021931
case ClassKind.JSClass =>
19031932
instrs += CALL(WasmFunctionName.loadJSClass(tree.className))
@@ -1918,7 +1947,7 @@ private class WasmExpressionBuilder private (
19181947
val jsNativeLoadSpec = info.jsNativeLoadSpec.getOrElse {
19191948
throw new AssertionError(s"Found $tree for class without jsNativeLoadSpec at ${tree.pos}")
19201949
}
1921-
genLoadJSNativeLoadSpec(jsNativeLoadSpec)(tree.pos)
1950+
genLoadJSNativeLoadSpec(fctx, jsNativeLoadSpec)(ctx)
19221951

19231952
case ClassKind.JSModuleClass =>
19241953
instrs += CALL(WasmFunctionName.loadModule(tree.className))
@@ -1938,34 +1967,7 @@ private class WasmExpressionBuilder private (
19381967
throw new AssertionError(s"Found $tree for non-existing JS native member at ${tree.pos}")
19391968
}
19401969
)
1941-
genLoadJSNativeLoadSpec(jsNativeLoadSpec)(tree.pos)
1942-
}
1943-
1944-
private def genLoadJSNativeLoadSpec(loadSpec: IRTrees.JSNativeLoadSpec)(implicit
1945-
pos: Position
1946-
): IRTypes.Type = {
1947-
import IRTrees.JSNativeLoadSpec._
1948-
1949-
def genFollowPath(path: List[String]): Unit = {
1950-
for (prop <- path) {
1951-
genLiteral(IRTrees.StringLiteral(prop))
1952-
instrs += CALL(WasmFunctionName.jsSelect)
1953-
}
1954-
}
1955-
1956-
loadSpec match {
1957-
case Global(globalRef, path) =>
1958-
genLiteral(IRTrees.StringLiteral(globalRef))
1959-
instrs += CALL(WasmFunctionName.jsGlobalRefGet)
1960-
genFollowPath(path)
1961-
IRTypes.AnyType
1962-
case Import(module, path) =>
1963-
instrs += GLOBAL_GET(ctx.getImportedModuleGlobal(module))
1964-
genFollowPath(path)
1965-
IRTypes.AnyType
1966-
case ImportWithGlobalFallback(importSpec, globalSpec) =>
1967-
genLoadJSNativeLoadSpec(importSpec)
1968-
}
1970+
genLoadJSNativeLoadSpec(fctx, jsNativeLoadSpec)(ctx)
19691971
}
19701972

19711973
private def genJSDelete(tree: IRTrees.JSDelete): IRTypes.Type = {

0 commit comments

Comments
 (0)