Skip to content

Commit

Permalink
Module Splitting
Browse files Browse the repository at this point in the history
  • Loading branch information
gzm0 committed Jul 14, 2020
1 parent 88905ca commit 26170eb
Show file tree
Hide file tree
Showing 52 changed files with 2,134 additions and 593 deletions.
Expand Up @@ -23,8 +23,21 @@ import org.scalajs.logging.Logger
* .js file.
*/
abstract class Linker private[interface] () {
def link(irFiles: Seq[IRFile],
def link(irFiles: Seq[IRFile], moduleSpecs: List[ModuleSpec],
outputSpec: OutputSpec, logger: Logger)(
implicit ec: ExecutionContext): Future[Unit]

final def link(irFiles: Seq[IRFile],
moduleInitializers: Seq[ModuleInitializer],
output: LinkerOutput, logger: Logger)(
implicit ec: ExecutionContext): Future[Unit]
implicit ec: ExecutionContext): Future[Unit] = {
val moduleSpec = ModuleSpec("id")
.withSelector(ModuleSpec.Selector.default)
.withInitializers(moduleInitializers)

val outputSpec =
LinkerCompat.linkerOutputToOutputSpec(output, moduleSpec.id)

link(irFiles, List(moduleSpec), outputSpec, logger)
}
}
@@ -0,0 +1,68 @@
/*
* 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.interface

import scala.concurrent._

import java.nio.ByteBuffer

import org.scalajs.linker.interface.unstable.{OutputFileImpl, OutputDirectoryImpl}

private[linker] object LinkerCompat {
def linkerOutputToOutputSpec(
output: LinkerOutput, moduleID: ModuleSpec.ModuleID): OutputSpec = {

val jsFileImpl = OutputFileImpl.fromOutputFile(output.jsFile)

val jsFileName =
output.jsFileURI.fold(jsFileImpl.name)(_.toASCIIString)

val sourceMapFileImpl =
output.sourceMap.map(OutputFileImpl.fromOutputFile(_))

val sourceMapName = output.sourceMapURI
.map(_.toASCIIString)
.orElse(sourceMapFileImpl.map(_.name))
.getOrElse(jsFileName + ".map")

require(jsFileName != sourceMapName,
"Could not determine different names for the source map and the JS file.")

def selectFile(name: String): OutputFileImpl = name match {
case `jsFileName` => jsFileImpl

case `sourceMapName` =>
sourceMapFileImpl.getOrElse {
throw new IllegalArgumentException("The linker tried to write a " +
"source map, but none was provided in an adapted LinkerOutput. " +
"This is not supported.")
}

case _ =>
throw new IllegalArgumentException("The linker tried to write to a file " +
"name not provided by the adapter.")
}

val outputDirectory = new OutputDirectoryImpl {
def newChannel(name: String)(implicit ec: ExecutionContext): Future[OutputDirectoryImpl.Channel] =
selectFile(name).newChannel()

override def writeFull(name: String, buf: ByteBuffer)(implicit ec: ExecutionContext): Future[Unit] =
selectFile(name).writeFull(buf)
}

OutputSpec(outputDirectory)
.withJSName(_ => jsFileName)
.withSourceMapName(_ => sourceMapName)
}
}
@@ -0,0 +1,77 @@
/*
* 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.interface

import org.scalajs.linker.interface.unstable.ModuleSpecImpl.SelectorImpl

/** Module output specification. */
final class ModuleSpec (
val id: ModuleSpec.ModuleID,
val selector: ModuleSpec.Selector,
val initializers: Seq[ModuleInitializer],
) {
private def this(id: ModuleSpec.ModuleID) =
this(id, ModuleSpec.Selector.empty, Nil)

def withID(id: ModuleSpec.ModuleID): ModuleSpec =
copy(id = id)

def withSelector(selector: ModuleSpec.Selector): ModuleSpec =
copy(selector = selector)

def withInitializers(initializers: Seq[ModuleInitializer]): ModuleSpec =
copy(initializers = initializers)

private def copy(
id: ModuleSpec.ModuleID = id,
selector: ModuleSpec.Selector = selector,
initializers: Seq[ModuleInitializer] = initializers): ModuleSpec = {
new ModuleSpec(id, selector, initializers)
}
}

object ModuleSpec {
final class ModuleID(val id: String) extends AnyVal

def apply(id: String): ModuleSpec =
new ModuleSpec(new ModuleID(id))

def validate(moduleSpec: List[ModuleSpec]): Unit = {
???
}

final class Selector private (
private[interface] val impls: List[SelectorImpl]) {
def ++(that: Selector): Selector =
new Selector(this.impls ++ that.impls)
}

object Selector {
import SelectorImpl._

def empty: Selector =
new Selector(Nil)

def default: Selector =
new Selector(Default :: Nil)

def packageName(name: String): Selector =
new Selector(PackageName(name) :: Nil)

def className(name: String): Selector =
new Selector(ClassName(name) :: Nil)

def exportName(name: String): Selector =
new Selector(ExportName(name) :: Nil)
}
}
@@ -0,0 +1,40 @@
/*
* 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.interface

/** How to split the output into modules. */
abstract class ModuleSplitStyle private ()

