Skip to content

Wasm: Store the symbols of private JS fields on the JS side. #5220

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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._
Expand All @@ -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}
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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
)
Expand All @@ -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) {
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -317,6 +381,8 @@ final class Emitter(config: Emitter.Config) {
List(
js.StringLiteral(config.internalWasmFileURIPattern(module.id)),
exportSettersDict,
privateJSFieldGettersDict,
privateJSFieldSettersDict,
customJSHelpersDict,
wtf16StringsDict
)
Expand All @@ -325,6 +391,7 @@ final class Emitter(config: Emitter.Config) {
val fullTree = (
moduleImports :::
loaderImport ::
privateJSFieldDecls :::
exportDecls.flatten :::
js.Await(loadCall) ::
Nil
Expand Down Expand Up @@ -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)`. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -167,7 +165,6 @@ const scalaJSHelpers = {
jsIsTruthy: (x) => !!x,

// Non-native JS class support
newSymbol: Symbol,
jsSuperSelect: superSelect,
jsSuperSelectSet: superSelectSet,
}
Expand All @@ -190,14 +187,17 @@ 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()
};
const importsObj = {
"$CoreHelpersModule": myScalaJSHelpers,
"$ExportSettersModule": exportSetters,
"$PrivateJSFieldGetters": privateJSFieldGetters,
"$PrivateJSFieldSetters": privateJSFieldSetters,
"$CustomHelpersModule": customJSHelpers,
"$WTF16StringConstantsModule": wtf16Strings,
"$JSStringBuiltinsModule": stringBuiltinPolyfills,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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(
Expand All @@ -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] = {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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._
Expand Down
Loading