diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala index 645974dfa0..b0f7f53ecd 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala @@ -839,24 +839,6 @@ class ClassEmitter(coreSpec: CoreSpec) { 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) @@ -1046,9 +1028,7 @@ class ClassEmitter(coreSpec: CoreSpec) { 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)) - } + js.VarRef(js.Ident(ctx.privateJSFields(name.name))) case JSFieldDef(_, nameTree, _) => helperBuilder.addInput(nameTree) } @@ -1466,7 +1446,6 @@ object ClassEmitter { // Shared with JS backend -- fieldName val StaticField = UTF8String("t.") - val PrivateJSField = UTF8String("r.") // Shared with JS backend -- className val ModuleAccessor = UTF8String("m.") diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala index 1797050cae..dccce2deb4 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala @@ -382,8 +382,6 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { addHelperImport(genFunctionID.jsNewArray, Nil, List(RefType.any)) addHelperImport(genFunctionID.jsNewObject, Nil, List(RefType.any)) - addHelperImport(genFunctionID.jsSelect, List(anyref, anyref), List(anyref)) - addHelperImport(genFunctionID.jsSelectSet, List(anyref, anyref, anyref), Nil) addHelperImport(genFunctionID.jsNewNoArg, List(anyref), List(anyref)) addHelperImport(genFunctionID.jsImportCall, List(anyref), List(anyref)) addHelperImport(genFunctionID.jsImportMeta, Nil, List(anyref)) @@ -393,7 +391,6 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { addHelperImport(genFunctionID.jsForInNext, List(anyref), List(anyref, Int32)) addHelperImport(genFunctionID.jsIsTruthy, List(anyref), List(Int32)) - addHelperImport(genFunctionID.newSymbol, Nil, List(anyref)) addHelperImport( genFunctionID.jsSuperSelect, List(anyref, anyref, anyref), diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/EmbeddedConstants.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/EmbeddedConstants.scala index 512765e60d..c5bac3b7a1 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/EmbeddedConstants.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/EmbeddedConstants.scala @@ -26,6 +26,12 @@ object EmbeddedConstants { /** Module containing the export setters, generated by the `Emitter`. */ final val ExportSettersModule = "__scalaJSExportSetters" + /** Module containing the getters for private JS fields. */ + final val PrivateJSFieldGetters = "privateJSFieldGetters" + + /** Module containing the setters for private JS fields. */ + final val PrivateJSFieldSetters = "privateJSFieldSetters" + /** Module containing the custom JS helpers (see CustomJSHelperBuilder). */ final val CustomHelpersModule = "__scalaJSCustomHelpers" diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala index bb1d6b0200..2d43da25df 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala @@ -20,6 +20,7 @@ import org.scalajs.ir.Types._ import org.scalajs.ir.OriginalName import org.scalajs.ir.Position import org.scalajs.ir.ScalaJSVersions +import org.scalajs.ir.UTF8String import org.scalajs.ir.WellKnownNames._ import org.scalajs.linker.interface._ @@ -32,6 +33,7 @@ import org.scalajs.linker.backend.emitter.{NameGen => JSNameGen, PrivateLibHolde import org.scalajs.linker.backend.javascript.Printers.JSTreePrinter import org.scalajs.linker.backend.javascript.{Trees => js} +import org.scalajs.linker.backend.wasmemitter.EmbeddedConstants._ import org.scalajs.linker.backend.webassembly.FunctionBuilder import org.scalajs.linker.backend.webassembly.{Instructions => wa} import org.scalajs.linker.backend.webassembly.{Modules => wamod} @@ -94,6 +96,8 @@ final class Emitter(config: Emitter.Config) { genStartFunction(sortedClasses, moduleInitializers, topLevelExports) + val privateJSFields = genPrivateJSFields(sortedClasses) + /* Gen the string pool and the declarative elements at the very end, since * they depend on what instructions where produced by all the preceding codegen. */ @@ -103,6 +107,7 @@ final class Emitter(config: Emitter.Config) { val wasmModule = ctx.moduleBuilder.build() val jsFileContentInfo = new JSFileContentInfo( + privateJSFields = privateJSFields, customJSHelpers = ctx.getAllCustomJSHelpers(), wtf16Strings = wtf16Strings ) @@ -122,20 +127,6 @@ final class Emitter(config: Emitter.Config) { val fb = new FunctionBuilder(ctx.moduleBuilder, genFunctionID.start, OriginalName("start"), pos) - // Initialize the JS private field symbols - - for (clazz <- sortedClasses if clazz.kind.isJSClass) { - for (fieldDef <- clazz.fields) { - fieldDef match { - case FieldDef(flags, name, _, _) if !flags.namespace.isStatic => - fb += wa.Call(genFunctionID.newSymbol) - fb += wa.GlobalSet(genGlobalID.forJSPrivateField(name.name)) - case _ => - () - } - } - } - // Emit the static initializers for (clazz <- sortedClasses if clazz.hasStaticInitializer) { @@ -233,6 +224,49 @@ final class Emitter(config: Emitter.Config) { fb += wa.Call(helperID) } + private def genPrivateJSFields(sortedClasses: List[LinkedClass])( + implicit ctx: WasmContext): List[(String, FieldName)] = { + import org.scalajs.ir.Trees._ + + val privateJSFieldGetterTypeID = ctx.moduleBuilder.functionTypeToTypeID( + watpe.FunctionType(List(watpe.RefType.anyref), List(watpe.RefType.anyref))) + val privateJSFieldSetterTypeID = ctx.moduleBuilder.functionTypeToTypeID( + watpe.FunctionType(List(watpe.RefType.anyref, watpe.RefType.anyref), Nil)) + + val setSuffix = UTF8String("_set") + + for { + clazz <- sortedClasses + if clazz.kind.isJSClass + FieldDef(flags, FieldIdent(fieldName), origName, _) <- clazz.fields + if !flags.namespace.isStatic + } yield { + val varName = ctx.privateJSFields(fieldName) + + val origName1 = origName.orElse(fieldName) + ctx.moduleBuilder.addImport(wamod.Import( + PrivateJSFieldGetters, + varName, + wamod.ImportDesc.Func( + genFunctionID.forPrivateJSFieldGetter(fieldName), + origName1, + privateJSFieldGetterTypeID + ) + )) + ctx.moduleBuilder.addImport(wamod.Import( + PrivateJSFieldSetters, + varName, + wamod.ImportDesc.Func( + genFunctionID.forPrivateJSFieldSetter(fieldName), + OriginalName(origName1.get ++ setSuffix), + privateJSFieldSetterTypeID + ) + )) + + varName -> fieldName + } + } + private def genDeclarativeElements()(implicit ctx: WasmContext): Unit = { // Aggregated Elements @@ -290,6 +324,36 @@ final class Emitter(config: Emitter.Config) { val exportSettersDict = js.ObjectConstr(exportSettersItems) + // JS private field symbols and the accompanying getters and setters + + val (privateJSFieldDecls, privateJSFieldGetterItems, privateJSFieldSetterItems) = { + (for ((varName, fieldName) <- info.privateJSFields) yield { + val symbolValue = { + val args = + if (coreSpec.semantics.productionMode) Nil + else js.StringLiteral(fieldName.nameString) :: Nil + js.Apply(js.VarRef(js.Ident("Symbol")), args) + } + + val varIdent = js.Ident(varName) + val importName = js.StringLiteral(varName) + val qualParamDef = js.ParamDef(js.Ident("qual")) + val valueParamDef = js.ParamDef(js.Ident("value")) + + val varDef = js.VarDef(varIdent, Some(symbolValue)) + val getterItem = importName -> js.Function(ClosureFlags.arrow, List(qualParamDef), None, { + js.Return(js.BracketSelect(qualParamDef.ref, js.VarRef(varIdent))) + }) + val setterItem = importName -> js.Function(ClosureFlags.arrow, List(qualParamDef, valueParamDef), None, { + js.Assign(js.BracketSelect(qualParamDef.ref, js.VarRef(varIdent)), valueParamDef.ref) + }) + + (varDef, getterItem, setterItem) + }).unzip3 + } + val privateJSFieldGettersDict = js.ObjectConstr(privateJSFieldGetterItems) + val privateJSFieldSettersDict = js.ObjectConstr(privateJSFieldSetterItems) + // Custom JS helpers val customJSHelpersItems = for ((importName, jsFunction) <- info.customJSHelpers) yield { @@ -317,6 +381,8 @@ final class Emitter(config: Emitter.Config) { List( js.StringLiteral(config.internalWasmFileURIPattern(module.id)), exportSettersDict, + privateJSFieldGettersDict, + privateJSFieldSettersDict, customJSHelpersDict, wtf16StringsDict ) @@ -325,6 +391,7 @@ final class Emitter(config: Emitter.Config) { val fullTree = ( moduleImports ::: loaderImport :: + privateJSFieldDecls ::: exportDecls.flatten ::: js.Await(loadCall) :: Nil @@ -377,6 +444,8 @@ object Emitter { } private final class JSFileContentInfo( + /** Private JS fields for which we need symbols: pairs of `(importName/identName, fieldName)`. */ + val privateJSFields: List[(String, FieldName)], /** Custom JS helpers to generate: pairs of `(importName, jsFunction)`. */ val customJSHelpers: List[(String, js.Function)], /** WTF-16 string constants: pairs of `(importName, stringValue)`. */ diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index 0bce083e98..a4d318e9f8 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -785,10 +785,9 @@ private class FunctionEmitter private ( case JSPrivateSelect(qualifier, field) => genTree(qualifier, AnyType) - fb += wa.GlobalGet(genGlobalID.forJSPrivateField(field.name)) genTree(rhs, AnyType) markPosition(tree) - fb += wa.Call(genFunctionID.jsSelectSet) + fb += wa.Call(genFunctionID.forPrivateJSFieldSetter(field.name)) case JSSelect(qualifier, item) => genThroughCustomJSHelper(List(qualifier, item, rhs), VoidType) { allJSArgs => @@ -3358,8 +3357,7 @@ private class FunctionEmitter private ( markPosition(tree) - fb += wa.GlobalGet(genGlobalID.forJSPrivateField(fieldName)) - fb += wa.Call(genFunctionID.jsSelect) + fb += wa.Call(genFunctionID.forPrivateJSFieldGetter(fieldName)) AnyType } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala index b1942af687..7af9d6d54e 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala @@ -148,8 +148,6 @@ const scalaJSHelpers = { // JS interop jsNewArray: () => [], jsNewObject: () => ({}), - jsSelect: (o, p) => o[p], - jsSelectSet: (o, p, v) => o[p] = v, jsNewNoArg: (constr) => new constr(), jsImportCall: (s) => import(s), jsImportMeta: () => import.meta, @@ -167,7 +165,6 @@ const scalaJSHelpers = { jsIsTruthy: (x) => !!x, // Non-native JS class support - newSymbol: Symbol, jsSuperSelect: superSelect, jsSuperSelectSet: superSelectSet, } @@ -190,7 +187,8 @@ const stringConstantsPolyfills = new Proxy({}, { }, }); -export async function load(wasmFileURL, exportSetters, customJSHelpers, wtf16Strings) { +export async function load(wasmFileURL, exportSetters, privateJSFieldGetters, + privateJSFieldSetters, customJSHelpers, wtf16Strings) { const myScalaJSHelpers = { ...scalaJSHelpers, idHashCodeMap: new WeakMap() @@ -198,6 +196,8 @@ export async function load(wasmFileURL, exportSetters, customJSHelpers, wtf16Str const importsObj = { "$CoreHelpersModule": myScalaJSHelpers, "$ExportSettersModule": exportSetters, + "$PrivateJSFieldGetters": privateJSFieldGetters, + "$PrivateJSFieldSetters": privateJSFieldSetters, "$CustomHelpersModule": customJSHelpers, "$WTF16StringConstantsModule": wtf16Strings, "$JSStringBuiltinsModule": stringBuiltinPolyfills, diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Preprocessor.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Preprocessor.scala index 34e5f44f9a..1cda689932 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Preprocessor.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Preprocessor.scala @@ -29,6 +29,7 @@ object Preprocessor { def preprocess(coreSpec: CoreSpec, coreLib: CoreWasmLib, classes: List[LinkedClass], tles: List[LinkedTopLevelExport]): WasmContext = { val staticFieldMirrors = computeStaticFieldMirrors(tles) + val privateJSFields = computePrivateJSFields(classes) val specialInstanceTypes = computeSpecialInstanceTypes(classes) @@ -65,7 +66,7 @@ object Preprocessor { val reflectiveProxyIDs = definedReflectiveProxyNames.toList.sorted.zipWithIndex.toMap new WasmContext(coreSpec, coreLib, classInfos, reflectiveProxyIDs, - itableBucketCount) + privateJSFields, itableBucketCount) } private def computeStaticFieldMirrors( @@ -87,6 +88,24 @@ object Preprocessor { result } + private def computePrivateJSFields( + classes: List[LinkedClass]): Map[FieldName, String] = { + + val result = mutable.AnyRefMap.empty[FieldName, String] + + for { + clazz <- classes + if clazz.kind.isJSClass + FieldDef(flags, FieldIdent(fieldName), _, _) <- clazz.fields + if !flags.namespace.isStatic + } { + val varName = s"privateJSField${result.size}" + result(fieldName) = varName + } + + result.toMap + } + private def computeSpecialInstanceTypes( classes: List[LinkedClass]): Map[ClassName, Int] = { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala index 2e3ec3f1cc..6df16d2aef 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala @@ -38,7 +38,6 @@ object VarGen { } final case class forStaticField(fieldName: FieldName) extends GlobalID - final case class forJSPrivateField(fieldName: FieldName) extends GlobalID final case class forStringLiteral(str: String) extends GlobalID @@ -68,6 +67,8 @@ object VarGen { final case class forExport(exportedName: String) extends FunctionID final case class forTopLevelExportSetter(exportedName: String) extends FunctionID + final case class forPrivateJSFieldGetter(fieldName: FieldName) extends FunctionID + final case class forPrivateJSFieldSetter(fieldName: FieldName) extends FunctionID final case class loadModule(className: ClassName) extends FunctionID final case class newDefault(className: ClassName) extends FunctionID @@ -145,8 +146,6 @@ object VarGen { case object jsNewArray extends JSHelperFunctionID case object jsNewObject extends JSHelperFunctionID - case object jsSelect extends JSHelperFunctionID - case object jsSelectSet extends JSHelperFunctionID case object jsNewNoArg extends JSHelperFunctionID case object jsImportCall extends JSHelperFunctionID case object jsImportMeta extends JSHelperFunctionID @@ -156,7 +155,6 @@ object VarGen { case object jsForInNext extends JSHelperFunctionID case object jsIsTruthy extends JSHelperFunctionID - case object newSymbol extends JSHelperFunctionID case object jsSuperSelect extends JSHelperFunctionID case object jsSuperSelectSet extends JSHelperFunctionID diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/WasmContext.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/WasmContext.scala index 6f0551921a..f5731082d0 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/WasmContext.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/WasmContext.scala @@ -47,6 +47,7 @@ final class WasmContext( val coreLib: CoreWasmLib, classInfo: Map[ClassName, WasmContext.ClassInfo], reflectiveProxies: Map[MethodName, Int], + val privateJSFields: Map[FieldName, String], val itablesLength: Int ) { import WasmContext._