Skip to content

Implement most of the Scala.js IR code generator. #1148

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

Merged
merged 5 commits into from
Mar 18, 2016
Merged
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
1,359 changes: 1,293 additions & 66 deletions src/dotty/tools/backend/sjs/JSCodeGen.scala

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions src/dotty/tools/backend/sjs/JSDefinitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import dotty.tools.dotc.core._
import Types._
import Contexts._
import Symbols._
import Names._
import StdNames._
import Decorators._

Expand Down Expand Up @@ -172,4 +173,27 @@ final class JSDefinitions()(implicit ctx: Context) {
lazy val BoxesRunTime_unboxToCharR = defn.BoxesRunTimeModule.requiredMethodRef("unboxToChar")
def BoxesRunTime_unboxToChar(implicit ctx: Context): Symbol = BoxesRunTime_unboxToCharR.symbol

/** If `cls` is a class in the scala package, its name, otherwise EmptyTypeName */
private def scalajsClassName(cls: Symbol)(implicit ctx: Context): TypeName =
if (cls.isClass && cls.owner == ScalaJSJSPackageClass) cls.asClass.name
else EmptyTypeName

/** Is the given `cls` a class of the form `scala.scalajs.js.prefixN` where
* `N` is a number.
*
* This is similar to `isVarArityClass` in `Definitions.scala`.
*/
private def isScalaJSVarArityClass(cls: Symbol, prefix: Name): Boolean = {
val name = scalajsClassName(cls)
name.startsWith(prefix) && name.drop(prefix.length).forall(_.isDigit)
}

def isJSFunctionClass(cls: Symbol): Boolean =
isScalaJSVarArityClass(cls, nme.Function)

private val ThisFunctionName = termName("ThisFunction")

def isJSThisFunctionClass(cls: Symbol): Boolean =
isScalaJSVarArityClass(cls, ThisFunctionName)

}
43 changes: 28 additions & 15 deletions src/dotty/tools/backend/sjs/JSEncoding.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import ir.{Trees => js, Types => jstpe}

import ScopedVar.withScopedVars
import JSDefinitions._
import JSInterop._

/** Encoding of symbol names for JavaScript
*
Expand Down Expand Up @@ -139,17 +140,32 @@ object JSEncoding {
* java.lang.String, which is the `this` parameter.
*/
def encodeRTStringMethodSym(sym: Symbol)(
implicit ctx: Context, pos: ir.Position): (Symbol, js.Ident) = {
require(sym.is(Flags.Method), "encodeMethodSym called with non-method symbol: " + sym)
implicit ctx: Context, pos: ir.Position): js.Ident = {
require(sym.owner == defn.StringClass)
require(!sym.isClassConstructor && !sym.is(Flags.Private))

val (encodedName, paramsString) =
encodeMethodNameInternal(sym, inRTClass = true)
val methodIdent = js.Ident(encodedName + paramsString,
js.Ident(encodedName + paramsString,
Some(sym.unexpandedName.decoded + paramsString))
}

/** Encodes a constructor symbol of java.lang.String for use in RuntimeString.
*
* - The name is rerouted to `newString`
* - The result type is set to `java.lang.String`
*/
def encodeRTStringCtorSym(sym: Symbol)(
implicit ctx: Context, pos: ir.Position): js.Ident = {
require(sym.owner == defn.StringClass)
require(sym.isClassConstructor && !sym.is(Flags.Private))

(jsdefn.RuntimeStringModuleClass, methodIdent)
val paramTypeNames = sym.info.firstParamTypes.map(internalName(_))
val paramAndResultTypeNames = paramTypeNames :+ ir.Definitions.StringClass
val paramsString = makeParamsString(paramAndResultTypeNames)

js.Ident("newString" + paramsString,
Some(sym.unexpandedName.decoded + paramsString))
}

private def encodeMethodNameInternal(sym: Symbol,
Expand Down Expand Up @@ -197,7 +213,7 @@ object JSEncoding {

def encodeClassType(sym: Symbol)(implicit ctx: Context): jstpe.Type = {
if (sym == defn.ObjectClass) jstpe.AnyType
else if (isRawJSType(sym)) jstpe.AnyType
else if (isJSType(sym)) jstpe.AnyType
else {
assert(sym != defn.ArrayClass,
"encodeClassType() cannot be called with ArrayClass")
Expand All @@ -210,20 +226,17 @@ object JSEncoding {
js.Ident(encodeClassFullName(sym), Some(sym.fullName.toString))
}

def encodeClassFullName(sym: Symbol)(implicit ctx: Context): String =
ir.Definitions.encodeClassName(sym.fullName.toString)
def encodeClassFullName(sym: Symbol)(implicit ctx: Context): String = {
if (sym == defn.NothingClass) ir.Definitions.RuntimeNothingClass
else if (sym == defn.NullClass) ir.Definitions.RuntimeNullClass
else ir.Definitions.encodeClassName(sym.fullName.toString)
}

private def encodeMemberNameInternal(sym: Symbol)(
implicit ctx: Context): String = {
sym.name.toString.replace("_", "$und")
sym.name.toString.replace("_", "$und").replace("~", "$tilde")
}

def isRawJSType(sym: Symbol)(implicit ctx: Context): Boolean =
sym.hasAnnotation(jsdefn.RawJSTypeAnnot)

def isScalaJSDefinedJSClass(sym: Symbol)(implicit ctx: Context): Boolean =
isRawJSType(sym) && !sym.hasAnnotation(jsdefn.JSNativeAnnot)

def toIRType(tp: Type)(implicit ctx: Context): jstpe.Type = {
val refType = toReferenceTypeInternal(tp)
refType._1 match {
Expand All @@ -243,7 +256,7 @@ object JSEncoding {
else
jstpe.IntType
} else {
if (sym == defn.ObjectClass || isRawJSType(sym))
if (sym == defn.ObjectClass || isJSType(sym))
jstpe.AnyType
else if (sym == defn.NothingClass)
jstpe.NothingType
Expand Down
110 changes: 110 additions & 0 deletions src/dotty/tools/backend/sjs/JSInterop.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package dotty.tools.backend.sjs

import dotty.tools.dotc.core._
import Contexts._
import Flags._
import Symbols._
import NameOps._
import StdNames._

import JSDefinitions._

/** Management of the interoperability with JavaScript. */
object JSInterop {

/** Is this symbol a JavaScript type? */
def isJSType(sym: Symbol)(implicit ctx: Context): Boolean = {
//sym.hasAnnotation(jsdefn.RawJSTypeAnnot)
ctx.atPhase(ctx.erasurePhase) { implicit ctx =>
sym.derivesFrom(jsdefn.JSAnyClass)
}
}

/** Is this symbol a Scala.js-defined JS class, i.e., a non-native JS class? */
def isScalaJSDefinedJSClass(sym: Symbol)(implicit ctx: Context): Boolean =
isJSType(sym) && !sym.hasAnnotation(jsdefn.JSNativeAnnot)

/** Should this symbol be translated into a JS getter?
*
* This is true for any parameterless method, i.e., defined without `()`.
* Unlike `SymDenotations.isGetter`, it applies to user-defined methods as
* much as *accessor* methods created for `val`s and `var`s.
*/
def isJSGetter(sym: Symbol)(implicit ctx: Context): Boolean = {
sym.info.firstParamTypes.isEmpty && ctx.atPhase(ctx.erasurePhase) { implicit ctx =>
sym.info.isParameterless
}
}

/** Should this symbol be translated into a JS setter?
*
* This is true for any method whose name ends in `_=`.
* Unlike `SymDenotations.isGetter`, it applies to user-defined methods as
* much as *accessor* methods created for `var`s.
*/
def isJSSetter(sym: Symbol)(implicit ctx: Context): Boolean =
sym.name.isSetterName && sym.is(Method)

/** Should this symbol be translated into a JS bracket access?
*
* This is true for methods annotated with `@JSBracketAccess`.
*/
def isJSBracketAccess(sym: Symbol)(implicit ctx: Context): Boolean =
sym.hasAnnotation(jsdefn.JSBracketAccessAnnot)

/** Should this symbol be translated into a JS bracket call?
*
* This is true for methods annotated with `@JSBracketCall`.
*/
def isJSBracketCall(sym: Symbol)(implicit ctx: Context): Boolean =
sym.hasAnnotation(jsdefn.JSBracketCallAnnot)

/** Is this symbol a default param accessor for a JS method?
*
* For default param accessors of *constructors*, we need to test whether
* the companion *class* of the owner is a JS type; not whether the owner
* is a JS type.
*/
def isJSDefaultParam(sym: Symbol)(implicit ctx: Context): Boolean = {
sym.name.isDefaultGetterName && {
val owner = sym.owner
if (owner.is(ModuleClass) &&
sym.name.asTermName.defaultGetterToMethod == nme.CONSTRUCTOR) {
isJSType(owner.linkedClass)
} else {
isJSType(owner)
}
}
}

/** Gets the unqualified JS name of a symbol.
*
* If it is not explicitly specified with an `@JSName` annotation, the
* JS name is inferred from the Scala name.
*/
def jsNameOf(sym: Symbol)(implicit ctx: Context): String = {
sym.getAnnotation(jsdefn.JSNameAnnot).flatMap(_.argumentConstant(0)).fold {
val base = sym.name.unexpandedName.decode.toString.stripSuffix("_=")
if (sym.is(ModuleClass)) base.stripSuffix("$")
else if (!sym.is(Method)) base.stripSuffix(" ")
else base
} { constant =>
constant.stringValue
}
}

/** Gets the fully qualified JS name of a static class of module Symbol.
*
* This is the JS name of the symbol qualified by the fully qualified JS
* name of its original owner if the latter is a native JS object.
*/
def fullJSNameOf(sym: Symbol)(implicit ctx: Context): String = {
assert(sym.isClass, s"fullJSNameOf called for non-class symbol $sym")
sym.getAnnotation(jsdefn.JSFullNameAnnot).flatMap(_.argumentConstant(0)).fold {
jsNameOf(sym)
} { constant =>
constant.stringValue
}
}

}
17 changes: 17 additions & 0 deletions src/dotty/tools/backend/sjs/JSPrimitives.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,13 @@ object JSPrimitives {
final val ENV_INFO = 316 // runtime.environmentInfo
final val LINKING_INFO = 317 // runtime.linkingInfo

final val THROW = 318 // <special-ops>.throw

}

class JSPrimitives(ctx: Context) extends DottyPrimitives(ctx) {
import JSPrimitives._
import scala.tools.nsc.backend.ScalaPrimitives._

private lazy val jsPrimitives: Map[Symbol, Int] = initJSPrimitives(ctx)

Expand Down Expand Up @@ -77,6 +80,18 @@ class JSPrimitives(ctx: Context) extends DottyPrimitives(ctx) {

val jsdefn = JSDefinitions.jsdefn

// For some reason, the JVM primitive set does not register those
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newBooleanArray")), NEW_ZARRAY)
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newByteArray")), NEW_BARRAY)
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newShortArray")), NEW_SARRAY)
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newCharArray")), NEW_CARRAY)
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newIntArray")), NEW_IARRAY)
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newLongArray")), NEW_LARRAY)
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newFloatArray")), NEW_FARRAY)
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newDoubleArray")), NEW_DARRAY)
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newRefArray")), NEW_OARRAY)
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newUnitArray")), NEW_OARRAY)

addPrimitive(defn.Any_getClass, GETCLASS)

for (i <- 0 to 22)
Expand Down Expand Up @@ -107,6 +122,8 @@ class JSPrimitives(ctx: Context) extends DottyPrimitives(ctx) {
//addPrimitive(jsdefn.Runtime_environmentInfo, ENV_INFO)
//addPrimitive(jsdefn.Runtime_linkingInfo, LINKING_INFO)

addPrimitive(defn.throwMethod, THROW)

primitives.toMap
}

Expand Down
6 changes: 5 additions & 1 deletion src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,11 @@ class Compiler {
def rootContext(implicit ctx: Context): Context = {
ctx.initialize()(ctx)
val actualPhases = if (ctx.settings.scalajs.value) {
phases
// Remove phases that Scala.js does not want
phases.mapConserve(_.filter {
case _: FunctionalInterfaces => false
case _ => true
}).filter(_.nonEmpty)
} else {
// Remove Scala.js-related phases
phases.mapConserve(_.filter {
Expand Down
9 changes: 9 additions & 0 deletions src/dotty/tools/dotc/config/JavaPlatform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ClassPath.{ JavaContext, DefaultJavaContext }
import core._
import Symbols._, Types._, Contexts._, Denotations._, SymDenotations._, StdNames._, Names._
import Flags._, Scopes._, Decorators._, NameOps._, util.Positions._
import transform.ExplicitOuter, transform.SymUtils._

class JavaPlatform extends Platform {

Expand Down Expand Up @@ -38,6 +39,14 @@ class JavaPlatform extends Platform {

def rootLoader(root: TermSymbol)(implicit ctx: Context): SymbolLoader = new ctx.base.loaders.PackageLoader(root, classPath)

/** Is the SAMType `cls` also a SAM under the rules of the JVM? */
def isSam(cls: ClassSymbol)(implicit ctx: Context): Boolean =
cls.is(NoInitsTrait) &&
cls.superClass == defn.ObjectClass &&
cls.directlyInheritedTraits.forall(_.is(NoInits)) &&
!ExplicitOuter.needsOuterIfReferenced(cls) &&
cls.typeRef.fields.isEmpty // Superaccessors already show up as abstract methods here, so no test necessary

/** We could get away with excluding BoxedBooleanClass for the
* purpose of equality testing since it need not compare equal
* to anything but other booleans, but it should be present in
Expand Down
3 changes: 3 additions & 0 deletions src/dotty/tools/dotc/config/Platform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ abstract class Platform {
/** Any platform-specific phases. */
//def platformPhases: List[SubComponent]

/** Is the SAMType `cls` also a SAM under the rules of the platform? */
def isSam(cls: ClassSymbol)(implicit ctx: Context): Boolean

/** The various ways a boxed primitive might materialize at runtime. */
def isMaybeBoxed(sym: ClassSymbol)(implicit ctx: Context): Boolean

Expand Down
5 changes: 5 additions & 0 deletions src/dotty/tools/dotc/config/SJSPlatform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dotty.tools.dotc.config

import dotty.tools.dotc.core._
import Contexts._
import Symbols._

import dotty.tools.backend.sjs.JSDefinitions

Expand All @@ -10,4 +11,8 @@ class SJSPlatform()(implicit ctx: Context) extends JavaPlatform {
/** Scala.js-specific definitions. */
val jsDefinitions: JSDefinitions = new JSDefinitions()

/** Is the SAMType `cls` also a SAM under the rules of the Scala.js back-end? */
override def isSam(cls: ClassSymbol)(implicit ctx: Context): Boolean =
defn.isFunctionClass(cls) || jsDefinitions.isJSFunctionClass(cls)

}
4 changes: 4 additions & 0 deletions src/dotty/tools/dotc/core/Phases.scala
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,10 @@ object Phases {
private val picklerCache = new PhaseCache(classOf[Pickler])

private val refChecksCache = new PhaseCache(classOf[RefChecks])
private val elimRepeatedCache = new PhaseCache(classOf[ElimRepeated])
private val extensionMethodsCache = new PhaseCache(classOf[ExtensionMethods])
private val erasureCache = new PhaseCache(classOf[Erasure])
private val elimErasedValueTypeCache = new PhaseCache(classOf[ElimErasedValueType])
private val patmatCache = new PhaseCache(classOf[PatternMatcher])
private val lambdaLiftCache = new PhaseCache(classOf[LambdaLift])
private val flattenCache = new PhaseCache(classOf[Flatten])
Expand All @@ -245,8 +247,10 @@ object Phases {
def typerPhase = typerCache.phase
def picklerPhase = picklerCache.phase
def refchecksPhase = refChecksCache.phase
def elimRepeatedPhase = elimRepeatedCache.phase
def extensionMethodsPhase = extensionMethodsCache.phase
def erasurePhase = erasureCache.phase
def elimErasedValueTypePhase = elimErasedValueTypeCache.phase
def patmatPhase = patmatCache.phase
def lambdaLiftPhase = lambdaLiftCache.phase
def flattenPhase = flattenCache.phase
Expand Down
12 changes: 4 additions & 8 deletions src/dotty/tools/dotc/transform/ExpandSAMs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,17 @@ class ExpandSAMs extends MiniPhaseTransform { thisTransformer =>

import ast.tpd._

/** Is SAMType `cls` also a SAM under the rules of the JVM? */
def isJvmSam(cls: ClassSymbol)(implicit ctx: Context): Boolean =
cls.is(NoInitsTrait) &&
cls.superClass == defn.ObjectClass &&
cls.directlyInheritedTraits.forall(_.is(NoInits)) &&
!ExplicitOuter.needsOuterIfReferenced(cls) &&
cls.typeRef.fields.isEmpty // Superaccessors already show up as abstract methods here, so no test necessary
/** Is the SAMType `cls` also a SAM under the rules of the platform? */
def isPlatformSam(cls: ClassSymbol)(implicit ctx: Context): Boolean =
ctx.platform.isSam(cls)

override def transformBlock(tree: Block)(implicit ctx: Context, info: TransformerInfo): Tree = tree match {
case Block(stats @ (fn: DefDef) :: Nil, Closure(_, fnRef, tpt)) if fnRef.symbol == fn.symbol =>
tpt.tpe match {
case NoType => tree // it's a plain function
case tpe @ SAMType(_) if tpe.isRef(defn.PartialFunctionClass) =>
toPartialFunction(tree)
case tpe @ SAMType(_) if isJvmSam(tpe.classSymbol.asClass) =>
case tpe @ SAMType(_) if isPlatformSam(tpe.classSymbol.asClass) =>
tree
case tpe =>
cpy.Block(tree)(stats,
Expand Down