From 8bff68999839a7a8c1b05ecf39ccb205e788dcea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 16 May 2024 17:57:47 +0200 Subject: [PATCH 01/14] Rename ClassEmitter.transformX methods to genX. Everything else that generates code is called `genX` at this point. --- .../backend/wasmemitter/ClassEmitter.scala | 38 +++++++++---------- .../linker/backend/wasmemitter/Emitter.scala | 4 +- 2 files changed, 21 insertions(+), 21 deletions(-) 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 5e95693..8d731d8 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 @@ -23,7 +23,7 @@ import TypeTransformer._ import WasmContext._ class ClassEmitter(coreSpec: CoreSpec) { - def transformClassDef(clazz: LinkedClass)(implicit ctx: WasmContext) = { + def genClassDef(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { val classInfo = ctx.getClassInfo(clazz.className) if (!clazz.kind.isClass && classInfo.hasRuntimeTypeInfo) { @@ -56,13 +56,13 @@ class ClassEmitter(coreSpec: CoreSpec) { } clazz.kind match { - case ClassKind.ModuleClass => transformModuleClass(clazz) - case ClassKind.Class => transformClass(clazz) - case ClassKind.HijackedClass => transformHijackedClass(clazz) - case ClassKind.Interface => transformInterface(clazz) + case ClassKind.ModuleClass => genModuleClass(clazz) + case ClassKind.Class => genClass(clazz) + case ClassKind.HijackedClass => genHijackedClass(clazz) + case ClassKind.Interface => genInterface(clazz) case ClassKind.JSClass | ClassKind.JSModuleClass => - transformJSClass(clazz) + genJSClass(clazz) case ClassKind.AbstractJSType | ClassKind.NativeJSClass | ClassKind.NativeJSModuleClass => () // nothing to do } @@ -90,12 +90,12 @@ class ClassEmitter(coreSpec: CoreSpec) { * for the static field. This is fine because, by spec of ECMAScript modules, JavaScript code * that *uses* the export cannot mutate it; it can only read it. */ - def transformTopLevelExport( + def genTopLevelExport( topLevelExport: LinkedTopLevelExport )(implicit ctx: WasmContext): Unit = { genTopLevelExportSetter(topLevelExport.exportName) topLevelExport.tree match { - case d: TopLevelMethodExportDef => transformTopLevelMethodExportDef(d) + case d: TopLevelMethodExportDef => genTopLevelMethodExportDef(d) case _ => () } } @@ -312,7 +312,7 @@ class ClassEmitter(coreSpec: CoreSpec) { * Optionally returns the generated struct type for this class. If the given LinkedClass is an * abstract class, returns None */ - private def transformClassCommon( + private def genClassCommon( clazz: LinkedClass )(implicit ctx: WasmContext): wamod.StructType = { val className = clazz.name.name @@ -361,7 +361,7 @@ class ClassEmitter(coreSpec: CoreSpec) { watpe.RefType.nullable(genTypeName.itables), isMutable = false ) - val fields = classInfo.allFieldDefs.map(transformField) + val fields = classInfo.allFieldDefs.map(genField) val structTypeName = genTypeName.forClass(clazz.name.name) val superType = clazz.superClass.map(s => genTypeName.forClass(s.name)) val structType = wamod.StructType( @@ -665,17 +665,17 @@ class ClassEmitter(coreSpec: CoreSpec) { ) } - private def transformClass(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { + private def genClass(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { assert(clazz.kind == ClassKind.Class) - transformClassCommon(clazz) + genClassCommon(clazz) } - private def transformHijackedClass(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { + private def genHijackedClass(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { // nothing to do () } - private def transformInterface(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { + private def genInterface(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { assert(clazz.kind == ClassKind.Interface) // gen itable type val className = clazz.name.name @@ -696,10 +696,10 @@ class ClassEmitter(coreSpec: CoreSpec) { genInterfaceInstanceTest(clazz) } - private def transformModuleClass(clazz: LinkedClass)(implicit ctx: WasmContext) = { + private def genModuleClass(clazz: LinkedClass)(implicit ctx: WasmContext) = { assert(clazz.kind == ClassKind.ModuleClass) - transformClassCommon(clazz) + genClassCommon(clazz) if (clazz.hasInstances) { val heapType = watpe.HeapType(genTypeName.forClass(clazz.className)) @@ -718,7 +718,7 @@ class ClassEmitter(coreSpec: CoreSpec) { } } - private def transformJSClass(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { + private def genJSClass(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { assert(clazz.kind.isJSClass) // Define the globals holding the Symbols of private fields @@ -1124,7 +1124,7 @@ class ClassEmitter(coreSpec: CoreSpec) { ) } - private def transformTopLevelMethodExportDef( + private def genTopLevelMethodExportDef( exportDef: TopLevelMethodExportDef )(implicit ctx: WasmContext): Unit = { implicit val pos = exportDef.pos @@ -1225,7 +1225,7 @@ class ClassEmitter(coreSpec: CoreSpec) { } } - private def transformField( + private def genField( field: FieldDef )(implicit ctx: WasmContext): wamod.StructField = { wamod.StructField( diff --git a/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala b/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala index b2f8b9f..550a0b0 100644 --- a/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala +++ b/wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala @@ -46,10 +46,10 @@ final class Emitter(config: Emitter.Config) { CoreWasmLib.genPreClasses() sortedClasses.foreach { clazz => - classEmitter.transformClassDef(clazz) + classEmitter.genClassDef(clazz) } module.topLevelExports.foreach { tle => - classEmitter.transformTopLevelExport(tle) + classEmitter.genTopLevelExport(tle) } CoreWasmLib.genPostClasses() From fa6ee1d99fcba7a0bdddac74469edbd24f9922e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 16 May 2024 18:00:01 +0200 Subject: [PATCH 02/14] Remove genHijackedClass. It has been a no-op for a while. --- .../linker/backend/wasmemitter/ClassEmitter.scala | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) 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 8d731d8..4588840 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 @@ -56,14 +56,14 @@ class ClassEmitter(coreSpec: CoreSpec) { } clazz.kind match { - case ClassKind.ModuleClass => genModuleClass(clazz) - case ClassKind.Class => genClass(clazz) - case ClassKind.HijackedClass => genHijackedClass(clazz) - case ClassKind.Interface => genInterface(clazz) + case ClassKind.ModuleClass => genModuleClass(clazz) + case ClassKind.Class => genClass(clazz) + case ClassKind.Interface => genInterface(clazz) case ClassKind.JSClass | ClassKind.JSModuleClass => genJSClass(clazz) - case ClassKind.AbstractJSType | ClassKind.NativeJSClass | ClassKind.NativeJSModuleClass => + case ClassKind.HijackedClass | ClassKind.AbstractJSType | ClassKind.NativeJSClass | + ClassKind.NativeJSModuleClass => () // nothing to do } } @@ -670,11 +670,6 @@ class ClassEmitter(coreSpec: CoreSpec) { genClassCommon(clazz) } - private def genHijackedClass(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { - // nothing to do - () - } - private def genInterface(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { assert(clazz.kind == ClassKind.Interface) // gen itable type From 81052837a1f8cbfe64e7ff65a67ccb6e34dc86af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 16 May 2024 18:04:07 +0200 Subject: [PATCH 03/14] Merge genClass and genModuleClass into genClassCommon. Which we rename to genScalaClass. --- .../backend/wasmemitter/ClassEmitter.scala | 61 +++++++------------ 1 file changed, 22 insertions(+), 39 deletions(-) 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 4588840..eae571d 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 @@ -56,10 +56,10 @@ class ClassEmitter(coreSpec: CoreSpec) { } clazz.kind match { - case ClassKind.ModuleClass => genModuleClass(clazz) - case ClassKind.Class => genClass(clazz) - case ClassKind.Interface => genInterface(clazz) - + case ClassKind.Class | ClassKind.ModuleClass => + genScalaClass(clazz) + case ClassKind.Interface => + genInterface(clazz) case ClassKind.JSClass | ClassKind.JSModuleClass => genJSClass(clazz) case ClassKind.HijackedClass | ClassKind.AbstractJSType | ClassKind.NativeJSClass | @@ -308,13 +308,8 @@ class ClassEmitter(coreSpec: CoreSpec) { ) } - /** @return - * Optionally returns the generated struct type for this class. If the given LinkedClass is an - * abstract class, returns None - */ - private def genClassCommon( - clazz: LinkedClass - )(implicit ctx: WasmContext): wamod.StructType = { + /** Generates a Scala class or module class. */ + private def genScalaClass(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { val className = clazz.name.name val typeRef = ClassRef(className) val classInfo = ctx.getClassInfo(className) @@ -377,7 +372,22 @@ class ClassEmitter(coreSpec: CoreSpec) { genCloneFunction(clazz) } - structType + // Generate the module accessor + if (clazz.kind == ClassKind.ModuleClass && clazz.hasInstances) { + val heapType = watpe.HeapType(genTypeName.forClass(clazz.className)) + + // global instance + // (global name (ref null type)) + val global = wamod.Global( + genGlobalName.forModuleInstance(clazz.name.name), + watpe.RefType.nullable(heapType), + wamod.Expr(List(wa.RefNull(heapType))), + isMutable = true + ) + ctx.addGlobal(global) + + genLoadModuleFunc(clazz) + } } private def genVTableType( @@ -665,11 +675,6 @@ class ClassEmitter(coreSpec: CoreSpec) { ) } - private def genClass(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { - assert(clazz.kind == ClassKind.Class) - genClassCommon(clazz) - } - private def genInterface(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { assert(clazz.kind == ClassKind.Interface) // gen itable type @@ -691,28 +696,6 @@ class ClassEmitter(coreSpec: CoreSpec) { genInterfaceInstanceTest(clazz) } - private def genModuleClass(clazz: LinkedClass)(implicit ctx: WasmContext) = { - assert(clazz.kind == ClassKind.ModuleClass) - - genClassCommon(clazz) - - if (clazz.hasInstances) { - val heapType = watpe.HeapType(genTypeName.forClass(clazz.className)) - - // global instance - // (global name (ref null type)) - val global = wamod.Global( - genGlobalName.forModuleInstance(clazz.name.name), - watpe.RefType.nullable(heapType), - wamod.Expr(List(wa.RefNull(heapType))), - isMutable = true - ) - ctx.addGlobal(global) - - genLoadModuleFunc(clazz) - } - } - private def genJSClass(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { assert(clazz.kind.isJSClass) From 3338e6e87fa7fcb6a4271bd08a0b4d48d331d0fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 16 May 2024 18:11:28 +0200 Subject: [PATCH 04/14] Deduplicate generation of typeData for abstract classes. --- .../linker/backend/wasmemitter/ClassEmitter.scala | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) 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 eae571d..c7e5039 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 @@ -26,8 +26,8 @@ class ClassEmitter(coreSpec: CoreSpec) { def genClassDef(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { val classInfo = ctx.getClassInfo(clazz.className) - if (!clazz.kind.isClass && classInfo.hasRuntimeTypeInfo) { - // Gen typeData -- for classes, we do it as part of the vtable generation + if (classInfo.hasRuntimeTypeInfo && !(clazz.kind.isClass && clazz.hasDirectInstances)) { + // Gen typeData -- for concrete Scala classes, we do it as part of the vtable generation instead val typeRef = ClassRef(clazz.className) val typeDataFieldValues = genTypeDataFieldValues(clazz, Nil) val typeDataGlobal = @@ -326,7 +326,7 @@ class ClassEmitter(coreSpec: CoreSpec) { // When we don't generate a vtable, we still generate the typeData if (!isAbstractClass) { - // Generate an actual vtable + // Generate an actual vtable, which we integrate into the typeData val reflectiveProxies = classInfo.resolvedMethodInfos.valuesIterator.filter(_.methodName.isReflectiveProxy).toList val typeDataFieldValues = genTypeDataFieldValues(clazz, reflectiveProxies) @@ -336,13 +336,9 @@ class ClassEmitter(coreSpec: CoreSpec) { val globalVTable = genTypeDataGlobal(typeRef, vtableTypeName, typeDataFieldValues, vtableElems) ctx.addGlobal(globalVTable) + + // Generate the itable genGlobalClassItable(clazz) - } else if (classInfo.hasRuntimeTypeInfo) { - // Only generate typeData - val typeDataFieldValues = genTypeDataFieldValues(clazz, Nil) - val globalTypeData = - genTypeDataGlobal(typeRef, genTypeName.typeData, typeDataFieldValues, Nil) - ctx.addGlobal(globalTypeData) } // Declare the struct type for the class From cd64997977299a289e2a9a37ba9aed337f2b6201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 16 May 2024 18:15:18 +0200 Subject: [PATCH 05/14] Inline genField into genScalaClass. That centralizes building of all the `StructField`s of a class' struct type. --- .../backend/wasmemitter/ClassEmitter.scala | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) 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 c7e5039..5cc3de9 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 @@ -352,12 +352,16 @@ class ClassEmitter(coreSpec: CoreSpec) { watpe.RefType.nullable(genTypeName.itables), isMutable = false ) - val fields = classInfo.allFieldDefs.map(genField) + val fields = classInfo.allFieldDefs.map { field => + wamod.StructField( + genFieldName.forClassInstanceField(field.name.name), + transformType(field.ftpe), + isMutable = true // initialized by the constructors, so always mutable at the Wasm level + ) + } val structTypeName = genTypeName.forClass(clazz.name.name) val superType = clazz.superClass.map(s => genTypeName.forClass(s.name)) - val structType = wamod.StructType( - vtableField +: itablesField +: fields - ) + val structType = wamod.StructType(vtableField :: itablesField :: fields) val subType = wamod.SubType(structTypeName, isFinal = false, superType, structType) ctx.mainRecType.addSubType(subType) @@ -1198,16 +1202,4 @@ class ClassEmitter(coreSpec: CoreSpec) { fb.buildAndAddToModule() } } - - private def genField( - field: FieldDef - )(implicit ctx: WasmContext): wamod.StructField = { - wamod.StructField( - genFieldName.forClassInstanceField(field.name.name), - transformType(field.ftpe), - // needs to be mutable even if it's flags.isMutable = false - // because it's initialized by constructor - isMutable = true // field.flags.isMutable - ) - } } From 593d43cada354ba3e699bd3c2985ee87e9ca5764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 16 May 2024 20:32:56 +0200 Subject: [PATCH 06/14] Integrate the two overloads of `genTypeDataFieldValues`. Since one was the only caller of the other. --- .../backend/wasmemitter/ClassEmitter.scala | 138 +++++++----------- 1 file changed, 50 insertions(+), 88 deletions(-) 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 5cc3de9..8ea5ee5 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 @@ -100,49 +100,6 @@ class ClassEmitter(coreSpec: CoreSpec) { } } - private def genTypeDataFieldValues( - clazz: LinkedClass, - reflectiveProxies: List[ConcreteMethodInfo] - )(implicit - ctx: WasmContext - ): List[wa.Instr] = { - import genFieldName.typeData.{reflectiveProxies => _, _} - - val className = clazz.className - val classInfo = ctx.getClassInfo(className) - - val kind = className match { - case ObjectClass => KindObject - case BoxedUnitClass => KindBoxedUnit - case BoxedBooleanClass => KindBoxedBoolean - case BoxedCharacterClass => KindBoxedCharacter - case BoxedByteClass => KindBoxedByte - case BoxedShortClass => KindBoxedShort - case BoxedIntegerClass => KindBoxedInteger - case BoxedLongClass => KindBoxedLong - case BoxedFloatClass => KindBoxedFloat - case BoxedDoubleClass => KindBoxedDouble - case BoxedStringClass => KindBoxedString - - case _ => - clazz.kind match { - case ClassKind.Class | ClassKind.ModuleClass | ClassKind.HijackedClass => KindClass - case ClassKind.Interface => KindInterface - case _ => KindJSType - } - } - - val isJSClassInstanceFuncOpt = genIsJSClassInstanceFunction(clazz) - - genTypeDataFieldValues( - kind, - classInfo.specialInstanceTypes, - ClassRef(clazz.className), - isJSClassInstanceFuncOpt, - reflectiveProxies - ) - } - private def genIsJSClassInstanceFunction(clazz: LinkedClass)(implicit ctx: WasmContext ): Option[wanme.FunctionName] = { @@ -188,65 +145,70 @@ class ClassEmitter(coreSpec: CoreSpec) { } private def genTypeDataFieldValues( - kind: Int, - specialInstanceTypes: Int, - typeRef: NonArrayTypeRef, - isJSClassInstanceFuncOpt: Option[wanme.FunctionName], + clazz: LinkedClass, reflectiveProxies: List[ConcreteMethodInfo] )(implicit ctx: WasmContext ): List[wa.Instr] = { - val nameStr = typeRef match { - case typeRef: PrimRef => - typeRef.displayName - case ClassRef(className) => - RuntimeClassNameMapperImpl.map( - coreSpec.semantics.runtimeClassNameMapper, - className.nameString - ) - } + val className = clazz.className + val classInfo = ctx.getClassInfo(className) + val nameStr = RuntimeClassNameMapperImpl.map( + coreSpec.semantics.runtimeClassNameMapper, + className.nameString + ) val nameDataValue: List[wa.Instr] = ctx.getConstantStringDataInstr(nameStr) + val kind = className match { + case ObjectClass => KindObject + case BoxedUnitClass => KindBoxedUnit + case BoxedBooleanClass => KindBoxedBoolean + case BoxedCharacterClass => KindBoxedCharacter + case BoxedByteClass => KindBoxedByte + case BoxedShortClass => KindBoxedShort + case BoxedIntegerClass => KindBoxedInteger + case BoxedLongClass => KindBoxedLong + case BoxedFloatClass => KindBoxedFloat + case BoxedDoubleClass => KindBoxedDouble + case BoxedStringClass => KindBoxedString + + case _ => + clazz.kind match { + case ClassKind.Class | ClassKind.ModuleClass | ClassKind.HijackedClass => KindClass + case ClassKind.Interface => KindInterface + case _ => KindJSType + } + } + val strictAncestorsValue: List[wa.Instr] = { - typeRef match { - case ClassRef(className) => - val ancestors = ctx.getClassInfo(className).ancestors - - // By spec, the first element of `ancestors` is always the class itself - assert( - ancestors.headOption.contains(className), - s"The ancestors of ${className.nameString} do not start with itself: $ancestors" - ) - val strictAncestors = ancestors.tail + val ancestors = ctx.getClassInfo(className).ancestors - val elems = for { - ancestor <- strictAncestors - if ctx.getClassInfo(ancestor).hasRuntimeTypeInfo - } yield { - wa.GlobalGet(genGlobalName.forVTable(ancestor)) - } - elems :+ wa.ArrayNewFixed(genTypeName.typeDataArray, elems.size) - case _ => - wa.RefNull(watpe.HeapType.None) :: Nil + // By spec, the first element of `ancestors` is always the class itself + assert( + ancestors.headOption.contains(className), + s"The ancestors of ${className.nameString} do not start with itself: $ancestors" + ) + val strictAncestors = ancestors.tail + + val elems = for { + ancestor <- strictAncestors + if ctx.getClassInfo(ancestor).hasRuntimeTypeInfo + } yield { + wa.GlobalGet(genGlobalName.forVTable(ancestor)) } + elems :+ wa.ArrayNewFixed(genTypeName.typeDataArray, elems.size) } val cloneFunction = { - val nullref = wa.RefNull(watpe.HeapType.NoFunc) - typeRef match { - case ClassRef(className) => - val classInfo = ctx.getClassInfo(className) - // If the class is concrete and implements the `java.lang.Cloneable`, - // `genCloneFunction` should've generated the clone function - if (!classInfo.isAbstract && classInfo.ancestors.contains(CloneableClass)) - wa.RefFunc(genFunctionName.clone(className)) - else nullref - case _ => nullref - } + // If the class is concrete and implements the `java.lang.Cloneable`, + // `genCloneFunction` should've generated the clone function + if (!classInfo.isAbstract && classInfo.ancestors.contains(CloneableClass)) + wa.RefFunc(genFunctionName.clone(className)) + else + wa.RefNull(watpe.HeapType.NoFunc) } - val isJSClassInstance = isJSClassInstanceFuncOpt match { + val isJSClassInstance = genIsJSClassInstanceFunction(clazz) match { case None => wa.RefNull(watpe.HeapType.NoFunc) case Some(funcName) => wa.RefFunc(funcName) } @@ -267,7 +229,7 @@ class ClassEmitter(coreSpec: CoreSpec) { // kind wa.I32Const(kind), // specialInstanceTypes - wa.I32Const(specialInstanceTypes) + wa.I32Const(classInfo.specialInstanceTypes) ) ::: ( // strictAncestors strictAncestorsValue From cc71fa5593a636bcbebc8369345c69b84ffb6a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 16 May 2024 20:46:29 +0200 Subject: [PATCH 07/14] Factor out more behavior into genTypeDataGlobal. --- .../backend/wasmemitter/ClassEmitter.scala | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) 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 8ea5ee5..1949290 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 @@ -28,11 +28,8 @@ class ClassEmitter(coreSpec: CoreSpec) { if (classInfo.hasRuntimeTypeInfo && !(clazz.kind.isClass && clazz.hasDirectInstances)) { // Gen typeData -- for concrete Scala classes, we do it as part of the vtable generation instead - val typeRef = ClassRef(clazz.className) val typeDataFieldValues = genTypeDataFieldValues(clazz, Nil) - val typeDataGlobal = - genTypeDataGlobal(typeRef, genTypeName.typeData, typeDataFieldValues, Nil) - ctx.addGlobal(typeDataGlobal) + genTypeDataGlobal(clazz.className, genTypeName.typeData, typeDataFieldValues, Nil) } // Declare static fields @@ -255,18 +252,20 @@ class ClassEmitter(coreSpec: CoreSpec) { } private def genTypeDataGlobal( - typeRef: NonArrayTypeRef, + className: ClassName, typeDataTypeName: wanme.TypeName, typeDataFieldValues: List[wa.Instr], vtableElems: List[wa.RefFunc] - )(implicit ctx: WasmContext): wamod.Global = { + )(implicit ctx: WasmContext): Unit = { val instrs: List[wa.Instr] = typeDataFieldValues ::: vtableElems ::: wa.StructNew(typeDataTypeName) :: Nil - wamod.Global( - genGlobalName.forVTable(typeRef), - watpe.RefType(typeDataTypeName), - wamod.Expr(instrs), - isMutable = false + ctx.addGlobal( + wamod.Global( + genGlobalName.forVTable(className), + watpe.RefType(typeDataTypeName), + wamod.Expr(instrs), + isMutable = false + ) ) } @@ -295,9 +294,7 @@ class ClassEmitter(coreSpec: CoreSpec) { val vtableElems = classInfo.tableEntries.map { methodName => wa.RefFunc(classInfo.resolvedMethodInfos(methodName).tableEntryName) } - val globalVTable = - genTypeDataGlobal(typeRef, vtableTypeName, typeDataFieldValues, vtableElems) - ctx.addGlobal(globalVTable) + genTypeDataGlobal(className, vtableTypeName, typeDataFieldValues, vtableElems) // Generate the itable genGlobalClassItable(clazz) From c9ca55cfa522f4f6221b3c7abd4a0dca744caf75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 16 May 2024 20:50:37 +0200 Subject: [PATCH 08/14] Rename genLoadModuleFunc to genModuleAccessor. This corresponds to how it is called in the JS backend. --- .../org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 1949290..aa72fc9 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 @@ -345,7 +345,7 @@ class ClassEmitter(coreSpec: CoreSpec) { ) ctx.addGlobal(global) - genLoadModuleFunc(clazz) + genModuleAccessor(clazz) } } @@ -552,7 +552,7 @@ class ClassEmitter(coreSpec: CoreSpec) { fb.buildAndAddToModule() } - private def genLoadModuleFunc(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { + private def genModuleAccessor(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { implicit val pos = clazz.pos assert(clazz.kind == ClassKind.ModuleClass) From 55e203bac5c6f75a4586855202080cc1b8738501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 16 May 2024 21:01:28 +0200 Subject: [PATCH 09/14] Hard-code the constructor name in `genModuleAccessor`. By spec, the constructor to call for a module class is always `()`, i.e., `NoArgConstructorName`. --- .../backend/wasmemitter/ClassEmitter.scala | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) 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 aa72fc9..2415330 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 @@ -556,19 +556,12 @@ class ClassEmitter(coreSpec: CoreSpec) { implicit val pos = clazz.pos assert(clazz.kind == ClassKind.ModuleClass) - val ctor = clazz.methods - .find(_.methodName.isConstructor) - .getOrElse(throw new Error(s"Module class should have a constructor, ${clazz.name}")) - val typeName = genTypeName.forClass(clazz.name.name) - val globalInstanceName = genGlobalName.forModuleInstance(clazz.name.name) - - val ctorName = genFunctionName.forMethod( - ctor.flags.namespace, - clazz.name.name, - ctor.name.name - ) - val resultTyp = watpe.RefType(typeName) + val className = clazz.className + val globalInstanceName = genGlobalName.forModuleInstance(className) + val ctorName = + genFunctionName.forMethod(MemberNamespace.Constructor, className, NoArgConstructorName) + val resultTyp = watpe.RefType(genTypeName.forClass(className)) val fb = new FunctionBuilder( ctx.moduleBuilder, @@ -587,7 +580,7 @@ class ClassEmitter(coreSpec: CoreSpec) { instrs += wa.BrOnNonNull(nonNullLabel) // create an instance and call its constructor - instrs += wa.Call(genFunctionName.newDefault(clazz.name.name)) + instrs += wa.Call(genFunctionName.newDefault(className)) instrs += wa.LocalTee(instanceLocal) instrs += wa.Call(ctorName) From 340bbd7d7cd8bc9645a191c0cfd467b39a57c313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 16 May 2024 21:05:49 +0200 Subject: [PATCH 10/14] Inline genITableGlobal into genGlobalClassItable. It was its only call site, and separating it does not help. --- .../backend/wasmemitter/ClassEmitter.scala | 34 +++++++------------ 1 file changed, 12 insertions(+), 22 deletions(-) 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 2415330..4eba9b8 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 @@ -598,35 +598,25 @@ class ClassEmitter(coreSpec: CoreSpec) { /** Generate global instance of the class itable. Their init value will be an array of null refs * of size = number of interfaces. They will be initialized in start function */ - private def genGlobalClassItable( - clazz: LinkedClass - )(implicit ctx: WasmContext): Unit = { + private def genGlobalClassItable(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { val info = ctx.getClassInfo(clazz.className) val implementsAnyInterface = info.ancestors.exists(a => ctx.getClassInfo(a).isInterface) if (implementsAnyInterface) { val globalName = genGlobalName.forITable(clazz.className) - ctx.addGlobalITable(clazz.className, genITableGlobal(globalName)) + val itablesInit = List( + wa.I32Const(ctx.itablesLength), + wa.ArrayNewDefault(genTypeName.itables) + ) + val global = wamod.Global( + globalName, + watpe.RefType(genTypeName.itables), + wamod.Expr(itablesInit), + isMutable = false + ) + ctx.addGlobalITable(clazz.className, global) } } - private def genArrayClassItable()(implicit ctx: WasmContext): Unit = - ctx.addGlobal(genITableGlobal(genGlobalName.arrayClassITable)) - - private def genITableGlobal( - name: wanme.GlobalName - )(implicit ctx: WasmContext): wamod.Global = { - val itablesInit = List( - wa.I32Const(ctx.itablesLength), - wa.ArrayNewDefault(genTypeName.itables) - ) - wamod.Global( - name, - watpe.RefType(genTypeName.itables), - wamod.Expr(itablesInit), - isMutable = false - ) - } - private def genInterface(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { assert(clazz.kind == ClassKind.Interface) // gen itable type From 6b61f3529e1af53c89f784cc0420af854e8e2d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 16 May 2024 21:11:18 +0200 Subject: [PATCH 11/14] Replace a `find` by an `exists`. --- .../org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4eba9b8..3c176fc 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 @@ -938,7 +938,7 @@ class ClassEmitter(coreSpec: CoreSpec) { } // Class initializer - for (classInit <- clazz.methods.find(_.methodName.isClassInitializer)) { + if (clazz.methods.exists(_.methodName.isClassInitializer)) { assert( clazz.jsClassCaptures.isEmpty, s"Illegal class initializer in non-static class ${clazz.className.nameString}" From c47dd70c1190e92e3f07d3040b751392b65339b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 16 May 2024 21:15:51 +0200 Subject: [PATCH 12/14] Remove some useless `implicit val pos`. --- .../backend/wasmemitter/ClassEmitter.scala | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) 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 3c176fc..d073361 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 @@ -383,8 +383,6 @@ class ClassEmitter(coreSpec: CoreSpec) { * See https://github.com/tanishiking/scala-wasm/issues/27#issuecomment-2008252049 */ private def genInterfaceInstanceTest(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { - implicit val pos = clazz.pos - assert(clazz.kind == ClassKind.Interface) val classInfo = ctx.getClassInfo(clazz.className) @@ -392,7 +390,7 @@ class ClassEmitter(coreSpec: CoreSpec) { val fb = new FunctionBuilder( ctx.moduleBuilder, genFunctionName.instanceTest(clazz.name.name), - pos + clazz.pos ) val exprParam = fb.addParam("expr", watpe.RefType.anyref) fb.setResultType(watpe.Int32) @@ -474,8 +472,6 @@ class ClassEmitter(coreSpec: CoreSpec) { } private def genNewDefaultFunc(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { - implicit val pos = clazz.pos - val className = clazz.name.name val classInfo = ctx.getClassInfo(className) assert(clazz.hasDirectInstances) @@ -484,7 +480,7 @@ class ClassEmitter(coreSpec: CoreSpec) { val fb = new FunctionBuilder( ctx.moduleBuilder, genFunctionName.newDefault(className), - pos + clazz.pos ) fb.setResultType(watpe.RefType(structName)) @@ -512,15 +508,13 @@ class ClassEmitter(coreSpec: CoreSpec) { * called on the class instance. */ private def genCloneFunction(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { - implicit val pos = clazz.pos - val className = clazz.className val info = ctx.getClassInfo(className) val fb = new FunctionBuilder( ctx.moduleBuilder, genFunctionName.clone(className), - pos + clazz.pos ) val fromParam = fb.addParam("from", watpe.RefType(genTypeName.ObjectStruct)) fb.setResultType(watpe.RefType(genTypeName.ObjectStruct)) @@ -553,8 +547,6 @@ class ClassEmitter(coreSpec: CoreSpec) { } private def genModuleAccessor(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { - implicit val pos = clazz.pos - assert(clazz.kind == ClassKind.ModuleClass) val className = clazz.className @@ -566,7 +558,7 @@ class ClassEmitter(coreSpec: CoreSpec) { val fb = new FunctionBuilder( ctx.moduleBuilder, genFunctionName.loadModule(clazz.className), - pos + clazz.pos ) fb.setResultType(resultTyp) @@ -957,8 +949,6 @@ class ClassEmitter(coreSpec: CoreSpec) { } private def genLoadJSClassFunction(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { - implicit val pos = clazz.pos - val cachedJSClassGlobal = wamod.Global( genGlobalName.forJSClassValue(clazz.className), watpe.RefType.anyref, @@ -970,7 +960,7 @@ class ClassEmitter(coreSpec: CoreSpec) { val fb = new FunctionBuilder( ctx.moduleBuilder, genFunctionName.loadJSClass(clazz.className), - pos + clazz.pos ) fb.setResultType(watpe.RefType.any) @@ -988,8 +978,6 @@ class ClassEmitter(coreSpec: CoreSpec) { } private def genLoadJSModuleFunction(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { - implicit val pos = clazz.pos - val className = clazz.className val cacheGlobalName = genGlobalName.forModuleInstance(className) @@ -1005,7 +993,7 @@ class ClassEmitter(coreSpec: CoreSpec) { val fb = new FunctionBuilder( ctx.moduleBuilder, genFunctionName.loadModule(className), - pos + clazz.pos ) fb.setResultType(watpe.RefType.anyref) From 3d97dd4ca15511dbcd105d252a8aa6bf700faae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 16 May 2024 21:19:16 +0200 Subject: [PATCH 13/14] Remove a useless lookup of getClassInfo. --- .../org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d073361..f483c70 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 @@ -1065,7 +1065,7 @@ class ClassEmitter(coreSpec: CoreSpec) { val functionName = genFunctionName.forMethod(namespace, className, methodName) - val isHijackedClass = ctx.getClassInfo(className).kind == ClassKind.HijackedClass + val isHijackedClass = clazz.kind == ClassKind.HijackedClass val receiverTyp = if (namespace.isStatic) From ce367d18f9118d145949cb8817f192c48c190b3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 16 May 2024 21:21:22 +0200 Subject: [PATCH 14/14] Remove obsolete comment. --- .../scalajs/linker/backend/wasmemitter/ClassEmitter.scala | 6 ------ 1 file changed, 6 deletions(-) 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 f483c70..0d3f161 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 @@ -375,12 +375,6 @@ class ClassEmitter(coreSpec: CoreSpec) { * * The expression `isInstanceOf[]` will be compiled to a CALL to the function * generated by this method. - * - * TODO: Efficient type inclusion test. The current implementation generates a sparse array of - * itables, which, although O(1), may not be optimal for large interfaces. More compressed data - * structures could potentially improve performance in such cases. - * - * See https://github.com/tanishiking/scala-wasm/issues/27#issuecomment-2008252049 */ private def genInterfaceInstanceTest(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { assert(clazz.kind == ClassKind.Interface)