All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalajs.linker.backend.wasmemitter.Emitter.scala Maven / Gradle / Ivy

The newest version!
/*
 * 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.concurrent.{ExecutionContext, Future}

import org.scalajs.ir.Names._
import org.scalajs.ir.Types._
import org.scalajs.ir.OriginalName
import org.scalajs.ir.Position
import org.scalajs.ir.ScalaJSVersions

import org.scalajs.linker.interface._
import org.scalajs.linker.interface.unstable._
import org.scalajs.linker.standard._
import org.scalajs.linker.standard.ModuleSet.ModuleID

import org.scalajs.linker.backend.emitter.{NameGen => JSNameGen, PrivateLibHolder}

import org.scalajs.linker.backend.javascript.Printers.JSTreePrinter
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 org.scalajs.logging.Logger

import SpecialNames._
import VarGen._
import org.scalajs.linker.backend.javascript.ByteArrayWriter

final class Emitter(config: Emitter.Config) {
  import Emitter._

  private val coreSpec = config.coreSpec

  private val classEmitter = new ClassEmitter(coreSpec)

  val symbolRequirements: SymbolRequirement =
    Emitter.symbolRequirements(coreSpec)

  val injectedIRFiles: Seq[IRFile] = PrivateLibHolder.files

  def emit(module: ModuleSet.Module, globalInfo: LinkedGlobalInfo, logger: Logger): Result = {
    val (wasmModule, jsFileContentInfo) = emitWasmModule(module, globalInfo)
    val loaderContent = LoaderContent.bytesContent
    val jsFileContent = buildJSFileContent(module, jsFileContentInfo)

    new Result(wasmModule, loaderContent, jsFileContent)
  }

  private def emitWasmModule(module: ModuleSet.Module,
      globalInfo: LinkedGlobalInfo): (wamod.Module, JSFileContentInfo) = {
    // Inject the derived linked classes
    val allClasses =
      DerivedClasses.deriveClasses(module.classDefs) ::: module.classDefs

    /* Sort by ancestor count so that superclasses always appear before
     * subclasses, then tie-break by name for stability.
     */
    val sortedClasses = allClasses.sortWith { (a, b) =>
      val cmp = Integer.compare(a.ancestors.size, b.ancestors.size)
      if (cmp != 0) cmp < 0
      else a.className.compareTo(b.className) < 0
    }

    val topLevelExports = module.topLevelExports
    val moduleInitializers = module.initializers.toList

    val coreLib = new CoreWasmLib(coreSpec, globalInfo)

    implicit val ctx: WasmContext =
      Preprocessor.preprocess(coreSpec, coreLib, sortedClasses, topLevelExports)

    coreLib.genPreClasses()
    sortedClasses.foreach(classEmitter.genClassDef(_))
    topLevelExports.foreach(classEmitter.genTopLevelExport(_))
    classEmitter.genGlobalArrayClassItable()
    coreLib.genPostClasses()

    genStartFunction(sortedClasses, moduleInitializers, topLevelExports)

    /* 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.
     */
    ctx.stringPool.genPool()
    genDeclarativeElements()

    val wasmModule = ctx.moduleBuilder.build()

    val jsFileContentInfo = new JSFileContentInfo(
      customJSHelpers = ctx.getAllCustomJSHelpers()
    )

    (wasmModule, jsFileContentInfo)
  }

  private def genStartFunction(
      sortedClasses: List[LinkedClass],
      moduleInitializers: List[ModuleInitializer.Initializer],
      topLevelExportDefs: List[LinkedTopLevelExport]
  )(implicit ctx: WasmContext): Unit = {
    import org.scalajs.ir.Trees._

    implicit val pos = Position.NoPosition

    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) {
      val funcID = genFunctionID.forMethod(
        MemberNamespace.StaticConstructor,
        clazz.className,
        StaticInitializerName
      )
      fb += wa.Call(funcID)
    }

    // Initialize the top-level exports that require it

    for (tle <- topLevelExportDefs) {
      // Load the (initial) exported value on the stack
      tle.tree match {
        case TopLevelJSClassExportDef(_, exportName) =>
          fb += wa.Call(genFunctionID.loadJSClass(tle.owningClass))
        case TopLevelModuleExportDef(_, exportName) =>
          fb += wa.Call(genFunctionID.loadModule(tle.owningClass))
        case TopLevelMethodExportDef(_, methodDef) =>
          genTopLevelExportedFun(fb, tle.exportName, methodDef)
        case TopLevelFieldExportDef(_, _, fieldIdent) =>
          /* Usually redundant, but necessary if the static field is never
           * explicitly set and keeps its default (zero) value instead. In that
           * case this initial call is required to publish that zero value (as
           * opposed to the default `undefined` value of the JS `let`).
           */
          fb += wa.GlobalGet(genGlobalID.forStaticField(fieldIdent.name))
      }

      // Call the export setter
      fb += wa.Call(genFunctionID.forTopLevelExportSetter(tle.exportName))
    }

    // Emit the module initializers

    moduleInitializers.foreach { init =>
      def genCallStatic(className: ClassName, methodName: MethodName): Unit = {
        val funcID = genFunctionID.forMethod(MemberNamespace.PublicStatic, className, methodName)
        fb += wa.Call(funcID)
      }

      ModuleInitializerImpl.fromInitializer(init) match {
        case ModuleInitializerImpl.MainMethodWithArgs(className, encodedMainMethodName, args) =>
          val stringArrayTypeRef = ArrayTypeRef(ClassRef(BoxedStringClass), 1)
          SWasmGen.genArrayValue(fb, stringArrayTypeRef, args.size) {
            for (arg <- args) {
              fb ++= ctx.stringPool.getConstantStringInstr(arg)
              fb += wa.AnyConvertExtern
            }
          }
          genCallStatic(className, encodedMainMethodName)

        case ModuleInitializerImpl.VoidMainMethod(className, encodedMainMethodName) =>
          genCallStatic(className, encodedMainMethodName)
      }
    }

    // Finish the start function

    fb.buildAndAddToModule()
    ctx.moduleBuilder.setStart(genFunctionID.start)
  }

  private def genTopLevelExportedFun(fb: FunctionBuilder, exportName: String,
      methodDef: org.scalajs.ir.Trees.JSMethodDef)(
      implicit ctx: WasmContext): Unit = {

    import org.scalajs.ir.Trees._

    val JSMethodDef(_, _, params, restParam, _) = methodDef

    implicit val pos = methodDef.pos

    val builder = new CustomJSHelperBuilder()

    val fRef = builder.addWasmInput("f", watpe.RefType.func) {
      fb += ctx.refFuncWithDeclaration(genFunctionID.forExport(exportName))
    }

    val helperID = builder.build(AnyNotNullType) {
      js.Return {
        val (argsParamDefs, restParamDef) = builder.genJSParamDefs(params, restParam)
        // Exported defs must be `function`s although they do not use their `this`
        js.Function(arrow = false, argsParamDefs, restParamDef, {
          js.Return(js.Apply(
              fRef,
              argsParamDefs.map(_.ref) ::: restParamDef.map(_.ref).toList
          ))
        })
      }
    }

    fb += wa.Call(helperID)
  }

  private def genDeclarativeElements()(implicit ctx: WasmContext): Unit = {
    // Aggregated Elements

    val funcDeclarations = ctx.getAllFuncDeclarations()

    if (funcDeclarations.nonEmpty) {
      /* Functions that are referred to with `ref.func` in the Code section
       * must be declared ahead of time in one of the earlier sections
       * (otherwise the module does not validate). It can be the Global section
       * if they are meaningful there (which is why `ref.func` in the vtables
       * work out of the box). In the absence of any other specific place, an
       * Element section with the declarative mode is the recommended way to
       * introduce these declarations.
       */
      val exprs = funcDeclarations.map { funcID =>
        wa.Expr(List(wa.RefFunc(funcID)))
      }
      ctx.moduleBuilder.addElement(
        wamod.Element(watpe.RefType.funcref, exprs, wamod.Element.Mode.Declarative)
      )
    }
  }

  private def buildJSFileContent(module: ModuleSet.Module,
      info: JSFileContentInfo): Array[Byte] = {

    implicit val noPos = Position.NoPosition

    // Linking info
    val linkingInfo = {
      // must be in sync with scala.scalajs.runtime.LinkingInfo
      import config.coreSpec._
      import js.{BooleanLiteral => bool, IntLiteral => int, StringLiteral => str}

      def objectFreeze(tree: js.Tree): js.Tree =
        js.Apply(js.DotSelect(js.VarRef(js.Ident("Object")), js.Ident("freeze")), tree :: Nil)

      objectFreeze(js.ObjectConstr(List(
        str("esVersion") -> int(esFeatures.esVersion.edition),
        str("assumingES6") -> bool(esFeatures.useECMAScript2015Semantics), // different name for historical reasons
        str("isWebAssembly") -> bool(true),
        str("productionMode") -> bool(semantics.productionMode),
        str("linkerVersion") -> str(ScalaJSVersions.current),
        str("fileLevelThis") -> js.This()
      )))
    }

    // Sort for stability
    val importedModules = module.externalDependencies.toList.sorted

    // External imports

    val moduleImports = for (moduleName <- importedModules) yield {
      val importIdent = js.Ident("imported" + JSNameGen.genModuleName(moduleName))
      val moduleNameStr = js.StringLiteral(moduleName)
      js.ImportNamespace(importIdent, moduleNameStr)
    }

    // Exports

    val (exportDecls, exportSettersItems) = (for {
      exportName <- module.topLevelExports.map(_.exportName)
    } yield {
      val ident = js.Ident(s"exported$exportName")
      val decl = js.Let(ident, mutable = true, None)
      val exportStat = js.Export(List(ident -> js.ExportName(exportName)))
      val xParam = js.ParamDef(js.Ident("x"))
      val setterFun = js.Function(arrow = true, List(xParam), None, {
        js.Assign(js.VarRef(ident), xParam.ref)
      })
      val setterItem = js.StringLiteral(exportName) -> setterFun
      (List(decl, exportStat), setterItem)
    }).unzip

    val exportSettersDict = js.ObjectConstr(exportSettersItems)

    // Custom JS helpers

    val customJSHelpersItems = for ((importName, jsFunction) <- info.customJSHelpers) yield {
      js.StringLiteral(importName) -> jsFunction
    }
    val customJSHelpersDict = js.ObjectConstr(customJSHelpersItems)

    // Overall structure of the result

    val loadFunIdent = js.Ident("__load")
    val loaderImport = js.Import(
      List(js.ExportName("load") -> loadFunIdent),
      js.StringLiteral(config.loaderModuleName)
    )

    val loadCall = js.Apply(
      js.VarRef(loadFunIdent),
      List(
        js.StringLiteral(config.internalWasmFileURIPattern(module.id)),
        linkingInfo,
        exportSettersDict,
        customJSHelpersDict
      )
    )

    val fullTree = (
      moduleImports :::
      loaderImport ::
      exportDecls.flatten :::
      js.Await(loadCall) ::
      Nil
    )

    val writer = new ByteArrayWriter
    val printer = new JSTreePrinter(writer)
    fullTree.foreach(printer.printStat(_))
    writer.toByteArray()
  }
}

