diff --git a/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala b/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala index 8f4a5b1..9d53f9b 100644 --- a/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala +++ b/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala @@ -545,26 +545,28 @@ class ClassEmitter(coreSpec: CoreSpec) { val instrs = fb - val heapType = watpe.HeapType(genTypeID.forClass(className)) + val structTypeID = genTypeID.forClass(className) + val structRefType = watpe.RefType(structTypeID) - val from = fb.addLocal("fromTyped", watpe.RefType.nullable(heapType)) - val result = fb.addLocal("result", watpe.RefType.nullable(heapType)) + val fromTypedLocal = fb.addLocal("fromTyped", structRefType) + // Downcast fromParam to fromTyped instrs += wa.LocalGet(fromParam) - instrs += wa.RefCast(watpe.RefType(heapType)) - instrs += wa.LocalSet(from) + instrs += wa.RefCast(structRefType) + instrs += wa.LocalSet(fromTypedLocal) - instrs += wa.Call(genFunctionID.newDefault(className)) - instrs += wa.LocalSet(result) + // Push vtable and itables on the stack (there is at least Cloneable in the itables) + instrs += wa.GlobalGet(genGlobalID.forVTable(className)) + instrs += wa.GlobalGet(genGlobalID.forITable(className)) + + // Push every field of `fromTyped` on the stack info.allFieldDefs.foreach { field => - val fieldIdx = genFieldID.forClassInstanceField(field.name.name) - instrs += wa.LocalGet(result) - instrs += wa.LocalGet(from) - instrs += wa.StructGet(genTypeID.forClass(className), fieldIdx) - instrs += wa.StructSet(genTypeID.forClass(className), fieldIdx) + instrs += wa.LocalGet(fromTypedLocal) + instrs += wa.StructGet(structTypeID, genFieldID.forClassInstanceField(field.name.name)) } - instrs += wa.LocalGet(result) - instrs += wa.RefAsNotNull + + // Create the result + instrs += wa.StructNew(structTypeID) fb.buildAndAddToModule() } diff --git a/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala b/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala index 25d78b4..bd32706 100644 --- a/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala +++ b/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala @@ -388,7 +388,6 @@ object CoreWasmLib { } addHelperImport(genFunctionID.box(primRef), List(wasmType), List(anyref)) addHelperImport(genFunctionID.unbox(primRef), List(anyref), List(wasmType)) - addHelperImport(genFunctionID.unboxOrNull(primRef), List(anyref), List(anyref)) addHelperImport(genFunctionID.typeTest(primRef), List(anyref), List(Int32)) } diff --git a/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index b2b6a60..4aa7d06 100644 --- a/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -1731,64 +1731,44 @@ private class FunctionEmitter private ( val sourceTpe = tree.expr.tpe val targetTpe = tree.tpe - if (isSubtype(sourceTpe, targetTpe)(isSubclass(_, _))) { - // Common case where no cast is necessary - genTreeAuto(tree.expr) - sourceTpe + if (sourceTpe == NothingType) { + // We cannot call transformType for NothingType, so we have to handle this case separately. + genTree(tree.expr, NothingType) + NothingType } else { - genTree(tree.expr, AnyType) + // By IR checker rules, targetTpe is none of NothingType, NullType, NoType or RecordType - markPosition(tree) + val sourceWasmType = TypeTransformer.transformType(sourceTpe)(ctx) + val targetWasmType = TypeTransformer.transformType(targetTpe)(ctx) - def genAsPrimType(targetTpe: PrimType): Unit = { - // TODO We could do something better for things like double.asInstanceOf[int] - genUnbox(targetTpe)(tree.pos) - } + if (sourceWasmType == targetWasmType) { + /* Common case where no cast is necessary at the Wasm level. + * Note that this is not *obviously* correct. It is only correct + * because, under our choices of representation and type translation + * rules, there is no pair `(sourceTpe, targetTpe)` for which the Wasm + * types are equal but a valid cast would require a *conversion*. + */ + genTreeAuto(tree.expr) + } else { + genTree(tree.expr, AnyType) - targetTpe match { - case targetTpe: PrimType => - genAsPrimType(targetTpe) + markPosition(tree) - case AnyType => - () + targetTpe match { + case targetTpe: PrimType => + // TODO Opt: We could do something better for things like double.asInstanceOf[int] + genUnbox(targetTpe)(tree.pos) - case ClassType(targetClassName) => - val info = ctx.getClassInfo(targetClassName) - if (info.kind == ClassKind.HijackedClass) { - BoxedClassToPrimType(targetClassName) match { - case UndefType | StringType => - () - case primType: PrimTypeWithRef => - primType match { - case CharType => - val structTypeName = genTypeID.forClass(SpecialNames.CharBoxClass) - instrs += wa.RefCast(watpe.RefType.nullable(structTypeName)) - case LongType => - val structTypeName = genTypeID.forClass(SpecialNames.LongBoxClass) - instrs += wa.RefCast(watpe.RefType.nullable(structTypeName)) - case NoType | NothingType | NullType => - throw new AssertionError(s"Unexpected prim type $primType for $targetClassName") - case _ => - instrs += wa.Call(genFunctionID.unboxOrNull(primType.primRef)) - } + case _ => + targetWasmType match { + case watpe.RefType(true, watpe.HeapType.Any) => + () // nothing to do + case targetWasmType: watpe.RefType => + instrs += wa.RefCast(targetWasmType) + case _ => + throw new AssertionError(s"Unexpected type in AsInstanceOf: $targetTpe") } - } else if (info.isAncestorOfHijackedClass) { - // Nothing to do; the translation is `anyref` - () - } else if (info.kind.isClass) { - instrs += wa.RefCast( - watpe.RefType.nullable(genTypeID.forClass(targetClassName)) - ) - } else if (info.isInterface) { - instrs += wa.RefCast(watpe.RefType.nullable(genTypeID.ObjectStruct)) - } - - case ArrayType(arrayTypeRef) => - val structTypeName = genTypeID.forArrayClass(arrayTypeRef) - instrs += wa.RefCast(watpe.RefType.nullable(structTypeName)) - - case targetTpe: RecordType => - throw new AssertionError(s"Illegal type in AsInstanceOf: $targetTpe") + } } targetTpe @@ -1842,9 +1822,6 @@ private class FunctionEmitter private ( } } - private def isSubclass(subClass: ClassName, superClass: ClassName): Boolean = - ctx.getClassInfo(subClass).ancestors.contains(superClass) - private def genGetClass(tree: GetClass): Type = { /* Unlike in `genApply` or `genStringConcat`, here we make no effort to * optimize known-primitive receivers. In practice, such cases would be @@ -2282,25 +2259,15 @@ private class FunctionEmitter private ( private def genLoadJSModule(tree: LoadJSModule): Type = { markPosition(tree) - val info = ctx.getClassInfo(tree.className) - - info.kind match { - case ClassKind.NativeJSModuleClass => - val jsNativeLoadSpec = info.jsNativeLoadSpec.getOrElse { - throw new AssertionError(s"Found $tree for class without jsNativeLoadSpec at ${tree.pos}") - } - genLoadJSFromSpec(instrs, jsNativeLoadSpec)(ctx) - AnyType - - case ClassKind.JSModuleClass => + ctx.getClassInfo(tree.className).jsNativeLoadSpec match { + case Some(loadSpec) => + genLoadJSFromSpec(instrs, loadSpec)(ctx) + case None => + // This is a non-native JS module instrs += wa.Call(genFunctionID.loadModule(tree.className)) - AnyType - - case _ => - throw new AssertionError( - s"Invalid LoadJSModule for class ${tree.className.nameString} of kind ${info.kind}" - ) } + + AnyType } private def genSelectJSNativeMember(tree: SelectJSNativeMember): Type = { diff --git a/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala b/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala index 989c72c..c8ac680 100644 --- a/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala +++ b/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala @@ -95,14 +95,6 @@ const scalaJSHelpers = { uF: (x) => Math.fround(x), uD: (x) => +x, - // Unboxes to primitive or null (downcast to the boxed classes) - uNZ: (x) => (x !== null) ? (x | 0) : null, - uNB: (x) => (x !== null) ? ((x << 24) >> 24) : null, - uNS: (x) => (x !== null) ? ((x << 16) >> 16) : null, - uNI: (x) => (x !== null) ? (x | 0) : null, - uNF: (x) => (x !== null) ? Math.fround(x) : null, - uND: (x) => (x !== null) ? +x : null, - // Type tests tZ: (x) => typeof x === 'boolean', tB: (x) => typeof x === 'number' && Object.is((x << 24) >> 24, x), diff --git a/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala b/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala index 6a1c138..765d71e 100644 --- a/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala +++ b/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala @@ -154,17 +154,12 @@ object VarGen { override def toString(): String = "u" + primRef.charCode } - private final case class UnboxOrNullID(primRef: PrimRef) extends JSHelperFunctionID { - override def toString(): String = "uN" + primRef.charCode - } - private final case class TypeTestID(primRef: PrimRef) extends JSHelperFunctionID { override def toString(): String = "t" + primRef.charCode } def box(primRef: PrimRef): JSHelperFunctionID = BoxID(primRef) def unbox(primRef: PrimRef): JSHelperFunctionID = UnboxID(primRef) - def unboxOrNull(primRef: PrimRef): JSHelperFunctionID = UnboxOrNullID(primRef) def typeTest(primRef: PrimRef): JSHelperFunctionID = TypeTestID(primRef) case object fmod extends JSHelperFunctionID