Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Scala.js (https://www.scala-js.org/)
*
* Copyright EPFL.
*
* Licensed under Apache License 2.0
* (https://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package org.scalajs.linker.backend.wasmemitter
import scala.collection.mutable
import org.scalajs.ir.{ClassKind, OriginalName, Position, UTF8String}
import org.scalajs.ir.Names._
import org.scalajs.ir.OriginalName.NoOriginalName
import org.scalajs.ir.Trees._
import org.scalajs.ir.Types._
import org.scalajs.linker.interface.CheckedBehavior
import org.scalajs.linker.interface.unstable.RuntimeClassNameMapperImpl
import org.scalajs.linker.standard.{CoreSpec, LinkedClass, LinkedGlobalInfo, LinkedTopLevelExport}
import org.scalajs.linker.backend.javascript.{Trees => js}
import org.scalajs.linker.backend.webassembly.FunctionBuilder
import org.scalajs.linker.backend.webassembly.{Instructions => wa}
import org.scalajs.linker.backend.webassembly.{Modules => wamod}
import org.scalajs.linker.backend.webassembly.{Identitities => wanme}
import org.scalajs.linker.backend.webassembly.{Types => watpe}
import EmbeddedConstants._
import SWasmGen._
import VarGen._
import TypeTransformer._
import WasmContext._
class ClassEmitter(coreSpec: CoreSpec) {
import ClassEmitter._
import coreSpec.semantics
def genClassDef(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = {
val classInfo = ctx.getClassInfo(clazz.className)
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 typeDataFieldValues = genTypeDataFieldValues(clazz, Nil)
genTypeDataGlobal(clazz.className, genTypeID.typeData, typeDataFieldValues, Nil)
}
// Declare static fields
for {
field @ FieldDef(flags, name, _, ftpe) <- clazz.fields
if flags.namespace.isStatic
} {
val origName = makeDebugName(ns.StaticField, name.name)
val global = wamod.Global(
genGlobalID.forStaticField(name.name),
origName,
isMutable = true,
transformFieldType(ftpe),
wa.Expr(List(genZeroOf(ftpe)))
)
ctx.addGlobal(global)
}
// Generate method implementations
for (method <- clazz.methods) {
if (method.body.isDefined)
genMethod(clazz, method)
}
clazz.kind match {
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 |
ClassKind.NativeJSModuleClass =>
() // nothing to do
}
}
/** Generates code for a top-level export.
*
* It is tempting to use Wasm `export`s for top-level exports. However, that
* does not work in several situations:
*
* - for values, an `export`ed `global` is visible in JS as an instance of
* `WebAssembly.Global`, of which we need to extract the `.value` field anyway
* - this in turn causes issues for mutable static fields, since we need to
* republish changes
* - we cannot distinguish mutable static fields from immutable ones, so we
* have to use the same strategy for both
* - exported top-level `def`s must be seen by JS as `function` functions,
* but `export`ed `func`s are JS arrow functions
*
* Overall, the only things for which `export`s would work are for exported
* JS classes and objects.
*
* Instead, we uniformly use the following strategy for all top-level exports:
*
* - the JS code declares a non-initialized `let` for every top-level export, and exports it
* from the module with an ECMAScript `export`
* - the JS code provides a setter function that we import into a Wasm, which allows to set the
* value of that `let`
* - the Wasm code "publishes" every update to top-level exports to the JS code via this
* setter; this happens once in the `start` function for every kind of top-level export (see
* `Emitter.genStartFunction`), and in addition upon each reassignment of a top-level
* exported field (see `FunctionEmitter.genAssign`).
*
* This method declares the import of the setter on the Wasm side, for all kinds of top-level
* exports. In addition, for exported *methods*, it generates the implementation of the method as
* a Wasm function.
*
* The JS code is generated by `Emitter.buildJSFileContent`. Note that for fields, the JS `let`s
* are only "mirrors" of the state. The source of truth for the state remains in the Wasm Global
* 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.
*
* The calls to the setters, which actually initialize all the exported `let`s, are performed:
*
* - in the `start` function for all kinds of exports, and
* - in addition on every assignment to an exported mutable static field.
*/
def genTopLevelExport(topLevelExport: LinkedTopLevelExport)(
implicit ctx: WasmContext): Unit = {
genTopLevelExportSetter(topLevelExport.exportName)
topLevelExport.tree match {
case d: TopLevelMethodExportDef => genTopLevelMethodExportDef(d)
case _ => ()
}
}
/** Generate common itable global for all array classes. */
def genGlobalArrayClassItable()(implicit ctx: WasmContext): Unit = {
genGlobalClassItable(
genGlobalID.arrayClassITable, ctx.getClassInfo(ObjectClass),
List(SerializableClass, CloneableClass),
OriginalName(genGlobalID.arrayClassITable.toString())
)
}
private def genIsJSClassInstanceFunction(clazz: LinkedClass)(
implicit ctx: WasmContext): Option[wanme.FunctionID] = {
implicit val noPos: Position = Position.NoPosition
val hasIsJSClassInstance = clazz.kind match {
case ClassKind.NativeJSClass => clazz.jsNativeLoadSpec.isDefined
case ClassKind.JSClass => clazz.jsClassCaptures.isEmpty
case _ => false
}
if (hasIsJSClassInstance) {
val className = clazz.className
val fb = new FunctionBuilder(
ctx.moduleBuilder,
genFunctionID.isJSClassInstance(className),
makeDebugName(ns.IsInstance, className),
noPos
)
val xParam = fb.addParam("x", watpe.RefType.anyref)
fb.setResultType(watpe.Int32)
fb.setFunctionType(genTypeID.isJSClassInstanceFuncType)
if (clazz.kind == ClassKind.JSClass && !clazz.hasInstances) {
/* We need to constant-fold the instance test, to avoid trying to
* call $loadJSClass.className, since it will not exist at all.
*/
fb += wa.I32Const(0) // false
} else {
val helperBuilder = new CustomJSHelperBuilder()
val xRef = helperBuilder.addWasmInput("x", watpe.RefType.anyref) {
fb += wa.LocalGet(xParam)
}
val ctorRef = clazz.jsNativeLoadSpec match {
case Some(loadSpec) =>
helperBuilder.genJSNativeLoadSpec(loadSpec)
case None =>
// This is a non-native JS class
helperBuilder.addWasmInput("ctor", watpe.RefType.any) {
fb += wa.Call(genFunctionID.loadJSClass(className))
}
}
val helperID = helperBuilder.build(BooleanType) {
js.Return(js.BinaryOp(JSBinaryOp.instanceof, xRef, ctorRef))
}
fb += wa.Call(helperID)
}
val func = fb.buildAndAddToModule()
Some(func.id)
} else {
None
}
}
private def genTypeDataFieldValues(clazz: LinkedClass,
reflectiveProxies: List[ConcreteMethodInfo])(
implicit ctx: WasmContext): List[wa.Instr] = {
val className = clazz.className
val classInfo = ctx.getClassInfo(className)
val nameStr = RuntimeClassNameMapperImpl.map(
coreSpec.semantics.runtimeClassNameMapper,
className.nameString
)
val nameDataValue: List[wa.Instr] =
ctx.stringPool.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 _ =>
import ClassKind._
clazz.kind match {
case Class | ModuleClass | HijackedClass =>
KindClass
case Interface =>
KindInterface
case JSClass | JSModuleClass | AbstractJSType | NativeJSClass | NativeJSModuleClass =>
if (clazz.superClass.isDefined)
KindJSTypeWithSuperClass
else
KindJSType
}
}
val strictAncestorsTypeData: List[wa.Instr] = {
val ancestors = clazz.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 strictAncestors0 = ancestors.tail
// If the class has a super class, move it first for the benefit of Class_superClass
val strictAncestors = clazz.superClass match {
case Some(ClassIdent(superClass)) =>
superClass :: strictAncestors0.filter(_ != superClass)
case None =>
strictAncestors0
}
val elems = for {
ancestor <- strictAncestors
if ctx.getClassInfo(ancestor).hasRuntimeTypeInfo
} yield {
wa.GlobalGet(genGlobalID.forVTable(ancestor))
}
elems :+ wa.ArrayNewFixed(genTypeID.typeDataArray, elems.size)
}
val cloneFunction = {
// If the class is concrete and implements the `java.lang.Cloneable`,
// `genCloneFunction` should've generated the clone function
if (!classInfo.isAbstract && clazz.ancestors.contains(CloneableClass))
wa.RefFunc(genFunctionID.clone(className))
else
wa.RefNull(watpe.HeapType.NoFunc)
}
val isJSClassInstance = genIsJSClassInstanceFunction(clazz) match {
case None => wa.RefNull(watpe.HeapType.NoFunc)
case Some(funcID) => wa.RefFunc(funcID)
}
val reflectiveProxiesInstrs: List[wa.Instr] = {
val elemsInstrs: List[wa.Instr] = reflectiveProxies
.map(proxyInfo => ctx.getReflectiveProxyId(proxyInfo.methodName) -> proxyInfo.tableEntryID)
.sortBy(_._1) // we will perform a binary search on the ID at run-time
.flatMap { case (proxyID, tableEntryID) =>
List(
wa.I32Const(proxyID),
wa.RefFunc(tableEntryID),
wa.StructNew(genTypeID.reflectiveProxy)
)
}
elemsInstrs :+ wa.ArrayNewFixed(genTypeID.reflectiveProxies, reflectiveProxies.size)
}
nameDataValue :::
List(
// kind
wa.I32Const(kind),
// specialInstanceTypes
wa.I32Const(classInfo.specialInstanceTypes)
) ::: (
// strictAncestors
strictAncestorsTypeData
) :::
List(
// componentType - always `null` since this method is not used for array types
wa.RefNull(watpe.HeapType(genTypeID.typeData)),
// name - initially `null`; filled in by the `typeDataName` helper
wa.RefNull(watpe.HeapType.NoExtern),
// the classOf instance - initially `null`; filled in by the `createClassOf` helper
wa.RefNull(watpe.HeapType(genTypeID.ClassStruct)),
// arrayOf, the typeData of an array of this type - initially `null`; filled in by the `arrayTypeData` helper
wa.RefNull(watpe.HeapType(genTypeID.ObjectVTable)),
// clonefFunction - will be invoked from `clone()` method invokaion on the class
cloneFunction,
// isJSClassInstance - invoked from the `isInstance()` helper for JS types
isJSClassInstance
) :::
// reflective proxies - used to reflective call on the class at runtime.
// Generated instructions create an array of reflective proxy structs, where each struct
// contains the ID of the reflective proxy and a reference to the actual method implementation.
reflectiveProxiesInstrs
}
private def genTypeDataGlobal(className: ClassName, typeDataTypeID: wanme.TypeID,
typeDataFieldValues: List[wa.Instr], vtableElems: List[wa.RefFunc])(
implicit ctx: WasmContext): Unit = {
val instrs: List[wa.Instr] =
typeDataFieldValues ::: vtableElems ::: wa.StructNew(typeDataTypeID) :: Nil
ctx.addGlobal(
wamod.Global(
genGlobalID.forVTable(className),
makeDebugName(ns.TypeData, className),
isMutable = false,
watpe.RefType(typeDataTypeID),
wa.Expr(instrs)
)
)
}
/** 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)
// generate vtable type, this should be done for both abstract and concrete classes
val vtableTypeID = genVTableType(clazz, classInfo)
val isAbstractClass = !clazz.hasDirectInstances
// Generate the vtable and itable for concrete classes
if (!isAbstractClass) {
// Generate an actual vtable, which we integrate into the typeData
val reflectiveProxies =
classInfo.resolvedMethodInfos.valuesIterator.filter(_.methodName.isReflectiveProxy).toList
val typeDataFieldValues = genTypeDataFieldValues(clazz, reflectiveProxies)
val vtableElems = classInfo.tableEntries.map { methodName =>
wa.RefFunc(classInfo.resolvedMethodInfos(methodName).tableEntryID)
}
genTypeDataGlobal(className, vtableTypeID, typeDataFieldValues, vtableElems)
// Generate the itable
genGlobalClassItable(clazz)
}
// Declare the struct type for the class
val vtableField = watpe.StructField(
genFieldID.objStruct.vtable,
vtableOriginalName,
watpe.RefType(vtableTypeID),
isMutable = false
)
val itablesField = watpe.StructField(
genFieldID.objStruct.itables,
itablesOriginalName,
watpe.RefType(genTypeID.itables),
isMutable = false
)
val fields = classInfo.allFieldDefs.map { field =>
watpe.StructField(
genFieldID.forClassInstanceField(field.name.name),
makeDebugName(ns.InstanceField, field.name.name),
transformFieldType(field.ftpe),
isMutable = true // initialized by the constructors, so always mutable at the Wasm level
)
}
val jlClassDataField = if (className == ClassClass) {
// Inject the magic `data` field
watpe.StructField(
genFieldID.classData,
OriginalName("data"),
watpe.RefType(genTypeID.typeData),
isMutable = false
) :: Nil
} else {
Nil
}
val structTypeID = genTypeID.forClass(className)
val superType = clazz.superClass.map(s => genTypeID.forClass(s.name))
val structType = watpe.StructType(vtableField :: itablesField :: fields ::: jlClassDataField)
val subType = watpe.SubType(
structTypeID,
makeDebugName(ns.ClassInstance, className),
isFinal = false,
superType,
structType
)
ctx.mainRecType.addSubType(subType)
// Define the `new` function and possibly the `clone` function, unless the class is abstract
if (!isAbstractClass) {
genNewDefaultFunc(clazz)
if (clazz.ancestors.contains(CloneableClass))
genCloneFunction(clazz)
}
// Generate cast functions
if (clazz.hasInstanceTests && semantics.asInstanceOfs != CheckedBehavior.Unchecked) {
if (className != ObjectClass)
genClassCastFunction(clazz)
}
// Generate the module accessor
if (clazz.kind == ClassKind.ModuleClass && clazz.hasInstances) {
val heapType = watpe.HeapType(genTypeID.forClass(clazz.className))
// global instance
val global = wamod.Global(
genGlobalID.forModuleInstance(className),
makeDebugName(ns.ModuleInstance, className),
isMutable = true,
watpe.RefType.nullable(heapType),
wa.Expr(List(wa.RefNull(heapType)))
)
ctx.addGlobal(global)
if (semantics.moduleInit != CheckedBehavior.Unchecked) {
val initFlagGlobal = wamod.Global(
genGlobalID.forModuleInitFlag(className),
makeDebugName(ns.ModuleInitFlag, className),
isMutable = true,
watpe.Int32,
wa.Expr(List(wa.I32Const(0)))
)
ctx.addGlobal(initFlagGlobal)
}
genModuleAccessor(clazz)
}
}
private def genVTableType(clazz: LinkedClass, classInfo: ClassInfo)(
implicit ctx: WasmContext): wanme.TypeID = {
val className = classInfo.name
val typeID = genTypeID.forVTable(className)
val vtableFields =
classInfo.tableEntries.map { methodName =>
watpe.StructField(
genFieldID.forMethodTableEntry(methodName),
makeDebugName(ns.TableEntry, className, methodName),
watpe.RefType(ctx.tableFunctionType(methodName)),
isMutable = false
)
}
val superType = clazz.superClass match {
case None => genTypeID.typeData
case Some(s) => genTypeID.forVTable(s.name)
}
val structType = watpe.StructType(ctx.coreLib.typeDataStructFields ::: vtableFields)
val subType = watpe.SubType(
typeID,
makeDebugName(ns.VTable, className),
isFinal = false,
Some(superType),
structType
)
ctx.mainRecType.addSubType(subType)
typeID
}
/** Generate type inclusion test for interfaces.
*
* The expression `isInstanceOf[]` will be compiled to a CALL to the function
* generated by this method.
*/
private def genInterfaceInstanceTest(clazz: LinkedClass)(
implicit ctx: WasmContext): Unit = {
assert(clazz.kind == ClassKind.Interface)
val className = clazz.className
val classInfo = ctx.getClassInfo(className)
val fb = new FunctionBuilder(
ctx.moduleBuilder,
genFunctionID.instanceTest(className),
makeDebugName(ns.IsInstance, className),
clazz.pos
)
val exprParam = fb.addParam("expr", watpe.RefType.anyref)
fb.setResultType(watpe.Int32)
if (!clazz.hasInstances) {
/* Interfaces that do not have instances do not receive an itable index,
* so the codegen below would not work. Return a constant false instead.
*/
fb += wa.I32Const(0) // false
} else {
fb.block(watpe.RefType.anyref) { testFail =>
// if expr is not an instance of Object, return false
fb += wa.LocalGet(exprParam)
fb += wa.BrOnCastFail(
testFail,
watpe.RefType.anyref,
watpe.RefType(genTypeID.ObjectStruct)
)
/* Test whether the itable at the target interface's slot is indeed an
* instance of that interface's itable struct type.
*/
fb += wa.StructGet(genTypeID.ObjectStruct, genFieldID.objStruct.itables)
fb += wa.StructGet(
genTypeID.itables,
genFieldID.itablesStruct.itableSlot(classInfo.itableIdx)
)
fb += wa.RefTest(watpe.RefType(genTypeID.forITable(className)))
fb += wa.Return
} // test fail
if (classInfo.isAncestorOfHijackedClass) {
/* It could be a hijacked class instance that implements this interface.
* Test whether `jsValueType(expr)` is in the `specialInstanceTypes` bitset.
* In other words, return `((1 << jsValueType(expr)) & specialInstanceTypes) != 0`.
*
* For example, if this class is `Comparable`,
* `specialInstanceTypes == 0b00001111`, since `jl.Boolean`, `jl.String`
* and `jl.Double` implement `Comparable`, but `jl.Void` does not.
* If `expr` is a `number`, `jsValueType(expr) == 3`. We then test whether
* `(1 << 3) & 0b00001111 != 0`, which is true because `(1 << 3) == 0b00001000`.
* If `expr` is `undefined`, it would be `(1 << 4) == 0b00010000`, which
* would give `false`.
*/
val anyRefToVoidSig = watpe.FunctionType(List(watpe.RefType.anyref), Nil)
val exprNonNullLocal = fb.addLocal("exprNonNull", watpe.RefType.any)
fb.block(anyRefToVoidSig) { isNullLabel =>
// exprNonNull := expr; branch to isNullLabel if it is null
fb += wa.BrOnNull(isNullLabel)
fb += wa.LocalSet(exprNonNullLocal)
// Load 1 << jsValueType(expr)
fb += wa.I32Const(1)
fb += wa.LocalGet(exprNonNullLocal)
fb += wa.Call(genFunctionID.jsValueType)
fb += wa.I32Shl
// return (... & specialInstanceTypes) != 0
fb += wa.I32Const(classInfo.specialInstanceTypes)
fb += wa.I32And
fb += wa.I32Const(0)
fb += wa.I32Ne
fb += wa.Return
}
fb += wa.I32Const(0) // false
} else {
fb += wa.Drop
fb += wa.I32Const(0) // false
}
}
fb.buildAndAddToModule()
}
/** Generate the cast function for an interface.
*
* When `asInstanceOfs` are checked, the expression `asInstanceOf[]`
* will be compiled to a CALL to the function generated by this method.
*/
private def genInterfaceCastFunction(clazz: LinkedClass)(
implicit ctx: WasmContext): Unit = {
assert(clazz.kind == ClassKind.Interface)
val className = clazz.className
val resultType = TypeTransformer.transformClassType(className, nullable = true)
val fb = new FunctionBuilder(
ctx.moduleBuilder,
genFunctionID.asInstance(ClassType(className, nullable = true)),
makeDebugName(ns.AsInstance, className),
clazz.pos
)
val objParam = fb.addParam("obj", watpe.RefType.anyref)
fb.setResultType(resultType)
fb.block() { successLabel =>
// Succeed if null
fb += wa.LocalGet(objParam)
fb += wa.BrOnNull(successLabel)
// Succeed if the instance test succeeds
fb += wa.Call(genFunctionID.instanceTest(className))
fb += wa.BrIf(successLabel)
// If we get here, it's a CCE
fb += wa.LocalGet(objParam)
fb += wa.GlobalGet(genGlobalID.forVTable(className))
fb += wa.Call(genFunctionID.classCastException)
fb += wa.Unreachable
}
fb += wa.LocalGet(objParam)
if (resultType != watpe.RefType.anyref)
fb += wa.RefCast(resultType)
fb.buildAndAddToModule()
}
private def genNewDefaultFunc(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = {
val className = clazz.name.name
val classInfo = ctx.getClassInfo(className)
assert(clazz.hasDirectInstances)
val structTypeID = genTypeID.forClass(className)
val fb = new FunctionBuilder(
ctx.moduleBuilder,
genFunctionID.newDefault(className),
makeDebugName(ns.NewDefault, className),
clazz.pos
)
val dataParamOpt =
if (className == ClassClass) Some(fb.addParam("data", watpe.RefType(genTypeID.typeData)))
else None
fb.setResultType(watpe.RefType(structTypeID))
fb += wa.GlobalGet(genGlobalID.forVTable(className))
if (classInfo.classImplementsAnyInterface)
fb += wa.GlobalGet(genGlobalID.forITable(className))
else
fb += wa.GlobalGet(genGlobalID.emptyITable)
classInfo.allFieldDefs.foreach { f =>
fb += genZeroOf(f.ftpe)
}
for (dataParam <- dataParamOpt)
fb += wa.LocalGet(dataParam)
fb += wa.StructNew(structTypeID)
fb.buildAndAddToModule()
}
/** Generates the clone function for the given class, if it is concrete and
* implements the Cloneable interface.
*
* The generated clone function will be registered in the typeData of the class (which
* resides in the vtable of the class), and will be invoked for a `Clone` IR tree on
* the class instance.
*/
private def genCloneFunction(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = {
val className = clazz.className
val info = ctx.getClassInfo(className)
val fb = new FunctionBuilder(
ctx.moduleBuilder,
genFunctionID.clone(className),
makeDebugName(ns.Clone, className),
clazz.pos
)
val fromParam = fb.addParam("from", watpe.RefType(genTypeID.ObjectStruct))
fb.setResultType(watpe.RefType(genTypeID.ObjectStruct))
fb.setFunctionType(genTypeID.cloneFunctionType)
val structTypeID = genTypeID.forClass(className)
val structRefType = watpe.RefType(structTypeID)
val fromTypedLocal = fb.addLocal("fromTyped", structRefType)
// Downcast fromParam to fromTyped
fb += wa.LocalGet(fromParam)
fb += wa.RefCast(structRefType)
fb += wa.LocalSet(fromTypedLocal)
// Push vtable and itables on the stack (there is at least Cloneable in the itables)
fb += wa.GlobalGet(genGlobalID.forVTable(className))
fb += wa.GlobalGet(genGlobalID.forITable(className))
// Push every field of `fromTyped` on the stack
info.allFieldDefs.foreach { field =>
fb += wa.LocalGet(fromTypedLocal)
fb += wa.StructGet(structTypeID, genFieldID.forClassInstanceField(field.name.name))
}
// Create the result
fb += wa.StructNew(structTypeID)
fb.buildAndAddToModule()
}
/** Generate the cast function for a class.
*
* When `asInstanceOfs` are checked, the expression `asInstanceOf[]`
* will be compiled to a CALL to the function generated by this method.
*/
private def genClassCastFunction(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = {
val className = clazz.className
val resultType = TypeTransformer.transformClassType(className, nullable = true)
val fb = new FunctionBuilder(
ctx.moduleBuilder,
genFunctionID.asInstance(ClassType(clazz.className, nullable = true)),
makeDebugName(ns.AsInstance, className),
clazz.pos
)
val objParam = fb.addParam("obj", watpe.RefType.anyref)
fb.setResultType(resultType)
fb.block(resultType) { successLabel =>
fb += wa.LocalGet(objParam)
if (className == SpecialNames.JLNumberClass) {
/* jl.Number is special, because it is the only non-Object *class*
* that is an ancestor of a hijacked class.
*/
fb += wa.BrOnCast(successLabel, watpe.RefType.anyref,
watpe.RefType.nullable(genTypeID.forClass(SpecialNames.JLNumberClass)))
/* The `obj` still on the stack will be used for:
* a) the result in the true case
* b) consistency with non-Number in the false case
*/
fb += wa.LocalGet(objParam)
fb += wa.Call(genFunctionID.typeTest(DoubleRef))
fb += wa.BrIf(successLabel)
} else {
fb += wa.BrOnCast(successLabel, watpe.RefType.anyref, resultType)
}
// If we get here, it's a CCE -- `obj` is still on the stack
fb += wa.GlobalGet(genGlobalID.forVTable(className))
fb += wa.Call(genFunctionID.classCastException)
fb += wa.Unreachable
}
fb.buildAndAddToModule()
}
private def genModuleAccessor(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = {
assert(clazz.kind == ClassKind.ModuleClass)
val className = clazz.className
val globalInstanceID = genGlobalID.forModuleInstance(className)
val ctorID =
genFunctionID.forMethod(MemberNamespace.Constructor, className, NoArgConstructorName)
val resultType = watpe.RefType(genTypeID.forClass(className))
val fb = new FunctionBuilder(
ctx.moduleBuilder,
genFunctionID.loadModule(clazz.className),
makeDebugName(ns.ModuleAccessor, className),
clazz.pos
)
if (semantics.moduleInit == CheckedBehavior.Compliant)
fb.setResultType(resultType.toNullable)
else
fb.setResultType(resultType)
val instanceLocal = fb.addLocal("instance", resultType)
fb.block(resultType) { nonNullLabel =>
// load global, return if not null
fb += wa.GlobalGet(globalInstanceID)
fb += wa.BrOnNonNull(nonNullLabel)
// check ongoing initialization
if (semantics.moduleInit != CheckedBehavior.Unchecked) {
val initFlagID = genGlobalID.forModuleInitFlag(className)
// if being initialized
fb += wa.GlobalGet(initFlagID)
fb.ifThen() {
if (semantics.moduleInit == CheckedBehavior.Compliant) {
// then, return null
fb += wa.RefNull(watpe.HeapType.None)
fb += wa.Return
} else {
// then, throw
fb += wa.GlobalGet(genGlobalID.forVTable(className))
fb += wa.Call(genFunctionID.throwModuleInitError)
fb += wa.Unreachable // for clarity; technically redundant since the stacks align
}
}
// mark as being initialized
fb += wa.I32Const(1)
fb += wa.GlobalSet(initFlagID)
}
// create an instance and call its constructor
fb += wa.Call(genFunctionID.newDefault(className))
fb += wa.LocalTee(instanceLocal)
fb += wa.Call(ctorID)
// store it in the global
fb += wa.LocalGet(instanceLocal)
fb += wa.GlobalSet(globalInstanceID)
// return it
fb += wa.LocalGet(instanceLocal)
}
fb.buildAndAddToModule()
}
/** Generates the global instance of the class itable.
*
* If the class implements no interface at all, we skip this step. Instead,
* we will use the unique `emptyITable` as itable for this class.
*/
private def genGlobalClassItable(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = {
val className = clazz.className
val classInfo = ctx.getClassInfo(className)
if (classInfo.classImplementsAnyInterface) {
genGlobalClassItable(
genGlobalID.forITable(className),
classInfo,
clazz.ancestors,
makeDebugName(ns.ITable, classInfo.name)
)
}
}
private def genGlobalClassItable(classITableGlobalID: wanme.GlobalID,
classInfoForResolving: WasmContext.ClassInfo, ancestors: List[ClassName],
originalName: OriginalName)(
implicit ctx: WasmContext): Unit = {
val itablesInit = Array.fill[List[wa.Instr]](ctx.itablesLength) {
List(wa.RefNull(watpe.HeapType.Struct))
}
val resolvedMethodInfos = classInfoForResolving.resolvedMethodInfos
for {
ancestor <- ancestors
// Use getClassInfoOption in case the reachability analysis got rid of those interfaces
interfaceInfo <- ctx.getClassInfoOption(ancestor)
if interfaceInfo.isInterface
} {
val init = interfaceInfo.tableEntries.map { method =>
wa.RefFunc(resolvedMethodInfos(method).tableEntryID)
} :+ wa.StructNew(genTypeID.forITable(ancestor))
itablesInit(interfaceInfo.itableIdx) = init
}
val global = wamod.Global(
classITableGlobalID,
originalName,
isMutable = false,
watpe.RefType(genTypeID.itables),
wa.Expr(itablesInit.flatten.toList :+ wa.StructNew(genTypeID.itables))
)
ctx.addGlobal(global)
}
private def genInterface(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = {
assert(clazz.kind == ClassKind.Interface)
// gen itable type
val className = clazz.name.name
val classInfo = ctx.getClassInfo(clazz.className)
val itableTypeID = genTypeID.forITable(className)
val itableType = watpe.StructType(
classInfo.tableEntries.map { methodName =>
watpe.StructField(
genFieldID.forMethodTableEntry(methodName),
makeDebugName(ns.TableEntry, className, methodName),
watpe.RefType(ctx.tableFunctionType(methodName)),
isMutable = false
)
}
)
ctx.mainRecType.addSubType(
itableTypeID,
makeDebugName(ns.ITable, className),
itableType
)
if (clazz.hasInstanceTests) {
genInterfaceInstanceTest(clazz)
if (semantics.asInstanceOfs != CheckedBehavior.Unchecked)
genInterfaceCastFunction(clazz)
}
}
private def genJSClass(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = {
assert(clazz.kind.isJSClass)
// Define the globals holding the Symbols of private fields
for (fieldDef <- clazz.fields) {
fieldDef match {
case FieldDef(flags, name, _, _) if !flags.namespace.isStatic =>
ctx.addGlobal(
wamod.Global(
genGlobalID.forJSPrivateField(name.name),
makeDebugName(ns.PrivateJSField, name.name),
isMutable = true,
watpe.RefType.anyref,
wa.Expr(List(wa.RefNull(watpe.HeapType.Any)))
)
)
case _ =>
()
}
}
if (clazz.hasInstances) {
genCreateJSClassFunction(clazz)
if (clazz.jsClassCaptures.isEmpty)
genLoadJSClassFunction(clazz)
if (clazz.kind == ClassKind.JSModuleClass)
genLoadJSModuleFunction(clazz)
}
}
private def genCreateJSClassFunction(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = {
implicit val pos: Position = Position.NoPosition
val className = clazz.className
val jsClassCaptures = clazz.jsClassCaptures.getOrElse(Nil)
/* We need to decompose the body of the constructor into 3 closures.
* Given an IR constructor of the form
* constructor(...params) {
* preSuperStats;
* super(...superArgs);
* postSuperStats;
* }
* We will create closures for `preSuperStats`, `superArgs` and `postSuperStats`.
*
* There is one huge catch: `preSuperStats` can declare `VarDef`s at its top-level,
* and those vars are still visible inside `superArgs` and `postSuperStats`.
* The `preSuperStats` must therefore return a struct with the values of its
* declared vars, which will be given as an additional argument to `superArgs`
* and `postSuperStats`. We call that struct the `preSuperEnv`.
*
* In the future, we should optimize `preSuperEnv` to only store locals that
* are still used by `superArgs` and/or `postSuperArgs`.
*/
val preSuperStatsFunctionID = genFunctionID.preSuperStats(className)
val superArgsFunctionID = genFunctionID.superArgs(className)
val postSuperStatsFunctionID = genFunctionID.postSuperStats(className)
val ctor = clazz.jsConstructorDef.get
FunctionEmitter.emitJSConstructorFunctions(
preSuperStatsFunctionID,
superArgsFunctionID,
postSuperStatsFunctionID,
className,
jsClassCaptures,
ctor
)
val fb = new FunctionBuilder(
ctx.moduleBuilder,
genFunctionID.createJSClassOf(className),
makeDebugName(ns.CreateJSClass, className),
clazz.pos
)
val classCaptureParams = jsClassCaptures.map { cc =>
fb.addParam("cc." + cc.name.name.nameString, transformParamType(cc.ptpe))
}
fb.setResultType(watpe.RefType.any)
val dataStructTypeID = ctx.getClosureDataStructType(jsClassCaptures.map(_.ptpe))
val dataStructLocal = fb.addLocal("classCaptures", watpe.RefType(dataStructTypeID))
val jsClassLocal = fb.addLocal("jsClass", watpe.RefType.any)
// Build the actual `createJSClass` helper
val createJSClassHelperID: wanme.FunctionID = {
// --- Actual start of instructions of `createJSClass`
// Bundle class captures in a capture data struct
for (classCaptureParam <- classCaptureParams)
fb += wa.LocalGet(classCaptureParam)
fb += wa.StructNew(dataStructTypeID)
fb += wa.LocalSet(dataStructLocal)
val classCaptureParamsOfTypeAny: Map[LocalName, wanme.LocalID] = {
jsClassCaptures
.zip(classCaptureParams)
.collect { case (ParamDef(ident, _, AnyType, _), param) =>
ident.name -> param
}
.toMap
}
val helperBuilder = new CustomJSHelperBuilder.WithTreeEval() {
protected def evalTreeAtCallSite(tree: Tree, expectedType: Type): Unit = tree match {
case VarRef(LocalIdent(localName)) if classCaptureParamsOfTypeAny.contains(localName) =>
/* Common shape for the `jsSuperClass` value
* We can only deal with class captures of type `AnyType` in this way,
* since otherwise we might need `adapt` to box the values.
*/
fb += wa.LocalGet(classCaptureParamsOfTypeAny(localName))
case _ =>
// For everything else, put the tree in its own function and call it
val closureFuncID = new JSClassClosureFunctionID(className)
FunctionEmitter.emitFunction(
closureFuncID,
NoOriginalName,
enclosingClassName = None,
Some(jsClassCaptures),
receiverType = None,
paramDefs = Nil,
restParam = None,
tree,
AnyType
)
fb += wa.LocalGet(dataStructLocal)
fb += wa.Call(closureFuncID)
}
}
/* Get a Tree for the super constructor; specified by
* https://lampwww.epfl.ch/~doeraene/sjsir-semantics/#sec-sjsir-classdef-runtime-semantics-evaluation
* - if `jsSuperClass` is defined, evaluate it;
* - otherwise load the JS constructor of the declared superClass,
* as if by `LoadJSConstructor`.
*/
val jsSuperClassTree = clazz.jsSuperClass.getOrElse {
LoadJSConstructor(clazz.superClass.get.name)
}
val dataRef = helperBuilder.addWasmInput("data", watpe.RefType(dataStructTypeID)) {
fb += wa.LocalGet(dataStructLocal)
}
val preSuperStatsFunctionRef = helperBuilder.addWasmInput("preSuperStats", watpe.RefType.func) {
fb += ctx.refFuncWithDeclaration(preSuperStatsFunctionID)
}
val superArgsFunctionRef = helperBuilder.addWasmInput("superArgs", watpe.RefType.func) {
fb += ctx.refFuncWithDeclaration(superArgsFunctionID)
}
val postSuperStatsFunctionRef = helperBuilder.addWasmInput("postSuperStats", watpe.RefType.func) {
fb += ctx.refFuncWithDeclaration(postSuperStatsFunctionID)
}
def genDefineProperty(obj: js.Tree, name: js.Tree, value: js.Tree): js.Tree = {
js.Apply(
js.DotSelect(js.VarRef(js.Ident("Object")), js.Ident("defineProperty")),
List(
obj,
name,
js.ObjectConstr(
List(
js.Ident("configurable") -> js.BooleanLiteral(true),
js.Ident("enumerable") -> js.BooleanLiteral(true),
js.Ident("writable") -> js.BooleanLiteral(true),
js.Ident("value") -> value
)
)
)
)
}
def toJSPropertyName(tree: js.Tree): js.PropertyName = tree match {
case js.StringLiteral("constructor") => js.ComputedName(tree)
case js.StringLiteral(name) if js.Ident.isValidJSIdentifierName(name) => js.Ident(name)
case tree: js.StringLiteral => tree
case _ => js.ComputedName(tree)
}
val jsClassIdent = helperBuilder.newLocalIdent("cls")
val jsCtorDef: js.MethodDef = {
val JSConstructorDef(_, params, restParam, body) = ctor
val (paramDefs, restParamDef) = helperBuilder.genJSParamDefs(params, restParam)
val allParamRefs = (paramDefs ::: restParamDef.toList).map(_.ref)
js.MethodDef(static = false, js.Ident("constructor"), paramDefs, restParamDef, {
val preSuperEnv = helperBuilder.newLocalIdent("preSuperEnv")
js.Block(
// var preSuperEnv = preSuperStats(data, new.target, ...allParamRefs);
js.VarDef(preSuperEnv, Some(js.Apply(preSuperStatsFunctionRef,
dataRef :: js.NewTarget() :: allParamRefs))),
// super(...superArgs(data, preSuperEnv, new.target, ...args));
js.Apply(
js.Super(),
List(
js.Spread(
js.Apply(
superArgsFunctionRef,
dataRef :: js.VarRef(preSuperEnv) :: js.NewTarget() :: allParamRefs
)
)
)
),
// Initialize fields to the (boxed) zero of their type
js.Block(for (fieldDef <- clazz.fields if !fieldDef.flags.namespace.isStatic) yield {
val nameRef = fieldDef match {
case FieldDef(_, name, _, _) =>
helperBuilder.addWasmInput("name", watpe.RefType.anyref) {
fb += wa.GlobalGet(genGlobalID.forJSPrivateField(name.name))
}
case JSFieldDef(_, nameTree, _) =>
helperBuilder.addInput(nameTree)
}
val valueRef = helperBuilder.addInput(zeroOf(fieldDef.ftpe))
genDefineProperty(js.This(), nameRef, valueRef)
}),
// postSuperStats(data, preSuperEnv, new.target, this, ...args);
js.Apply(postSuperStatsFunctionRef,
dataRef :: js.VarRef(preSuperEnv) :: js.NewTarget() :: js.This() :: allParamRefs)
)
})
}
// Methods and properties
val jsMethodProps: List[js.Tree] = clazz.exportedMembers.flatMap { methodOrProp =>
val isStatic = methodOrProp.flags.namespace.isStatic
val jsThisUnlessStatic = if (isStatic) Nil else List(js.This())
val receiverType = if (isStatic) None else Some(watpe.RefType.anyref)
methodOrProp match {
case JSMethodDef(flags, nameTree, params, restParam, body) =>
val nameRef = toJSPropertyName(helperBuilder.addInput(nameTree))
val closureFuncID = new JSClassClosureFunctionID(className)
FunctionEmitter.emitFunction(
closureFuncID,
NoOriginalName, // TODO Come up with something here?
Some(className),
Some(jsClassCaptures),
receiverType,
params,
restParam,
body,
AnyType
)
val fRef = helperBuilder.addWasmInput("f", watpe.RefType.func) {
fb += ctx.refFuncWithDeclaration(closureFuncID)
}
val (argsParamDefs, restParamDef) = helperBuilder.genJSParamDefs(params, restParam)
val jsMethodDef = js.MethodDef(isStatic, nameRef, argsParamDefs, restParamDef, {
js.Return(js.Apply(
fRef,
dataRef ::
jsThisUnlessStatic :::
argsParamDefs.map(_.ref) :::
restParamDef.map(_.ref).toList
))
})
List(jsMethodDef)
case JSPropertyDef(flags, nameTree, optGetter, optSetter) =>
val nameRef = toJSPropertyName(helperBuilder.addInput(nameTree))
val jsGetter = optGetter.map { getterBody =>
val closureFuncID = new JSClassClosureFunctionID(className)
FunctionEmitter.emitFunction(
closureFuncID,
NoOriginalName, // TODO Come up with something here?
Some(className),
Some(jsClassCaptures),
receiverType,
paramDefs = Nil,
restParam = None,
getterBody,
resultType = AnyType
)
val getterRef = helperBuilder.addWasmInput("get", watpe.RefType.func) {
fb += ctx.refFuncWithDeclaration(closureFuncID)
}
js.GetterDef(isStatic, nameRef, {
js.Return(js.Apply(getterRef, dataRef :: jsThisUnlessStatic))
})
}
val jsSetter = optSetter.map { setter =>
val (setterParamDef, setterBody) = setter
val closureFuncID = new JSClassClosureFunctionID(className)
FunctionEmitter.emitFunction(
closureFuncID,
NoOriginalName, // TODO Come up with something here?
Some(className),
Some(jsClassCaptures),
receiverType,
setterParamDef :: Nil,
restParam = None,
setterBody,
resultType = NoType
)
val setterRef = helperBuilder.addWasmInput("set", watpe.RefType.func) {
fb += ctx.refFuncWithDeclaration(closureFuncID)
}
val jsSetterParamDef = helperBuilder.genJSParamDef(setterParamDef)
js.SetterDef(isStatic, nameRef, jsSetterParamDef, {
js.Apply(setterRef, dataRef :: jsThisUnlessStatic ::: jsSetterParamDef.ref :: Nil)
})
}
jsGetter.toList ::: jsSetter.toList
}
}
val jsSuperClass = helperBuilder.addInput(jsSuperClassTree)
val jsClassDef = js.ClassDef(Some(jsClassIdent), Some(jsSuperClass),
jsCtorDef :: jsMethodProps)
// Static fields
val jsInitStaticFields = for {
fieldDef <- clazz.fields if fieldDef.flags.namespace.isStatic
} yield {
// Name
val nameRef = fieldDef match {
case FieldDef(_, name, _, _) =>
throw new AssertionError(
s"Unexpected private static field ${name.name.nameString} "
+ s"in JS class ${className.nameString}"
)
case JSFieldDef(_, nameTree, _) =>
helperBuilder.addInput(nameTree)
}
// Generate boxed representation of the zero of the field
val valueRef = helperBuilder.addInput(zeroOf(fieldDef.ftpe))
genDefineProperty(js.VarRef(jsClassIdent), nameRef, valueRef)
}
// Complete the helper
helperBuilder.build(AnyNotNullType) {
js.Block(
jsClassDef ::
jsInitStaticFields :::
js.Return(js.VarRef(jsClassIdent)) ::
Nil
)
}
}
// Call the helper to produce the JS class value
fb += wa.Call(createJSClassHelperID)
// Store the result, locally in `jsClass` and possibly in the global cache
if (clazz.jsClassCaptures.isEmpty) {
/* Static JS class with a global cache. We must fill the global cache
* before we call the class initializer, later in the current function.
*/
fb += wa.LocalTee(jsClassLocal)
fb += wa.GlobalSet(genGlobalID.forJSClassValue(className))
} else {
// Local or inner JS class, which is new every time
fb += wa.LocalSet(jsClassLocal)
}
// Class initializer
if (clazz.methods.exists(_.methodName.isClassInitializer)) {
assert(
clazz.jsClassCaptures.isEmpty,
s"Illegal class initializer in non-static class ${className.nameString}"
)
val namespace = MemberNamespace.StaticConstructor
fb += wa.Call(
genFunctionID.forMethod(namespace, className, ClassInitializerName)
)
}
// Final result
fb += wa.LocalGet(jsClassLocal)
fb.buildAndAddToModule()
}
private def genLoadJSClassFunction(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = {
require(clazz.jsClassCaptures.isEmpty)
val className = clazz.className
val cachedJSClassGlobal = wamod.Global(
genGlobalID.forJSClassValue(className),
makeDebugName(ns.JSClassValueCache, className),
isMutable = true,
watpe.RefType.anyref,
wa.Expr(List(wa.RefNull(watpe.HeapType.Any)))
)
ctx.addGlobal(cachedJSClassGlobal)
val fb = new FunctionBuilder(
ctx.moduleBuilder,
genFunctionID.loadJSClass(className),
makeDebugName(ns.JSClassAccessor, className),
clazz.pos
)
fb.setResultType(watpe.RefType.any)
fb.block(watpe.RefType.any) { doneLabel =>
// Load cached JS class, return if non-null
fb += wa.GlobalGet(cachedJSClassGlobal.id)
fb += wa.BrOnNonNull(doneLabel)
// Otherwise, call createJSClass -- it will also store the class in the cache
fb += wa.Call(genFunctionID.createJSClassOf(className))
}
fb.buildAndAddToModule()
}
private def genLoadJSModuleFunction(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = {
val className = clazz.className
val cacheGlobalID = genGlobalID.forModuleInstance(className)
ctx.addGlobal(
wamod.Global(
cacheGlobalID,
makeDebugName(ns.ModuleInstance, className),
isMutable = true,
watpe.RefType.anyref,
wa.Expr(List(wa.RefNull(watpe.HeapType.Any)))
)
)
val fb = new FunctionBuilder(
ctx.moduleBuilder,
genFunctionID.loadModule(className),
makeDebugName(ns.ModuleAccessor, className),
clazz.pos
)
fb.setResultType(watpe.RefType.anyref)
fb.block(watpe.RefType.anyref) { doneLabel =>
// Load cached instance; return if non-null
fb += wa.GlobalGet(cacheGlobalID)
fb += wa.BrOnNonNull(doneLabel)
// Get the JS class and instantiate it
fb += wa.Call(genFunctionID.loadJSClass(className))
fb += wa.Call(genFunctionID.jsNewNoArg)
// Store and return the result
fb += wa.GlobalSet(cacheGlobalID)
fb += wa.GlobalGet(cacheGlobalID)
}
fb.buildAndAddToModule()
}
/** Generates the function import for a top-level export setter. */
private def genTopLevelExportSetter(exportedName: String)(implicit ctx: WasmContext): Unit = {
val functionID = genFunctionID.forTopLevelExportSetter(exportedName)
val functionSig = watpe.FunctionType(List(watpe.RefType.anyref), Nil)
val functionType = ctx.moduleBuilder.functionTypeToTypeID(functionSig)
ctx.moduleBuilder.addImport(
wamod.Import(
"__scalaJSExportSetters",
exportedName,
wamod.ImportDesc.Func(
functionID,
makeDebugName(ns.TopLevelExportSetter, exportedName),
functionType
)
)
)
}
private def genTopLevelMethodExportDef(exportDef: TopLevelMethodExportDef)(
implicit ctx: WasmContext): Unit = {
implicit val pos = exportDef.pos
val method = exportDef.methodDef
val exportedName = exportDef.topLevelExportName
val functionID = genFunctionID.forExport(exportedName)
FunctionEmitter.emitFunction(
functionID,
makeDebugName(ns.TopLevelExport, exportedName),
enclosingClassName = None,
captureParamDefs = None,
receiverType = None,
method.args,
method.restParam,
method.body,
resultType = AnyType
)
}
private def genMethod(clazz: LinkedClass, method: MethodDef)(
implicit ctx: WasmContext): Unit = {
implicit val pos = method.pos
val namespace = method.flags.namespace
val className = clazz.className
val methodName = method.methodName
val functionID = genFunctionID.forMethod(namespace, className, methodName)
val namespaceUTF8String = namespace match {
case MemberNamespace.Public => ns.Public
case MemberNamespace.PublicStatic => ns.PublicStatic
case MemberNamespace.Private => ns.Private
case MemberNamespace.PrivateStatic => ns.PrivateStatic
case MemberNamespace.Constructor => ns.Constructor
case MemberNamespace.StaticConstructor => ns.StaticConstructor
}
val originalName = makeDebugName(namespaceUTF8String, className, methodName)
val isHijackedClass = clazz.kind == ClassKind.HijackedClass
val receiverType =
if (namespace.isStatic)
None
else if (isHijackedClass)
Some(transformPrimType(BoxedClassToPrimType(className)))
else
Some(transformClassType(className, nullable = false))
val body = method.body.getOrElse(throw new Exception("abstract method cannot be transformed"))
// Emit the function
FunctionEmitter.emitFunction(
functionID,
originalName,
Some(className),
captureParamDefs = None,
receiverType,
method.args,
restParam = None,
body,
method.resultType
)
if (namespace == MemberNamespace.Public && !isHijackedClass) {
/* Also generate the bridge that is stored in the table entries. In table
* entries, the receiver type is always `(ref any)`.
*
* TODO: generate this only when the method is actually referred to from
* at least one table.
*/
val fb = new FunctionBuilder(
ctx.moduleBuilder,
genFunctionID.forTableEntry(className, methodName),
makeDebugName(ns.TableEntry, className, methodName),
pos
)
val receiverParam = fb.addParam(thisOriginalName, watpe.RefType.any)
val argParams = method.args.map { arg =>
val origName = arg.originalName.orElse(arg.name.name)
fb.addParam(origName, TypeTransformer.transformParamType(arg.ptpe))
}
fb.setResultTypes(TypeTransformer.transformResultType(method.resultType))
fb.setFunctionType(ctx.tableFunctionType(methodName))
// Load and cast down the receiver
fb += wa.LocalGet(receiverParam)
receiverType match {
case Some(watpe.RefType(_, watpe.HeapType.Any)) =>
() // no cast necessary
case Some(receiverType: watpe.RefType) =>
fb += wa.RefCast(receiverType)
case _ =>
throw new AssertionError(s"Unexpected receiver type $receiverType")
}
// Load the other parameters
for (argParam <- argParams)
fb += wa.LocalGet(argParam)
// Call the statically resolved method
fb += wa.ReturnCall(functionID)
fb.buildAndAddToModule()
}
}
private def makeDebugName(namespace: UTF8String, exportedName: String): OriginalName =
OriginalName(namespace ++ UTF8String(exportedName))
private def makeDebugName(namespace: UTF8String, className: ClassName): OriginalName =
OriginalName(namespace ++ className.encoded)
private def makeDebugName(namespace: UTF8String, fieldName: FieldName): OriginalName = {
OriginalName(
namespace ++ fieldName.className.encoded ++ dotUTF8String ++ fieldName.simpleName.encoded
)
}
private def makeDebugName(
namespace: UTF8String,
className: ClassName,
methodName: MethodName
): OriginalName = {
// TODO Opt: directly encode the MethodName rather than using nameString
val methodNameUTF8 = UTF8String(methodName.nameString)
OriginalName(namespace ++ className.encoded ++ dotUTF8String ++ methodNameUTF8)
}
}
object ClassEmitter {
private final class JSClassClosureFunctionID(classNameDebug: ClassName) extends wanme.FunctionID {
override def toString(): String =
s"JSClassClosureFunctionID(${classNameDebug.nameString})"
}
private val dotUTF8String: UTF8String = UTF8String(".")
// These particular names are the same as in the JS backend
private object ns {
// Shared with JS backend -- className + methodName
val Public = UTF8String("f.")
val PublicStatic = UTF8String("s.")
val Private = UTF8String("p.")
val PrivateStatic = UTF8String("ps.")
val Constructor = UTF8String("ct.")
val StaticConstructor = UTF8String("sct.")
// Shared with JS backend -- fieldName
val StaticField = UTF8String("t.")
val PrivateJSField = UTF8String("r.")
// Shared with JS backend -- className
val ModuleAccessor = UTF8String("m.")
val ModuleInstance = UTF8String("n.")
val ModuleInitFlag = UTF8String("ni.")
val JSClassAccessor = UTF8String("a.")
val JSClassValueCache = UTF8String("b.")
val TypeData = UTF8String("d.")
val IsInstance = UTF8String("is.")
val AsInstance = UTF8String("as.")
// Shared with JS backend -- string
val TopLevelExport = UTF8String("e.")
val TopLevelExportSetter = UTF8String("u.")
// Wasm only -- className + methodName
val TableEntry = UTF8String("m.")
// Wasm only -- fieldName
val InstanceField = UTF8String("f.")
// Wasm only -- className
val ClassInstance = UTF8String("c.")
val CreateJSClass = UTF8String("c.")
val VTable = UTF8String("v.")
val ITable = UTF8String("it.")
val Clone = UTF8String("clone.")
val NewDefault = UTF8String("new.")
}
private val thisOriginalName: OriginalName = OriginalName("this")
private val vtableOriginalName: OriginalName = OriginalName("vtable")
private val itablesOriginalName: OriginalName = OriginalName("itables")
}