object Emitter {

  /** Configuration for the Emitter. */
  final class Config private (
      val coreSpec: CoreSpec,
      val loaderModuleName: String,
      val internalWasmFileURIPattern: ModuleID => String
  ) {
    private def this(coreSpec: CoreSpec, loaderModuleName: String) = {
      this(
        coreSpec,
        loaderModuleName,
        internalWasmFileURIPattern = { moduleID => s"./${moduleID.id}.wasm" }
      )
    }

    def withInternalWasmFileURIPattern(
        internalWasmFileURIPattern: ModuleID => String): Config = {
      copy(internalWasmFileURIPattern = internalWasmFileURIPattern)
    }

    private def copy(
      coreSpec: CoreSpec = coreSpec,
      loaderModuleName: String = loaderModuleName,
      internalWasmFileURIPattern: ModuleID => String = internalWasmFileURIPattern
    ): Config = {
      new Config(
        coreSpec,
        loaderModuleName,
        internalWasmFileURIPattern
      )
    }
  }

  object Config {
    def apply(coreSpec: CoreSpec, loaderModuleName: String): Config =
      new Config(coreSpec, loaderModuleName)
  }

  private final class JSFileContentInfo(
      /** Custom JS helpers to generate: pairs of `(importName, jsFunction)`. */
      val customJSHelpers: List[(String, js.Function)]
  )

