Skip to content

Commit

Permalink
Module Splitting
Browse files Browse the repository at this point in the history
  • Loading branch information
gzm0 committed Jul 1, 2020
1 parent fe74ed5 commit c5ac3ee
Show file tree
Hide file tree
Showing 49 changed files with 1,942 additions and 631 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,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)
}
@@ -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,0 +1,59 @@
/*
* 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 scala.concurrent._

import java.nio.ByteBuffer

import org.scalajs.linker.interface.OutputDirectory

abstract class OutputDirectoryImpl extends OutputDirectory {
final private[interface] def impl: OutputDirectoryImpl = this

def newChannel(name: String)(
implicit ec: ExecutionContext): Future[OutputDirectoryImpl.Channel]

def writeFull(name: String, buf: ByteBuffer)(
implicit ec: ExecutionContext): Future[Unit] = {
newChannel(name).flatMap { chan =>
def writeLoop(): Future[Unit] = {
if (buf.hasRemaining()) chan.write(buf).flatMap(_ => writeLoop())
else Future.successful(())
}

finallyWith(writeLoop(), chan.close())
}
}

private def finallyWith(v: Future[Unit], f: => Future[Unit])(
implicit ec: ExecutionContext): Future[Unit] = {
v.map[Option[Throwable]](_ => None)
.recover { case t => Some(t) }
.flatMap {
case None => f

case Some(vt) =>
f.transform(_ => throw vt, ft => { ft.addSuppressed(vt); ft })
}
}
}

object OutputDirectoryImpl {
def fromOutputDirectory(f: OutputDirectory): OutputDirectoryImpl = f.impl

trait Channel {
def write(buf: ByteBuffer)(implicit ec: ExecutionContext): Future[Unit]
def close()(implicit ec: ExecutionContext): Future[Unit]
}
}
Expand Up @@ -16,42 +16,27 @@ import scala.concurrent._

import java.nio.ByteBuffer

import org.scalajs.linker.interface.LinkerOutput
import org.scalajs.linker.interface.{LinkerOutput, OutputDirectory}

abstract class OutputFileImpl extends LinkerOutput.File {
class OutputFileImpl(
val name: String,
val directory: OutputDirectory
) extends LinkerOutput.File {
final private[interface] def impl: OutputFileImpl = this

def newChannel()(implicit ec: ExecutionContext): Future[OutputFileImpl.Channel]

def writeFull(buf: ByteBuffer)(implicit ec: ExecutionContext): Future[Unit] = {
newChannel().flatMap { chan =>
def writeLoop(): Future[Unit] = {
if (buf.hasRemaining()) chan.write(buf).flatMap(_ => writeLoop())
else Future.successful(())
}

finallyWith(writeLoop(), chan.close())
}
/** Convencience method to open a new channel this file's output directory. */
final def newChannel()(
implicit ec: ExecutionContext): Future[OutputDirectoryImpl.Channel] = {
OutputDirectoryImpl.fromOutputDirectory(directory).newChannel(name)
}

private def finallyWith(v: Future[Unit], f: => Future[Unit])(
/** Convencience method to write this file in its output directory. */
final def writeFull(buf: ByteBuffer)(
implicit ec: ExecutionContext): Future[Unit] = {
v.map[Option[Throwable]](_ => None)
.recover { case t => Some(t) }
.flatMap {
case None => f

case Some(vt) =>
f.transform(_ => throw vt, ft => { ft.addSuppressed(vt); ft })
}
OutputDirectoryImpl.fromOutputDirectory(directory).writeFull(name, buf)
}
}

object OutputFileImpl {
def fromOutputFile(f: LinkerOutput.File): OutputFileImpl = f.impl

trait Channel {
def write(buf: ByteBuffer)(implicit ec: ExecutionContext): Future[Unit]
def close()(implicit ec: ExecutionContext): Future[Unit]
}
}

0 comments on commit c5ac3ee

Please sign in to comment.