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

Commit 7adcb8d

Browse files
authored
Merge pull request #95 from sjrd/isinstance-for-js-classes
Fix #76: Implement jl.Class.isInstance for JS classes.
2 parents 0f44ca0 + 33d23a9 commit 7adcb8d

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
@@ -230,21 +230,20 @@ lazy val IgnoredTestNames: Set[String] = {
230230
"org.scalajs.testsuite.compiler.WasPublicBeforeTyperTestScala2",
231231
// Various run-time errors and JS exceptions
232232
"org.scalajs.testsuite.compiler.InteroperabilityTest",
233+
"org.scalajs.testsuite.compiler.ReflectionTest",
233234
"org.scalajs.testsuite.compiler.RegressionJSTest",
234235
"org.scalajs.testsuite.jsinterop.FunctionTest",
235236
"org.scalajs.testsuite.jsinterop.MiscInteropTest",
236237
"org.scalajs.testsuite.jsinterop.NonNativeJSTypeTest",
237238
"org.scalajs.testsuite.jsinterop.SpecialTest",
238239
"org.scalajs.testsuite.jsinterop.SymbolTest",
239-
// RuntimeError: unreachable (in the `isInstance` helper)
240-
"org.scalajs.testsuite.compiler.ReflectionTest",
241-
"org.scalajs.testsuite.compiler.RuntimeTypeTestsJSTest",
242-
"org.scalajs.testsuite.jsinterop.ModulesTest",
243240
// TypeError: WebAssembly objects are opaque
244241
"org.scalajs.testsuite.javalib.lang.SystemJSTest",
245242
// throwablesAreTrueErrors failed: org.junit.ComparisonFailure: expected:<[object [Error]]> but was:<[object [Object]]>
246243
// throwablesAreJSErrors failed: java.lang.AssertionError: null
247244
"org.scalajs.testsuite.javalib.lang.ThrowableJSTest",
245+
// jsError/jsObject failed: AssertionError because Wasm objects are not instanceof Error/Object
246+
"org.scalajs.testsuite.compiler.RuntimeTypeTestsJSTest",
248247
// keepBreakToLabelWithinFinallyBlock_Issue2689 failed: java.lang.AssertionError: expected:<2> but was:<1>
249248
"org.scalajs.testsuite.compiler.OptimizerTest",
250249
// No support for stack traces

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)
@@ -179,18 +180,79 @@ class WasmBuilder {
179180
}
180181
}
181182

183+
val isJSClassInstanceFuncOpt = genIsJSClassInstanceFunction(clazz)
184+
182185
genTypeDataFieldValues(
183186
kind,
184187
classInfo.specialInstanceTypes,
185188
IRTypes.ClassRef(clazz.className),
189+
isJSClassInstanceFuncOpt,
186190
vtableElems
187191
)
188192
}
189193