object ModuleSplitStyle {

/** Make as few modules as possible (while not including unnecessary code).
*
* This is the default and the only style that works with
* [[ModuleKind.NoModule]].
*/
case object FewestModules extends ModuleSplitStyle

/** Make modules as small as possible. */
case object SmallestModules extends ModuleSplitStyle

private[interface] implicit object ModuleSplitStyleFingerprint
extends Fingerprint[ModuleSplitStyle] {

override def fingerprint(moduleSplitStyle: ModuleSplitStyle): String = {
moduleSplitStyle match {
case FewestModules => "FewestModules"
case SmallestModules => "SmallestModules"
}
}
}
}
@@ -0,0 +1,20 @@
/*
* 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.interface

import org.scalajs.linker.interface.unstable.OutputDirectoryImpl

/** Directory where the linker will write its output files. */
abstract class OutputDirectory private[interface] () {
private[interface] def impl: OutputDirectoryImpl
}
@@ -0,0 +1,43 @@
/*
* 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.interface

final class OutputSpec private (
val directory: OutputDirectory,
val jsName: ModuleSpec.ModuleID => String,
val sourceMapName: ModuleSpec.ModuleID => String,
) {
def this(directory: OutputDirectory) =
this(directory, _.id + ".js", _.id + ".js.map")

def withDirectory(directory: OutputDirectory): OutputSpec =
copy(directory = directory)

def withJSName(jsName: ModuleSpec.ModuleID => String): OutputSpec =
copy(jsName = jsName)

def withSourceMapName(sourceMapName: ModuleSpec.ModuleID => String): OutputSpec =
copy(sourceMapName = sourceMapName)

private def copy(
directory: OutputDirectory = directory,
jsName: ModuleSpec.ModuleID => String = jsName,
sourceMapName: ModuleSpec.ModuleID => String = sourceMapName): OutputSpec = {
new OutputSpec(directory, jsName, sourceMapName)
}
}

object OutputSpec {
def apply(directory: OutputDirectory): OutputSpec =
new OutputSpec(directory)
}
Expand Up @@ -24,6 +24,8 @@ final class StandardConfig private (
val semantics: Semantics,
/** Module kind. */
val moduleKind: ModuleKind,
/** How to split modules (if at all). */
val moduleSplitStyle: ModuleSplitStyle,
/** ECMAScript features to use. */
val esFeatures: ESFeatures,
/** If true, performs expensive checks of the IR for the used parts. */
Expand Down Expand Up @@ -61,6 +63,7 @@ final class StandardConfig private (
this(
semantics = Semantics.Defaults,
moduleKind = ModuleKind.NoModule,
moduleSplitStyle = ModuleSplitStyle.FewestModules,
esFeatures = ESFeatures.Defaults,
checkIR = false,
optimizer = true,
Expand All @@ -82,6 +85,9 @@ final class StandardConfig private (
def withModuleKind(moduleKind: ModuleKind): StandardConfig =
copy(moduleKind = moduleKind)

def withModuleSplitStyle(moduleSplitStyle: ModuleSplitStyle): StandardConfig =
copy(moduleSplitStyle = moduleSplitStyle)

def withESFeatures(esFeatures: ESFeatures): StandardConfig =
copy(esFeatures = esFeatures)

Expand Down Expand Up @@ -116,6 +122,7 @@ final class StandardConfig private (
s"""StandardConfig(
| semantics = $semantics,
| moduleKind = $moduleKind,
| moduleSplitStyle = $moduleSplitStyle,
| esFeatures = $esFeatures,
| checkIR = $checkIR,
| optimizer = $optimizer,
Expand All @@ -131,6 +138,7 @@ final class StandardConfig private (
private def copy(
semantics: Semantics = semantics,
moduleKind: ModuleKind = moduleKind,
moduleSplitStyle: ModuleSplitStyle = moduleSplitStyle,
esFeatures: ESFeatures = esFeatures,
checkIR: Boolean = checkIR,
optimizer: Boolean = optimizer,
Expand All @@ -144,6 +152,7 @@ final class StandardConfig private (
new StandardConfig(
semantics,
moduleKind,
moduleSplitStyle,
esFeatures,
checkIR,
optimizer,
Expand All @@ -167,6 +176,7 @@ object StandardConfig {
new FingerprintBuilder("StandardConfig")
.addField("semantics", config.semantics)
.addField("moduleKind", config.moduleKind)
.addField("moduleSplitStyle", config.moduleSplitStyle)
.addField("esFeatures", config.esFeatures)
.addField("checkIR", config.checkIR)
.addField("optimizer", config.optimizer)
Expand All @@ -191,6 +201,7 @@ object StandardConfig {
*
* - `semantics`: [[Semantics.Defaults]]
* - `moduleKind`: [[ModuleKind.NoModule]]
* - `moduleSplitStyle`: [[ModuleSplitStyle.FewestModules]]
* - `esFeatures`: [[ESFeatures.Defaults]]
* - `checkIR`: `true`
* - `optimizer`: `true`
Expand Down
@@ -0,0 +1,46 @@
/*
* 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.interface.unstable

import org.scalajs.linker.interface.ModuleSpec

object ModuleSpecImpl {
sealed abstract class SelectorImpl(private[ModuleSpecImpl] val precedence: Int)

object SelectorImpl {
// TODO: Validate that names are correct.
final case class ExportName(name: String) extends SelectorImpl(1)
final case class ClassName(name: String) extends SelectorImpl(2)
final case class PackageName(name: String) extends SelectorImpl(3)
final case object Default extends SelectorImpl(4)
}

def compileSelectors(moduleSpecs: List[ModuleSpec]): Seq[(SelectorImpl, ModuleSpec.ModuleID)] = {
val selectors = for {
moduleSpec <- moduleSpecs
selectorImpl <- moduleSpec.selector.impls
} yield (selectorImpl, moduleSpec.id)

// Check for duplicates.
for {
(selector, pairs) <- selectors.groupBy(_._1)
if pairs.size > 1
} {
val ids = pairs.map(_._2.id).mkString(", ")
throw new IllegalArgumentException(
s"Module selector $selector appears multiple times (in modules $ids)")
}

selectors.sortBy(_._1.precedence)
}
}

0 comments on commit 26170eb

Please sign in to comment.