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

Commit fd9f08a

Browse files
committed
Fix #76: Implement jl.Class.isInstance for JS classes.
1 parent 782d6ca commit fd9f08a

File tree

8 files changed

+179
-63
lines changed

8 files changed

+179
-63
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

+54-23
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,11 @@ object HelperFunctions {
534534
WasmFunctionName.clone(IRTypes.ClassRef(IRNames.ObjectClass))
535535
)
536536
}
537+
538+
// isJSClassInstance
539+
instrs += REF_NULL(WasmHeapType.NoFunc)
540+
541+
// reflectiveProxies
537542
instrs += ARRAY_NEW_FIXED(WasmArrayTypeName.reflectiveProxies, 0) // TODO
538543

539544
instrs ++= ctx
@@ -601,15 +606,6 @@ object HelperFunctions {
601606
val valueNonNullLocal = fctx.addLocal("valueNonNull", WasmRefType.any)
602607
val specialInstanceTypesLocal = fctx.addLocal("specialInstanceTypes", WasmInt32)
603608

604-
// valueNonNull := as_non_null value; return false if null
605-
fctx.block(WasmRefType.any) { nonNullLabel =>
606-
instrs += LOCAL_GET(valueParam)
607-
instrs += BR_ON_NON_NULL(nonNullLabel)
608-
instrs += I32_CONST(0)
609-
instrs += RETURN
610-
}
611-
instrs += LOCAL_SET(valueNonNullLocal)
612-
613609
// switch (typeData.kind)
614610
fctx.switch(WasmInt32) { () =>
615611
instrs += LOCAL_GET(typeDataParam)
@@ -619,60 +615,95 @@ object HelperFunctions {
619615
(KindVoid to KindLastPrimitive).toList -> { () =>
620616
instrs += I32_CONST(0)
621617
},
622-
// case KindObject => true
618+
// case KindObject => value ne null
623619
List(KindObject) -> { () =>
624-
instrs += I32_CONST(1)
620+
instrs += LOCAL_GET(valueParam)
621+
instrs += REF_IS_NULL
622+
instrs += I32_EQZ
625623
},
626624
// for each boxed class, the corresponding primitive type test
627625
List(KindBoxedUnit) -> { () =>
628-
instrs += LOCAL_GET(valueNonNullLocal)
626+
instrs += LOCAL_GET(valueParam)
629627
instrs += CALL(WasmFunctionName.isUndef)
630628
},
631629
List(KindBoxedBoolean) -> { () =>
632-
instrs += LOCAL_GET(valueNonNullLocal)
630+
instrs += LOCAL_GET(valueParam)
633631
instrs += CALL(WasmFunctionName.typeTest(IRTypes.BooleanRef))
634632
},
635633
List(KindBoxedCharacter) -> { () =>
636-
instrs += LOCAL_GET(valueNonNullLocal)
634+
instrs += LOCAL_GET(valueParam)
637635
val structTypeName = WasmStructTypeName.forClass(SpecialNames.CharBoxClass)
638636
instrs += REF_TEST(WasmRefType(structTypeName))
639637
},
640638
List(KindBoxedByte) -> { () =>
641-
instrs += LOCAL_GET(valueNonNullLocal)
639+
instrs += LOCAL_GET(valueParam)
642640
instrs += CALL(WasmFunctionName.typeTest(IRTypes.ByteRef))
643641
},
644642
List(KindBoxedShort) -> { () =>
645-
instrs += LOCAL_GET(valueNonNullLocal)
643+
instrs += LOCAL_GET(valueParam)
646644
instrs += CALL(WasmFunctionName.typeTest(IRTypes.ShortRef))
647645
},
648646
List(KindBoxedInteger) -> { () =>
649-
instrs += LOCAL_GET(valueNonNullLocal)
647+
instrs += LOCAL_GET(valueParam)
650648
instrs += CALL(WasmFunctionName.typeTest(IRTypes.IntRef))
651649
},
652650
List(KindBoxedLong) -> { () =>
653-
instrs += LOCAL_GET(valueNonNullLocal)
651+
instrs += LOCAL_GET(valueParam)
654652
val structTypeName = WasmStructTypeName.forClass(SpecialNames.LongBoxClass)
655653
instrs += REF_TEST(WasmRefType(structTypeName))
656654
},
657655
List(KindBoxedFloat) -> { () =>
658-
instrs += LOCAL_GET(valueNonNullLocal)
656+
instrs += LOCAL_GET(valueParam)
659657
instrs += CALL(WasmFunctionName.typeTest(IRTypes.FloatRef))
660658
},
661659
List(KindBoxedDouble) -> { () =>
662-
instrs += LOCAL_GET(valueNonNullLocal)
660+
instrs += LOCAL_GET(valueParam)
663661
instrs += CALL(WasmFunctionName.typeTest(IRTypes.DoubleRef))
664662
},
665663
List(KindBoxedString) -> { () =>
666-
instrs += LOCAL_GET(valueNonNullLocal)
664+
instrs += LOCAL_GET(valueParam)
667665
instrs += CALL(WasmFunctionName.isString)
668666
},
669-
// case KindJSType => trap (TODO: don't trap for JS *class*es)
667+
// case KindJSType => call typeData.isJSClassInstance(value) or throw if it is null
670668
List(KindJSType) -> { () =>
671-
instrs += UNREACHABLE
669+
fctx.block(WasmRefType.anyref) { isJSClassInstanceIsNull =>
670+
// Load value as the argument to the function
671+
instrs += LOCAL_GET(valueParam)
672+
673+
// Load the function reference; break if null
674+
instrs += LOCAL_GET(typeDataParam)
675+
instrs += STRUCT_GET(WasmStructTypeName.typeData, isJSClassInstanceIdx)
676+
instrs += BR_ON_NULL(isJSClassInstanceIsNull)
677+
678+
// Call the function
679+
instrs += CALL_REF(ctx.isJSClassInstanceFuncTypeName)
680+
instrs += RETURN
681+
}
682+
instrs += DROP // drop `value` which was left on the stack
683+
684+
// throw new TypeError("...")
685+
instrs ++= ctx.getConstantStringInstr("TypeError")
686+
instrs += CALL(WasmFunctionName.jsGlobalRefGet)
687+
instrs += CALL(WasmFunctionName.jsNewArray)
688+
instrs ++= ctx.getConstantStringInstr(
689+
"Cannot call isInstance() on a Class representing a JS trait/object"
690+
)
691+
instrs += CALL(WasmFunctionName.jsArrayPush)
692+
instrs += CALL(WasmFunctionName.jsNew)
693+
instrs += THROW(ctx.exceptionTagName)
672694
}
673695
) { () =>
674696
// case _ =>
675697

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

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

+72-4
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, Nil)
43+
val typeDataFieldValues =
44+
genTypeDataFieldValues(kind, specialInstanceTypes = 0, primRef, None, Nil)
4445
val typeDataGlobal =
4546
genTypeDataGlobal(primRef, WasmStructType.typeData, typeDataFieldValues, Nil)
4647
ctx.addGlobal(typeDataGlobal)
@@ -207,18 +208,79 @@ class WasmBuilder {
207208
}
208209
}
209210

211+
val isJSClassInstanceFuncOpt = genIsJSClassInstanceFunction(clazz)
212+
210213
genTypeDataFieldValues(
211214
kind,
212215
specialInstanceTypes,
213216
IRTypes.ClassRef(clazz.className),
217+
isJSClassInstanceFuncOpt,
214218
vtableElems
215219
)
216220
}
217221

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

266328
val cloneFunction = {
267-
val nullref =
268-
REF_NULL(WasmHeapType(ctx.cloneFunctionTypeName))
329+
val nullref = REF_NULL(WasmHeapType.NoFunc)
269330
typeRef match {
270331
case IRTypes.ClassRef(className) =>
271332
val classInfo = ctx.getClassInfo(className)
@@ -278,6 +339,11 @@ class WasmBuilder {
278339
}
279340
}
280341

342+
val isJSClassInstance = isJSClassInstanceFuncOpt match {
343+
case None => REF_NULL(WasmHeapType.NoFunc)
344+
case Some(funcName) => REF_FUNC(funcName)
345+
}
346+
281347
val reflectiveProxies: List[WasmInstr] = {
282348
val proxies = vtableElems.filter(_.isReflectiveProxy)
283349
proxies.flatMap { method =>
@@ -310,7 +376,9 @@ class WasmBuilder {
310376
// arrayOf, the typeData of an array of this type - initially `null`; filled in by the `arrayTypeData` helper
311377
REF_NULL(WasmHeapType(WasmTypeName.WasmStructTypeName.ObjectVTable)),
312378
// clonefFunction - will be invoked from `clone()` method invokaion on the class
313-
cloneFunction
379+
cloneFunction,
380+
// isJSClassInstance - invoked from the `isInstance()` helper for JS types
381+
isJSClassInstance
314382
) :::
315383
// reflective proxies - used to reflective call on the class at runtime.
316384
// Generated instructions create an array of reflective proxy structs, where each struct

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)
@@ -1955,7 +1984,7 @@ private class WasmExpressionBuilder private (
19551984
val jsNativeLoadSpec = info.jsNativeLoadSpec.getOrElse {
19561985
throw new AssertionError(s"Found $tree for class without jsNativeLoadSpec at ${tree.pos}")
19571986
}
1958-
genLoadJSNativeLoadSpec(jsNativeLoadSpec)(tree.pos)
1987+
genLoadJSNativeLoadSpec(fctx, jsNativeLoadSpec)(ctx)
19591988

19601989
case ClassKind.JSClass =>
19611990
instrs += CALL(WasmFunctionName.loadJSClass(tree.className))
@@ -1976,7 +2005,7 @@ private class WasmExpressionBuilder private (
19762005
val jsNativeLoadSpec = info.jsNativeLoadSpec.getOrElse {
19772006
throw new AssertionError(s"Found $tree for class without jsNativeLoadSpec at ${tree.pos}")
19782007
}
1979-
genLoadJSNativeLoadSpec(jsNativeLoadSpec)(tree.pos)
2008+
genLoadJSNativeLoadSpec(fctx, jsNativeLoadSpec)(ctx)
19802009

19812010
case ClassKind.JSModuleClass =>
19822011
instrs += CALL(WasmFunctionName.loadModule(tree.className))
@@ -1996,34 +2025,7 @@ private class WasmExpressionBuilder private (
19962025
throw new AssertionError(s"Found $tree for non-existing JS native member at ${tree.pos}")
19972026
}
19982027
)
1999-
genLoadJSNativeLoadSpec(jsNativeLoadSpec)(tree.pos)
2000-
}
2001-
2002-
private def genLoadJSNativeLoadSpec(loadSpec: IRTrees.JSNativeLoadSpec)(implicit
2003-
pos: Position
2004-
): IRTypes.Type = {
2005-
import IRTrees.JSNativeLoadSpec._
2006-
2007-
def genFollowPath(path: List[String]): Unit = {
2008-
for (prop <- path) {
2009-
genLiteral(IRTrees.StringLiteral(prop))
2010-
instrs += CALL(WasmFunctionName.jsSelect)
2011-
}
2012-
}
2013-
2014-
loadSpec match {
2015-
case Global(globalRef, path) =>
2016-
genLiteral(IRTrees.StringLiteral(globalRef))
2017-
instrs += CALL(WasmFunctionName.jsGlobalRefGet)
2018-
genFollowPath(path)
2019-
IRTypes.AnyType
2020-
case Import(module, path) =>
2021-
instrs += GLOBAL_GET(ctx.getImportedModuleGlobal(module))
2022-
genFollowPath(path)
2023-
IRTypes.AnyType
2024-
case ImportWithGlobalFallback(importSpec, globalSpec) =>
2025-
genLoadJSNativeLoadSpec(importSpec)
2026-
}
2028+
genLoadJSNativeLoadSpec(fctx, jsNativeLoadSpec)(ctx)
20272029
}
20282030

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

0 commit comments

Comments
 (0)