194+
private def genIsJSClassInstanceFunction(clazz: LinkedClass)(implicit
195+
ctx: WasmContext
196+
): Option[WasmFunctionName] = {
197+
import org.scalajs.ir.OriginalName.NoOriginalName
198+
199+
implicit val noPos: Position = Position.NoPosition
200+
201+
def build(loadJSClass: (WasmFunctionContext) => Unit): WasmFunctionName = {
202+
implicit val fctx = WasmFunctionContext(
203+
WasmFunctionName.isJSClassInstance(clazz.className),
204+
List("x" -> WasmRefType.anyref),
205+
List(WasmInt32)
206+
)
207+
208+
val List(xParam) = fctx.paramIndices
209+
210+
import fctx.instrs
211+
212+
if (clazz.kind == ClassKind.JSClass && !clazz.hasInstances) {
213+
/* We need to constant-fold the instance test, to avoid trying to
214+
* call $loadJSClass.className, since it will not exist at all.
215+
*/
216+
fctx.instrs += I32_CONST(0) // false
217+
} else {
218+
instrs += LOCAL_GET(xParam)
219+
loadJSClass(fctx)
220+
instrs += CALL(WasmFunctionName.jsBinaryOps(IRTrees.JSBinaryOp.instanceof))
221+
instrs += CALL(WasmFunctionName.unbox(IRTypes.BooleanRef))
222+
}
223+
224+
val func = fctx.buildAndAddToContext()
225+
func.name
226+
}
227+
228+
clazz.kind match {
229+
case ClassKind.NativeJSClass =>
230+
clazz.jsNativeLoadSpec.map { jsNativeLoadSpec =>
231+
build { fctx =>
232+
WasmExpressionBuilder.genLoadJSNativeLoadSpec(fctx, jsNativeLoadSpec)
233+
}
234+
}
235+
236+
case ClassKind.JSClass =>
237+
if (clazz.jsClassCaptures.isEmpty) {
238+
val funcName = build { fctx =>
239+
fctx.instrs += CALL(WasmFunctionName.loadJSClass(clazz.className))
240+
}
241+
Some(funcName)
242+
} else {
243+
None
244+
}
245+
246+
case _ =>
247+
None
248+
}
249+
}
250+
190251
private def genTypeDataFieldValues(
191252
kind: Int,
192253
specialInstanceTypes: Int,
193254
typeRef: IRTypes.NonArrayTypeRef,
255+
isJSClassInstanceFuncOpt: Option[WasmFunctionName],
194256
vtableElems: List[WasmFunctionInfo]
195257
)(implicit
196258
ctx: WasmContext
@@ -236,8 +298,7 @@ class WasmBuilder {
236298
}
237299

238300
val cloneFunction = {
239-
val nullref =
240-
REF_NULL(WasmHeapType(ctx.cloneFunctionTypeName))
301+
val nullref = REF_NULL(WasmHeapType.NoFunc)
241302
typeRef match {
242303
case IRTypes.ClassRef(className) =>
243304
val classInfo = ctx.getClassInfo(className)
@@ -250,6 +311,11 @@ class WasmBuilder {
250311
}
251312
}
252313

314+
val isJSClassInstance = isJSClassInstanceFuncOpt match {
315+
case None => REF_NULL(WasmHeapType.NoFunc)
316+
case Some(funcName) => REF_FUNC(funcName)
317+
}
318+
253319
val reflectiveProxies: List[WasmInstr] = {
254320
val proxies = vtableElems.filter(_.isReflectiveProxy)
255321
proxies.flatMap { method =>
@@ -282,7 +348,9 @@ class WasmBuilder {
282348
// arrayOf, the typeData of an array of this type - initially `null`; filled in by the `arrayTypeData` helper
283349
REF_NULL(WasmHeapType(WasmTypeName.WasmStructTypeName.ObjectVTable)),
284350
// clonefFunction - will be invoked from `clone()` method invokaion on the class
285-
cloneFunction
351+
cloneFunction,
352+
// isJSClassInstance - invoked from the `isInstance()` helper for JS types
353+
isJSClassInstance
286354
) :::
287355
// reflective proxies - used to reflective call on the class at runtime.
288356
// 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)
@@ -1969,7 +1998,7 @@ private class WasmExpressionBuilder private (
19691998
val jsNativeLoadSpec = info.jsNativeLoadSpec.getOrElse {
19701999
throw new AssertionError(s"Found $tree for class without jsNativeLoadSpec at ${tree.pos}")
19712000
}
1972-
genLoadJSNativeLoadSpec(jsNativeLoadSpec)(tree.pos)
2001+
genLoadJSNativeLoadSpec(fctx, jsNativeLoadSpec)(ctx)
19732002

19742003
case ClassKind.JSClass =>
19752004
instrs += CALL(WasmFunctionName.loadJSClass(tree.className))
@@ -1990,7 +2019,7 @@ private class WasmExpressionBuilder private (
19902019
val jsNativeLoadSpec = info.jsNativeLoadSpec.getOrElse {
19912020
throw new AssertionError(s"Found $tree for class without jsNativeLoadSpec at ${tree.pos}")
19922021
}
1993-
genLoadJSNativeLoadSpec(jsNativeLoadSpec)(tree.pos)
2022+
genLoadJSNativeLoadSpec(fctx, jsNativeLoadSpec)(ctx)
19942023

19952024
case ClassKind.JSModuleClass =>
19962025
instrs += CALL(WasmFunctionName.loadModule(tree.className))
@@ -2010,34 +2039,7 @@ private class WasmExpressionBuilder private (
20102039
throw new AssertionError(s"Found $tree for non-existing JS native member at ${tree.pos}")
20112040
}
20122041
)
2013-
genLoadJSNativeLoadSpec(jsNativeLoadSpec)(tree.pos)
2014-
}
2015-
2016-
private def genLoadJSNativeLoadSpec(loadSpec: IRTrees.JSNativeLoadSpec)(implicit
2017-
pos: Position
2018-
): IRTypes.Type = {
2019-
import IRTrees.JSNativeLoadSpec._
2020-
2021-
def genFollowPath(path: List[String]): Unit = {
2022-
for (prop <- path) {
2023-
genLiteral(IRTrees.StringLiteral(prop))
2024-
instrs += CALL(WasmFunctionName.jsSelect)
2025-
}
2026-
}
2027-
2028-
loadSpec match {
2029-
case Global(globalRef, path) =>
2030-
genLiteral(IRTrees.StringLiteral(globalRef))
2031-
instrs += CALL(WasmFunctionName.jsGlobalRefGet)
2032-
genFollowPath(path)
2033-
IRTypes.AnyType
2034-
case Import(module, path) =>
2035-
instrs += GLOBAL_GET(ctx.getImportedModuleGlobal(module))
2036-
genFollowPath(path)
2037-
IRTypes.AnyType
2038-
case ImportWithGlobalFallback(importSpec, globalSpec) =>
2039-
genLoadJSNativeLoadSpec(importSpec)
2040-
}
2042+
genLoadJSNativeLoadSpec(fctx, jsNativeLoadSpec)(ctx)
20412043
}
20422044

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

0 commit comments

Comments
 (0)