  final class Result(
      val wasmModule: wamod.Module,
      val loaderContent: Array[Byte],
      val jsFileContent: Array[Byte]
  )

  /** Builds the symbol requirements of our back-end.
   *
   *  The symbol requirements tell the LinkerFrontend that we need these symbols to always be
   *  reachable, even if no "user-land" IR requires them. They are roots for the reachability
   *  analysis, together with module initializers and top-level exports. If we don't do this, the
   *  linker frontend will dead-code eliminate our box classes.
   */
  private def symbolRequirements(coreSpec: CoreSpec): SymbolRequirement = {
    import coreSpec.semantics._
    import CheckedBehavior._
    import SpecialNames._

    val factory = SymbolRequirement.factory("emitter")
    import factory._

    def cond(p: Boolean)(v: => SymbolRequirement): SymbolRequirement =
      if (p) v else none()

    def isAnyFatal(behaviors: CheckedBehavior*): Boolean =
      behaviors.contains(Fatal)

    multiple(
      cond(asInstanceOfs != Unchecked) {
        instantiateClass(ClassCastExceptionClass, StringArgConstructorName)
      },

      cond(arrayIndexOutOfBounds != Unchecked) {
        instantiateClass(ArrayIndexOutOfBoundsExceptionClass,
            StringArgConstructorName)
      },

      cond(arrayStores != Unchecked) {
        instantiateClass(ArrayStoreExceptionClass,
            StringArgConstructorName)
      },

      cond(negativeArraySizes != Unchecked) {
        instantiateClass(NegativeArraySizeExceptionClass,
            StringArgConstructorName)
      },

      cond(nullPointers != Unchecked) {
        instantiateClass(NullPointerExceptionClass, NoArgConstructorName)
      },

      cond(stringIndexOutOfBounds != Unchecked) {
        instantiateClass(StringIndexOutOfBoundsExceptionClass,
            IntArgConstructorName)
      },

      cond(isAnyFatal(asInstanceOfs, arrayIndexOutOfBounds, arrayStores,
          negativeArraySizes, nullPointers, stringIndexOutOfBounds)) {
        instantiateClass(UndefinedBehaviorErrorClass,
            ThrowableArgConsructorName)
      },

      cond(moduleInit == Fatal) {
        instantiateClass(UndefinedBehaviorErrorClass,
            StringArgConstructorName)
      },

      // TODO Ideally we should not require these, but rather adapt to their absence
      instantiateClass(ClassClass, NoArgConstructorName),
      instantiateClass(JSExceptionClass, AnyArgConstructorName),
      instantiateClass(IllegalArgumentExceptionClass, NoArgConstructorName),

      // See genIdentityHashCode in HelperFunctions
      callMethodStatically(BoxedDoubleClass, hashCodeMethodName),
      callMethodStatically(BoxedStringClass, hashCodeMethodName)
    )
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy