diff options
Diffstat (limited to 'examples/scala-js/ir/src/main/scala/scala/scalajs/ir')
15 files changed, 3870 insertions, 0 deletions
diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/ClassKind.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/ClassKind.scala new file mode 100644 index 0000000..5092d2c --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/ClassKind.scala @@ -0,0 +1,53 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +import scala.annotation.switch + +sealed abstract class ClassKind { + import ClassKind._ + + def isClass = this match { + case Class | ModuleClass => true + case _ => false + } + + def isType = this match { + case TraitImpl => false + case _ => true + } +} + +object ClassKind { + case object Class extends ClassKind + case object ModuleClass extends ClassKind + case object Interface extends ClassKind + case object RawJSType extends ClassKind + case object HijackedClass extends ClassKind + case object TraitImpl extends ClassKind + + private[ir] def toByte(kind: ClassKind): Byte = kind match { + case ClassKind.Class => 1 + case ClassKind.ModuleClass => 2 + case ClassKind.Interface => 3 + case ClassKind.RawJSType => 4 + case ClassKind.HijackedClass => 5 + case ClassKind.TraitImpl => 6 + } + + private[ir] def fromByte(b: Byte): ClassKind = (b: @switch) match { + case 1 => ClassKind.Class + case 2 => ClassKind.ModuleClass + case 3 => ClassKind.Interface + case 4 => ClassKind.RawJSType + case 5 => ClassKind.HijackedClass + case 6 => ClassKind.TraitImpl + } +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Definitions.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Definitions.scala new file mode 100644 index 0000000..0762602 --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Definitions.scala @@ -0,0 +1,144 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +object Definitions { + val ObjectClass = "O" + val ClassClass = "jl_Class" + + val StringClass = "T" + + val PrimitiveClasses = Set("V", "Z", "C", "B", "S", "I", "J", "F", "D") + + def isPrimitiveClass(encodedName: String): Boolean = + PrimitiveClasses.contains(encodedName) + + val BoxedUnitClass = "sr_BoxedUnit" + val BoxedBooleanClass = "jl_Boolean" + val BoxedCharacterClass = "jl_Character" + val BoxedByteClass = "jl_Byte" + val BoxedShortClass = "jl_Short" + val BoxedIntegerClass = "jl_Integer" + val BoxedLongClass = "jl_Long" + val BoxedFloatClass = "jl_Float" + val BoxedDoubleClass = "jl_Double" + + val CharSequenceClass = "jl_CharSequence" + val SerializableClass = "Ljava_io_Serializable" + val ComparableClass = "jl_Comparable" + val NumberClass = "jl_Number" + + val HijackedBoxedClasses = Set( + BoxedUnitClass, BoxedBooleanClass, BoxedByteClass, BoxedShortClass, + BoxedIntegerClass, BoxedLongClass, BoxedFloatClass, BoxedDoubleClass) + val HijackedClasses = + HijackedBoxedClasses + StringClass + + val AncestorsOfStringClass = Set( + CharSequenceClass, ComparableClass, SerializableClass) + val AncestorsOfHijackedNumberClasses = Set( + NumberClass, ComparableClass, SerializableClass) + val AncestorsOfBoxedBooleanClass = Set( + ComparableClass, SerializableClass) + + val AncestorsOfHijackedClasses = + AncestorsOfStringClass ++ AncestorsOfHijackedNumberClasses ++ + AncestorsOfBoxedBooleanClass + + val RuntimeNullClass = "sr_Null$" + val RuntimeNothingClass = "sr_Nothing$" + + val ThrowableClass = "jl_Throwable" + + /** Encodes a class name. */ + def encodeClassName(fullName: String): String = { + val base = fullName.replace("_", "$und").replace(".", "_") + val encoded = compressedClasses.getOrElse(base, { + compressedPrefixes collectFirst { + case (prefix, compressed) if base.startsWith(prefix) => + compressed + base.substring(prefix.length) + } getOrElse { + "L"+base + } + }) + if (Trees.isKeyword(encoded) || encoded.charAt(0).isDigit || + encoded.charAt(0) == '$') { + "$" + encoded + } else encoded + } + + // !!! Duplicate logic: this code must be in sync with runtime.StackTrace + + /** Decodes a class name encoded with [[encodeClassName]]. */ + def decodeClassName(encodedName: String): String = { + val encoded = + if (encodedName.charAt(0) == '$') encodedName.substring(1) + else encodedName + val base = decompressedClasses.getOrElse(encoded, { + decompressedPrefixes collectFirst { + case (prefix, decompressed) if encoded.startsWith(prefix) => + decompressed + encoded.substring(prefix.length) + } getOrElse { + assert(!encoded.isEmpty && encoded.charAt(0) == 'L', + s"Cannot decode invalid encoded name '$encodedName'") + encoded.substring(1) + } + }) + base.replace("_", ".").replace("$und", "_") + } + + private val compressedClasses: Map[String, String] = Map( + "java_lang_Object" -> "O", + "java_lang_String" -> "T", + "scala_Unit" -> "V", + "scala_Boolean" -> "Z", + "scala_Char" -> "C", + "scala_Byte" -> "B", + "scala_Short" -> "S", + "scala_Int" -> "I", + "scala_Long" -> "J", + "scala_Float" -> "F", + "scala_Double" -> "D" + ) ++ ( + for (index <- 2 to 22) + yield s"scala_Tuple$index" -> ("T"+index) + ) ++ ( + for (index <- 0 to 22) + yield s"scala_Function$index" -> ("F"+index) + ) + + private val decompressedClasses: Map[String, String] = + compressedClasses map { case (a, b) => (b, a) } + + private val compressedPrefixes = Seq( + "scala_scalajs_runtime_" -> "sjsr_", + "scala_scalajs_" -> "sjs_", + "scala_collection_immutable_" -> "sci_", + "scala_collection_mutable_" -> "scm_", + "scala_collection_generic_" -> "scg_", + "scala_collection_" -> "sc_", + "scala_runtime_" -> "sr_", + "scala_" -> "s_", + "java_lang_" -> "jl_", + "java_util_" -> "ju_" + ) + + private val decompressedPrefixes: Seq[(String, String)] = + compressedPrefixes map { case (a, b) => (b, a) } + + /* Common predicates on encoded names */ + + def isConstructorName(name: String): Boolean = + name.startsWith("init___") + + def isReflProxyName(name: String): Boolean = + name.endsWith("__") && !isConstructorName(name) + +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Hashers.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Hashers.scala new file mode 100644 index 0000000..168d7c1 --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Hashers.scala @@ -0,0 +1,459 @@ +package scala.scalajs.ir + +import java.security.{MessageDigest, DigestOutputStream} +import java.io.{OutputStream, DataOutputStream} +import java.util.Arrays + +import Trees._ +import Types._ +import Tags._ + +object Hashers { + + def hashMethodDef(methodDef: MethodDef): MethodDef = { + if (methodDef.hash.isDefined) methodDef + else { + val hasher = new TreeHasher() + val MethodDef(name, args, resultType, body) = methodDef + + hasher.mixPos(methodDef.pos) + hasher.mixPropertyName(name) + hasher.mixTrees(args) + hasher.mixType(resultType) + hasher.mixTree(body) + + val hash = hasher.finalizeHash() + + MethodDef(name, args, resultType, body)(Some(hash))(methodDef.pos) + } + } + + /** Hash definitions from a ClassDef where applicable */ + def hashDefs(defs: List[Tree]): List[Tree] = defs map { + case methodDef: MethodDef => hashMethodDef(methodDef) + case otherDef => otherDef + } + + /** Hash the definitions in a ClassDef (where applicable) */ + def hashClassDef(classDef: ClassDef): ClassDef = + classDef.copy(defs = hashDefs(classDef.defs))(classDef.pos) + + def hashesEqual(x: TreeHash, y: TreeHash, considerPos: Boolean): Boolean = { + Arrays.equals(x.treeHash, y.treeHash) && + (!considerPos || Arrays.equals(x.posHash, y.posHash)) + } + + private final class TreeHasher { + private def newDigest = MessageDigest.getInstance("SHA-1") + private def newDigestStream(digest: MessageDigest) = { + val out = new OutputStream { + def write(b: Int): Unit = () + } + val digOut = new DigestOutputStream(out, digest) + new DataOutputStream(digOut) + } + + private[this] val treeDigest = newDigest + private[this] val treeStream = newDigestStream(treeDigest) + + private[this] val posDigest = newDigest + private[this] val posStream = newDigestStream(posDigest) + + def finalizeHash(): TreeHash = + new TreeHash(treeDigest.digest(), posDigest.digest()) + + def mixTree(tree: Tree): Unit = { + mixPos(tree.pos) + tree match { + case EmptyTree => + mixTag(TagEmptyTree) + + case VarDef(ident, vtpe, mutable, rhs) => + mixTag(TagVarDef) + mixIdent(ident) + mixType(vtpe) + mixBoolean(mutable) + mixTree(rhs) + + case ParamDef(ident, ptpe, mutable) => + mixTag(TagParamDef) + mixIdent(ident) + mixType(ptpe) + mixBoolean(mutable) + + case Skip() => + mixTag(TagSkip) + + case Block(stats) => + mixTag(TagBlock) + mixTrees(stats) + + case Labeled(label, tpe, body) => + mixTag(TagLabeled) + mixIdent(label) + mixType(tpe) + mixTree(body) + + case Assign(lhs, rhs) => + mixTag(TagAssign) + mixTree(lhs) + mixTree(rhs) + + case Return(expr, label) => + mixTag(TagReturn) + mixTree(expr) + mixOptIdent(label) + + case If(cond, thenp, elsep) => + mixTag(TagIf) + mixTree(cond) + mixTree(thenp) + mixTree(elsep) + mixType(tree.tpe) + + case While(cond, body, label) => + mixTag(TagWhile) + mixTree(cond) + mixTree(body) + mixOptIdent(label) + + case DoWhile(body, cond, label) => + mixTag(TagDoWhile) + mixTree(body) + mixTree(cond) + mixOptIdent(label) + + case Try(block, errVar, handler, finalizer) => + mixTag(TagTry) + mixTree(block) + mixIdent(errVar) + mixTree(handler) + mixTree(finalizer) + mixType(tree.tpe) + + case Throw(expr) => + mixTag(TagThrow) + mixTree(expr) + + case Continue(label) => + mixTag(TagContinue) + mixOptIdent(label) + + case Match(selector, cases, default) => + mixTag(TagMatch) + mixTree(selector) + cases foreach { case (patterns, body) => + mixTrees(patterns) + mixTree(body) + } + mixTree(default) + mixType(tree.tpe) + + case Debugger() => + mixTag(TagDebugger) + + case New(cls, ctor, args) => + mixTag(TagNew) + mixType(cls) + mixIdent(ctor) + mixTrees(args) + + case LoadModule(cls) => + mixTag(TagLoadModule) + mixType(cls) + + case StoreModule(cls, value) => + mixTag(TagStoreModule) + mixType(cls) + mixTree(value) + + case Select(qualifier, item, mutable) => + mixTag(TagSelect) + mixTree(qualifier) + mixIdent(item) + mixBoolean(mutable) + mixType(tree.tpe) + + case Apply(receiver, method, args) => + mixTag(TagApply) + mixTree(receiver) + mixIdent(method) + mixTrees(args) + mixType(tree.tpe) + + case StaticApply(receiver, cls, method, args) => + mixTag(TagStaticApply) + mixTree(receiver) + mixType(cls) + mixIdent(method) + mixTrees(args) + mixType(tree.tpe) + + case TraitImplApply(impl, method, args) => + mixTag(TagTraitImplApply) + mixType(impl) + mixIdent(method) + mixTrees(args) + mixType(tree.tpe) + + case UnaryOp(op, lhs) => + mixTag(TagUnaryOp) + mixInt(op) + mixTree(lhs) + + case BinaryOp(op, lhs, rhs) => + mixTag(TagBinaryOp) + mixInt(op) + mixTree(lhs) + mixTree(rhs) + + case NewArray(tpe, lengths) => + mixTag(TagNewArray) + mixType(tpe) + mixTrees(lengths) + + case ArrayValue(tpe, elems) => + mixTag(TagArrayValue) + mixType(tpe) + mixTrees(elems) + + case ArrayLength(array) => + mixTag(TagArrayLength) + mixTree(array) + + case ArraySelect(array, index) => + mixTag(TagArraySelect) + mixTree(array) + mixTree(index) + mixType(tree.tpe) + + case RecordValue(tpe, elems) => + mixTag(TagRecordValue) + mixType(tpe) + mixTrees(elems) + + case IsInstanceOf(expr, cls) => + mixTag(TagIsInstanceOf) + mixTree(expr) + mixType(cls) + + case AsInstanceOf(expr, cls) => + mixTag(TagAsInstanceOf) + mixTree(expr) + mixType(cls) + + case Unbox(expr, charCode) => + mixTag(TagUnbox) + mixTree(expr) + mixInt(charCode) + + case GetClass(expr) => + mixTag(TagGetClass) + mixTree(expr) + + case CallHelper(helper, args) => + mixTag(TagCallHelper) + mixString(helper) + mixTrees(args) + mixType(tree.tpe) + + case JSNew(ctor, args) => + mixTag(TagJSNew) + mixTree(ctor) + mixTrees(args) + + case JSDotSelect(qualifier, item) => + mixTag(TagJSDotSelect) + mixTree(qualifier) + mixIdent(item) + + case JSBracketSelect(qualifier, item) => + mixTag(TagJSBracketSelect) + mixTree(qualifier) + mixTree(item) + + case JSFunctionApply(fun, args) => + mixTag(TagJSFunctionApply) + mixTree(fun) + mixTrees(args) + + case JSDotMethodApply(receiver, method, args) => + mixTag(TagJSDotMethodApply) + mixTree(receiver) + mixIdent(method) + mixTrees(args) + + case JSBracketMethodApply(receiver, method, args) => + mixTag(TagJSBracketMethodApply) + mixTree(receiver) + mixTree(method) + mixTrees(args) + + case JSDelete(prop) => + mixTag(TagJSDelete) + mixTree(prop) + + case JSUnaryOp(op, lhs) => + mixTag(TagJSUnaryOp) + mixString(op) + mixTree(lhs) + + case JSBinaryOp(op, lhs, rhs) => + mixTag(TagJSBinaryOp) + mixString(op) + mixTree(lhs) + mixTree(rhs) + + case JSArrayConstr(items) => + mixTag(TagJSArrayConstr) + mixTrees(items) + + case JSObjectConstr(fields) => + mixTag(TagJSObjectConstr) + fields foreach { case (pn, value) => + mixPropertyName(pn) + mixTree(value) + } + + case JSEnvInfo() => + mixTag(TagJSEnvInfo) + + case Undefined() => + mixTag(TagUndefined) + + case UndefinedParam() => + mixTag(TagUndefinedParam) + mixType(tree.tpe) + + case Null() => + mixTag(TagNull) + + case BooleanLiteral(value) => + mixTag(TagBooleanLiteral) + mixBoolean(value) + + case IntLiteral(value) => + mixTag(TagIntLiteral) + mixInt(value) + + case LongLiteral(value) => + mixTag(TagLongLiteral) + mixLong(value) + + case FloatLiteral(value) => + mixTag(TagFloatLiteral) + mixFloat(value) + + case DoubleLiteral(value) => + mixTag(TagDoubleLiteral) + mixDouble(value) + + case StringLiteral(value) => + mixTag(TagStringLiteral) + mixString(value) + + case ClassOf(cls) => + mixTag(TagClassOf) + mixType(cls) + + case VarRef(ident, mutable) => + mixTag(TagVarRef) + mixIdent(ident) + mixBoolean(mutable) + mixType(tree.tpe) + + case This() => + mixTag(TagThis) + mixType(tree.tpe) + + case Closure(captureParams, params, body, captureValues) => + mixTag(TagClosure) + mixTrees(captureParams) + mixTrees(params) + mixTree(body) + mixTrees(captureValues) + + case _ => + sys.error(s"Unable to hash tree of class ${tree.getClass}") + + } + } + + def mixTrees(trees: List[Tree]): Unit = + trees.foreach(mixTree) + + def mixType(tpe: Type): Unit = tpe match { + case AnyType => mixTag(TagAnyType) + case NothingType => mixTag(TagNothingType) + case UndefType => mixTag(TagUndefType) + case BooleanType => mixTag(TagBooleanType) + case IntType => mixTag(TagIntType) + case LongType => mixTag(TagLongType) + case FloatType => mixTag(TagFloatType) + case DoubleType => mixTag(TagDoubleType) + case StringType => mixTag(TagStringType) + case NullType => mixTag(TagNullType) + case NoType => mixTag(TagNoType) + + case tpe: ClassType => + mixTag(TagClassType) + mixString(tpe.className) + + case tpe: ArrayType => + mixTag(TagArrayType) + mixString(tpe.baseClassName) + mixInt(tpe.dimensions) + + case RecordType(fields) => + mixTag(TagRecordType) + for (RecordType.Field(name, originalName, tpe, mutable) <- fields) { + mixString(name) + originalName.foreach(mixString) + mixType(tpe) + mixBoolean(mutable) + } + } + + def mixIdent(ident: Ident): Unit = { + mixPos(ident.pos) + mixString(ident.name) + ident.originalName.foreach(mixString) + } + + def mixOptIdent(optIdent: Option[Ident]): Unit = optIdent.foreach(mixIdent) + + def mixPropertyName(name: PropertyName): Unit = name match { + case name: Ident => mixIdent(name) + case name: StringLiteral => mixTree(name) + } + + def mixPos(pos: Position): Unit = { + posStream.writeUTF(pos.source.toString) + posStream.writeInt(pos.line) + posStream.writeInt(pos.column) + } + + @inline + private final def mixTag(tag: Int): Unit = mixInt(tag) + + @inline + private final def mixString(str: String): Unit = treeStream.writeUTF(str) + + @inline + private final def mixInt(i: Int): Unit = treeStream.writeInt(i) + + @inline + private final def mixLong(l: Long): Unit = treeStream.writeLong(l) + + @inline + private final def mixBoolean(b: Boolean): Unit = treeStream.writeBoolean(b) + + @inline + private final def mixFloat(f: Float): Unit = treeStream.writeFloat(f) + + @inline + private final def mixDouble(d: Double): Unit = treeStream.writeDouble(d) + + } + +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/InfoSerializers.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/InfoSerializers.scala new file mode 100644 index 0000000..dfb520f --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/InfoSerializers.scala @@ -0,0 +1,180 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +import java.io._ + +import Infos._ + +object InfoSerializers { + + /** Scala.js IR File Magic Number + * + * CA FE : first part of magic number of Java class files + * 4A 53 : "JS" in ASCII + * + */ + final val IRMagicNumber = 0xCAFE4A53 + + def serialize(stream: OutputStream, classInfo: ClassInfo): Unit = { + new Serializer().serialize(stream, classInfo) + } + + def deserializeRoughInfo(stream: InputStream): RoughClassInfo = { + deserializeVersionRoughInfo(stream)._2 + } + + def deserializeFullInfo(stream: InputStream): ClassInfo = { + deserializeVersionFullInfo(stream)._2 + } + + def deserializeVersionRoughInfo(stream: InputStream): (String, RoughClassInfo) = { + new Deserializer(stream).deserializeRough() + } + + def deserializeVersionFullInfo(stream: InputStream): (String, ClassInfo) = { + new Deserializer(stream).deserializeFull() + } + + private final class Serializer { + def serialize(stream: OutputStream, classInfo: ClassInfo): Unit = { + val s = new DataOutputStream(stream) + + def writeSeq[A](seq: Seq[A])(writeElem: A => Unit): Unit = { + s.writeInt(seq.size) + seq.foreach(writeElem) + } + + def writeStrings(seq: Seq[String]): Unit = + writeSeq(seq)(s.writeUTF(_)) + + // Write the Scala.js IR magic number + s.writeInt(IRMagicNumber) + + // Write the Scala.js Version + s.writeUTF(ScalaJSVersions.binaryEmitted) + + import classInfo._ + s.writeUTF(name) + s.writeUTF(encodedName) + s.writeBoolean(isExported) + s.writeInt(ancestorCount) + s.writeByte(ClassKind.toByte(kind)) + s.writeUTF(superClass) + writeStrings(ancestors) + s.writeInt(optimizerHints.bits) + + def writeMethodInfo(methodInfo: MethodInfo): Unit = { + import methodInfo._ + s.writeUTF(encodedName) + s.writeBoolean(isAbstract) + s.writeBoolean(isExported) + writeSeq(calledMethods.toSeq) { + case (caller, callees) => s.writeUTF(caller); writeStrings(callees) + } + writeSeq(calledMethodsStatic.toSeq) { + case (caller, callees) => s.writeUTF(caller); writeStrings(callees) + } + writeStrings(instantiatedClasses) + writeStrings(accessedModules) + writeStrings(accessedClassData) + s.writeInt(optimizerHints.bits) + } + + writeSeq(methods)(writeMethodInfo(_)) + + s.flush() + } + } + + private final class Deserializer(stream: InputStream) { + private[this] val input = new DataInputStream(stream) + + def readList[A](readElem: => A): List[A] = + List.fill(input.readInt())(readElem) + + def readStrings(): List[String] = + readList(input.readUTF()) + + def deserializeRough(): (String, RoughClassInfo) = { + val version = readHeader() + + import input._ + val name = readUTF() + val encodedName = readUTF() + val isExported = readBoolean() + val ancestorCount = readInt() + val info = RoughClassInfo(name, encodedName, isExported, ancestorCount) + + (version, info) + } + + def deserializeFull(): (String, ClassInfo) = { + val version = readHeader() + + import input._ + + val name = readUTF() + val encodedName = readUTF() + val isExported = readBoolean() + val ancestorCount = readInt() + val kind = ClassKind.fromByte(readByte()) + val superClass = readUTF() + val ancestors = readList(readUTF()) + + val optimizerHints = + if (version == "0.5.0" || version == "0.5.2") OptimizerHints.empty + else new OptimizerHints(readInt()) + + def readMethod(): MethodInfo = { + val encodedName = readUTF() + val isAbstract = readBoolean() + val isExported = readBoolean() + val calledMethods = readList(readUTF() -> readStrings()).toMap + val calledMethodsStatic = readList(readUTF() -> readStrings()).toMap + val instantiatedClasses = readStrings() + val accessedModules = readStrings() + val accessedClassData = readStrings() + val optimizerHints = new OptimizerHints(readInt()) + MethodInfo(encodedName, isAbstract, isExported, + calledMethods, calledMethodsStatic, + instantiatedClasses, accessedModules, accessedClassData, + optimizerHints) + } + + val methods = readList(readMethod()) + + val info = ClassInfo(name, encodedName, isExported, ancestorCount, kind, + superClass, ancestors, optimizerHints, methods) + + (version, info) + } + + /** Reads the Scala.js IR header and verifies the version compatibility. + * Returns the emitted binary version. + */ + def readHeader(): String = { + // Check magic number + if (input.readInt() != IRMagicNumber) + throw new IOException("Not a Scala.js IR file") + + // Check that we support this version of the IR + val version = input.readUTF() + val supported = ScalaJSVersions.binarySupported + if (!supported.contains(version)) { + throw new IOException( + s"This version ($version) of Scala.js IR is not supported. " + + s"Supported versions are: ${supported.mkString(", ")}") + } + + version + } + } +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Infos.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Infos.scala new file mode 100644 index 0000000..66feec2 --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Infos.scala @@ -0,0 +1,118 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +object Infos { + + sealed class RoughClassInfo protected ( + val name: String, + val encodedName: String, + val isExported: Boolean, + val ancestorCount: Int + ) + + object RoughClassInfo { + def apply(name: String, encodedName: String, isExported: Boolean, + ancestorCount: Int): RoughClassInfo = { + new RoughClassInfo(name, encodedName, isExported, ancestorCount) + } + } + + final class ClassInfo protected ( + name: String, + encodedName: String, + isExported: Boolean, + ancestorCount: Int, + val kind: ClassKind, + val superClass: String, + val ancestors: List[String], // includes this class + val optimizerHints: OptimizerHints, + val methods: List[MethodInfo] + ) extends RoughClassInfo(name, encodedName, isExported, ancestorCount) + + object ClassInfo { + def apply( + name: String, + encodedName: String, + isExported: Boolean = false, + ancestorCount: Int = 0, + kind: ClassKind = ClassKind.Class, + superClass: String = "", + ancestors: List[String] = Nil, + optimizerHints: OptimizerHints = OptimizerHints.empty, + methods: List[MethodInfo] = Nil): ClassInfo = { + new ClassInfo(name, encodedName, isExported, ancestorCount, + kind, superClass, ancestors, optimizerHints, methods) + } + } + + final class MethodInfo private ( + val encodedName: String, + val isAbstract: Boolean, + val isExported: Boolean, + val calledMethods: Map[String, List[String]], + val calledMethodsStatic: Map[String, List[String]], + val instantiatedClasses: List[String], + val accessedModules: List[String], + val accessedClassData: List[String], + val optimizerHints: OptimizerHints + ) + + object MethodInfo { + def apply( + encodedName: String, + isAbstract: Boolean = false, + isExported: Boolean = false, + calledMethods: Map[String, List[String]] = Map.empty, + calledMethodsStatic: Map[String, List[String]] = Map.empty, + instantiatedClasses: List[String] = Nil, + accessedModules: List[String] = Nil, + accessedClassData: List[String] = Nil, + optimizerHints: OptimizerHints = OptimizerHints.empty): MethodInfo = { + new MethodInfo(encodedName, isAbstract, isExported, calledMethods, + calledMethodsStatic, instantiatedClasses, accessedModules, + accessedClassData, optimizerHints) + } + } + + final class OptimizerHints(val bits: Int) extends AnyVal { + import OptimizerHints._ + + private[scalajs] def isAccessor: Boolean = (bits & AccessorMask) != 0 + private[scalajs] def hasInlineAnnot: Boolean = (bits & InlineAnnotMask) != 0 + + private[scalajs] def copy( + isAccessor: Boolean = this.isAccessor, + hasInlineAnnot: Boolean = this.hasInlineAnnot + ): OptimizerHints = { + var bits: Int = 0 + if (isAccessor) + bits |= AccessorMask + if (hasInlineAnnot) + bits |= InlineAnnotMask + new OptimizerHints(bits) + } + + override def toString(): String = + s"OptimizerHints($bits)" + } + + object OptimizerHints { + private final val AccessorShift = 0 + private final val AccessorMask = 1 << AccessorShift + + private final val InlineAnnotShift = 1 + private final val InlineAnnotMask = 1 << InlineAnnotShift + + final val empty: OptimizerHints = + new OptimizerHints(0) + } + +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Position.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Position.scala new file mode 100644 index 0000000..3b6d0a2 --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Position.scala @@ -0,0 +1,42 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +final case class Position( + /** Source file. */ + source: Position.SourceFile, + /** Zero-based line number. */ + line: Int, + /** Zero-based column number. */ + column: Int +) { + def show: String = s"$line:$column" + + def isEmpty: Boolean = { + source.getScheme == null && source.getRawAuthority == null && + source.getRawPath == "" && source.getRawQuery == null && + source.getRawFragment == null + } + + def isDefined: Boolean = !isEmpty + + def orElse(that: => Position): Position = if (isDefined) this else that +} + +object Position { + type SourceFile = java.net.URI + + object SourceFile { + def apply(f: java.io.File): SourceFile = f.toURI + def apply(f: String): SourceFile = new java.net.URI(f) + } + + val NoPosition = Position(SourceFile(""), 0, 0) +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Printers.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Printers.scala new file mode 100644 index 0000000..6208d5f --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Printers.scala @@ -0,0 +1,709 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +import scala.annotation.{switch, tailrec} + +import java.io.Writer + +import Position._ +import Trees._ +import Types._ +import Infos._ +import Utils.escapeJS + +object Printers { + + /** Basically copied from scala.reflect.internal.Printers */ + trait IndentationManager { + protected val out: Writer + + protected var indentMargin = 0 + protected val indentStep = 2 + protected var indentString = " " // 40 + + protected def indent(): Unit = indentMargin += indentStep + protected def undent(): Unit = indentMargin -= indentStep + + protected def println(): Unit = { + out.write('\n') + while (indentMargin > indentString.length()) + indentString += indentString + if (indentMargin > 0) + out.write(indentString, 0, indentMargin) + } + + @tailrec + protected final def printSeq[A](ls: List[A])(printelem: A => Unit)( + printsep: A => Unit): Unit = { + ls match { + case Nil => + case x :: Nil => + printelem(x) + case x :: rest => + printelem(x) + printsep(x) + printSeq(rest)(printelem)(printsep) + } + } + + protected def printColumn(ts: List[Any], start: String, sep: String, + end: String): Unit = { + print(start); indent(); println() + printSeq(ts) { x => + print(x) + } { _ => + print(sep) + println() + } + undent(); println(); print(end) + } + + protected def printRow(ts: List[Any], start: String, sep: String, + end: String): Unit = { + print(start) + printSeq(ts) { x => + print(x) + } { _ => + print(sep) + } + print(end) + } + + protected def printRow(ts: List[Any], sep: String): Unit = + printRow(ts, "", sep, "") + + protected def print(args: Any*): Unit = + args.foreach(printOne) + + protected def printOne(arg: Any): Unit + } + + class IRTreePrinter(protected val out: Writer) extends IndentationManager { + def printTopLevelTree(tree: Tree) { + tree match { + case Skip() => + // do not print anything + case Block(stats) => + for (stat <- stats) + printTopLevelTree(stat) + case _ => + printTree(tree) + println() + } + } + + protected def printBlock(tree: Tree): Unit = { + val trees = tree match { + case Block(trees) => trees + case _ => List(tree) + } + printColumn(trees, "{", ";", "}") + } + + protected def printSig(args: List[ParamDef], resultType: Type): Unit = { + printRow(args, "(", ", ", ")") + if (resultType != NoType) + print(": ", resultType, " = ") + else + print(" ") + } + + protected def printArgs(args: List[Tree]): Unit = { + printRow(args, "(", ", ", ")") + } + + def printTree(tree: Tree): Unit = { + tree match { + case EmptyTree => + print("<empty>") + + // Definitions + + case VarDef(ident, vtpe, mutable, rhs) => + if (mutable) + print("var ") + else + print("val ") + print(ident, ": ", vtpe) + if (rhs != EmptyTree) + print(" = ", rhs) + + case ParamDef(ident, ptpe, mutable) => + if (mutable) + print("var ") + print(ident, ": ", ptpe) + + // Control flow constructs + + case Skip() => + print("/*<skip>*/") + + case tree: Block => + printBlock(tree) + + case Labeled(label, tpe, body) => + print(label) + if (tpe != NoType) + print("[", tpe, "]") + print(": ") + printBlock(body) + + case Assign(lhs, rhs) => + print(lhs, " = ", rhs) + + case Return(expr, label) => + if (label.isEmpty) print("return ", expr) + else print("return(", label.get, ") ", expr) + + case If(cond, BooleanLiteral(true), elsep) => + print(cond, " || ", elsep) + case If(cond, thenp, BooleanLiteral(false)) => + print(cond, " && ", thenp) + + case If(cond, thenp, elsep) => + print("if (", cond, ") ") + printBlock(thenp) + elsep match { + case Skip() => () + case If(_, _, _) => + print(" else ") + printTree(elsep) + case _ => + print(" else ") + printBlock(elsep) + } + + case While(cond, body, label) => + if (label.isDefined) + print(label.get, ": ") + print("while (", cond, ") ") + printBlock(body) + + case DoWhile(body, cond, label) => + if (label.isDefined) + print(label.get, ": ") + print("do ") + printBlock(body) + print(" while (", cond, ")") + + case Try(block, errVar, handler, finalizer) => + print("try ") + printBlock(block) + if (handler != EmptyTree) { + print(" catch (", errVar, ") ") + printBlock(handler) + } + if (finalizer != EmptyTree) { + print(" finally ") + printBlock(finalizer) + } + + case Throw(expr) => + print("throw ", expr) + + case Continue(label) => + if (label.isEmpty) print("continue") + else print("continue ", label.get) + + case Match(selector, cases, default) => + print("match (", selector, ") ") + print("{"); indent + for ((values, body) <- cases) { + println() + printRow(values, "case ", " | ", ":"); indent; println() + printTree(body) + print(";") + undent + } + if (default != EmptyTree) { + println() + print("default:"); indent; println() + printTree(default) + print(";") + undent + } + undent; println(); print("}") + + case Debugger() => + print("debugger") + + // Scala expressions + + case New(cls, ctor, args) => + print("new ", cls, "().", ctor) + printArgs(args) + + case LoadModule(cls) => + print("mod:", cls) + + case StoreModule(cls, value) => + print("mod:", cls, "<-", value) + + case Select(qualifier, item, _) => + print(qualifier, ".", item) + + case Apply(receiver, method, args) => + print(receiver, ".", method) + printArgs(args) + + case StaticApply(receiver, cls, method, args) => + print(receiver, ".", cls, "::", method) + printArgs(args) + + case TraitImplApply(impl, method, args) => + print(impl, "::", method) + printArgs(args) + + case UnaryOp(op, lhs) => + import UnaryOp._ + print("(", (op: @switch) match { + case `typeof` => "typeof" + case Boolean_! => "!" + case IntToLong | DoubleToLong => "(long)" + case DoubleToInt | LongToInt => "(int)" + case DoubleToFloat => "(float)" + case LongToDouble => "(double)" + }, lhs, ")") + + case BinaryOp(BinaryOp.Int_-, IntLiteral(0), rhs) => + print("(-", rhs, ")") + case BinaryOp(BinaryOp.Int_^, IntLiteral(-1), rhs) => + print("(~", rhs, ")") + case BinaryOp(BinaryOp.Long_-, LongLiteral(0L), rhs) => + print("(-", rhs, ")") + case BinaryOp(BinaryOp.Long_^, LongLiteral(-1L), rhs) => + print("(~", rhs, ")") + case BinaryOp(BinaryOp.Float_-, FloatLiteral(0.0f), rhs) => + print("(-", rhs, ")") + case BinaryOp(BinaryOp.Double_-, + IntLiteral(0) | FloatLiteral(0.0f) | DoubleLiteral(0.0), rhs) => + print("(-", rhs, ")") + + case BinaryOp(op, lhs, rhs) => + import BinaryOp._ + print("(", lhs, " ", (op: @switch) match { + case === => "===" + case !== => "!==" + + case String_+ => "+[string]" + + case `in` => "in" + case `instanceof` => "instanceof" + + case Int_+ => "+[int]" + case Int_- => "-[int]" + case Int_* => "*[int]" + case Int_/ => "/[int]" + case Int_% => "%[int]" + + case Int_| => "|" + case Int_& => "&" + case Int_^ => "^" + case Int_<< => "<<" + case Int_>>> => ">>>" + case Int_>> => ">>" + + case Float_+ => "+[float]" + case Float_- => "-[float]" + case Float_* => "*[float]" + case Float_/ => "/[float]" + case Float_% => "%[float]" + + case Double_+ => "+" + case Double_- => "-" + case Double_* => "*" + case Double_/ => "/" + case Double_% => "%" + + case Num_== => "==" + case Num_!= => "!=" + case Num_< => "<" + case Num_<= => "<=" + case Num_> => ">" + case Num_>= => ">=" + + case Long_+ => "+[long]" + case Long_- => "-[long]" + case Long_* => "*[long]" + case Long_/ => "/[long]" + case Long_% => "%[long]" + + case Long_| => "|[long]" + case Long_& => "&[long]" + case Long_^ => "^[long]" + case Long_<< => "<<[long]" + case Long_>>> => ">>>[long]" + case Long_>> => ">>[long]" + + case Long_== => "==[long]" + case Long_!= => "!=[long]" + case Long_< => "<[long]" + case Long_<= => "<=[long]" + case Long_> => ">[long]" + case Long_>= => ">=[long]" + + case Boolean_== => "==[bool]" + case Boolean_!= => "!=[bool]" + case Boolean_| => "|[bool]" + case Boolean_& => "&[bool]" + }, " ", rhs, ")") + + case NewArray(tpe, lengths) => + print("new ", tpe.baseClassName) + for (length <- lengths) + print("[", length, "]") + for (dim <- lengths.size until tpe.dimensions) + print("[]") + + case ArrayValue(tpe, elems) => + print(tpe) + printArgs(elems) + + case ArrayLength(array) => + print(array, ".length") + + case ArraySelect(array, index) => + print(array, "[", index, "]") + + case RecordValue(tpe, elems) => + print("(") + var first = true + for ((field, value) <- tpe.fields zip elems) { + if (first) first = false + else print(", ") + print(field.name, " = ", value) + } + print(")") + + case IsInstanceOf(expr, cls) => + print(expr, ".isInstanceOf[", cls, "]") + + case AsInstanceOf(expr, cls) => + print(expr, ".asInstanceOf[", cls, "]") + + case Unbox(expr, charCode) => + print(expr, ".asInstanceOf[", charCode, "]") + + case GetClass(expr) => + print(expr, ".getClass()") + + case CallHelper(helper, args) => + print(helper) + printArgs(args) + + // JavaScript expressions + + case JSNew(ctor, args) => + def containsOnlySelectsFromAtom(tree: Tree): Boolean = tree match { + case JSDotSelect(qual, _) => containsOnlySelectsFromAtom(qual) + case JSBracketSelect(qual, _) => containsOnlySelectsFromAtom(qual) + case VarRef(_, _) => true + case This() => true + case _ => false // in particular, Apply + } + if (containsOnlySelectsFromAtom(ctor)) + print("new ", ctor) + else + print("new (", ctor, ")") + printArgs(args) + + case JSDotSelect(qualifier, item) => + print(qualifier, ".", item) + + case JSBracketSelect(qualifier, item) => + print(qualifier, "[", item, "]") + + case JSFunctionApply(fun, args) => + fun match { + case _:JSDotSelect | _:JSBracketSelect | _:Select => + print("protect(", fun, ")") + case _ => + print(fun) + } + printArgs(args) + + case JSDotMethodApply(receiver, method, args) => + print(receiver, ".", method) + printArgs(args) + + case JSBracketMethodApply(receiver, method, args) => + print(receiver, "[", method, "]") + printArgs(args) + + case JSDelete(prop) => + print("delete ", prop) + + case JSUnaryOp("typeof", lhs) => + print("typeof(", lhs, ")") + + case JSUnaryOp(op, lhs) => + print("(", op, lhs, ")") + + case JSBinaryOp(op, lhs, rhs) => + print("(", lhs, " ", op, " ", rhs, ")") + + case JSArrayConstr(items) => + printRow(items, "[", ", ", "]") + + case JSObjectConstr(Nil) => + print("{}") + + case JSObjectConstr(fields) => + print("{"); indent; println() + printSeq(fields) { + case (name, value) => print(name, ": ", value) + } { _ => + print(",") + println() + } + undent; println(); print("}") + + case JSEnvInfo() => + print("<envinfo>") + + // Literals + + case Undefined() => + print("(void 0)") + + case UndefinedParam() => + print("<undefined param>") + + case Null() => + print("null") + + case BooleanLiteral(value) => + print(if (value) "true" else "false") + + case IntLiteral(value) => + if (value >= 0) + print(value) + else + print("(", value, ")") + + case FloatLiteral(value) => + if (value == 0.0f && 1.0f / value < 0.0f) + print("(-0)") + else if (value >= 0.0f) + print(value) + else + print("(", value, ")") + + case DoubleLiteral(value) => + if (value == 0.0 && 1.0 / value < 0.0) + print("(-0)") + else if (value >= 0.0) + print(value) + else + print("(", value, ")") + + case StringLiteral(value) => + print("\"", escapeJS(value), "\"") + + case ClassOf(cls) => + print("classOf[", cls, "]") + + // Atomic expressions + + case VarRef(ident, _) => + print(ident) + + case This() => + print("this") + + case Closure(captureParams, params, body, captureValues) => + print("(lambda") + printRow(captureValues, "<", ", ", ">") + printRow(captureParams ++ params, "(", ", ", ") = ") + printBlock(body) + print(")") + + // Classes + + case ClassDef(name, kind, parent, ancestors, defs) => + kind match { + case ClassKind.Class => print("class ") + case ClassKind.ModuleClass => print("module class ") + case ClassKind.Interface => print("interface ") + case ClassKind.RawJSType => print("jstype ") + case ClassKind.HijackedClass => print("hijacked class ") + case ClassKind.TraitImpl => print("trait impl ") + } + print(name) + parent.foreach(print(" extends ", _)) + if (ancestors.nonEmpty) + printRow(ancestors, " ancestors ", ", ", "") + print(" ") + printColumn(defs, "{", "", "}") + println() + + case MethodDef(name, args, resultType, body) => + print(name) + printSig(args, resultType) + printBlock(body) + + case PropertyDef(name, _, _, _) => + // TODO + print(s"<property: $name>") + + case ConstructorExportDef(fullName, args, body) => + print("export \"", escapeJS(fullName), "\"") + printSig(args, NoType) // NoType as trick not to display a type + printBlock(body) + + case ModuleExportDef(fullName) => + print("export \"", escapeJS(fullName), "\"") + + case _ => + print(s"<error, elem of class ${tree.getClass()}>") + } + } + + def printType(tpe: Type): Unit = tpe match { + case AnyType => print("any") + case NothingType => print("nothing") + case UndefType => print("void") + case BooleanType => print("boolean") + case IntType => print("int") + case LongType => print("long") + case FloatType => print("float") + case DoubleType => print("number") + case StringType => print("string") + case NullType => print("null") + case ClassType(className) => print(className) + case NoType => print("<notype>") + + case ArrayType(base, dims) => + print(base) + for (i <- 1 to dims) + print("[]") + + case RecordType(fields) => + print("(") + var first = false + for (RecordType.Field(name, _, tpe, mutable) <- fields) { + if (first) first = false + else print(", ") + if (mutable) + print("var ") + print(name, ": ", tpe) + } + print(")") + } + + protected def printIdent(ident: Ident): Unit = + printString(escapeJS(ident.name)) + + protected def printOne(arg: Any): Unit = arg match { + case tree: Tree => + printTree(tree) + case tpe: Type => + printType(tpe) + case ident: Ident => + printIdent(ident) + case arg => + printString(if (arg == null) "null" else arg.toString) + } + + protected def printString(s: String): Unit = { + out.write(s) + } + + // Make it public + override def println(): Unit = super.println() + + def complete(): Unit = () + } + + class InfoPrinter(protected val out: Writer) extends IndentationManager { + def printClassInfo(classInfo: ClassInfo): Unit = { + import classInfo._ + println("name: ", escapeJS(name)) + println("encodedName: ", escapeJS(encodedName)) + println("isExported: ", isExported) + println("ancestorCount: ", ancestorCount) + println("kind: ", kind) + println("superClass: ", superClass) + + if (ancestors.nonEmpty) { + println("ancestors: ", + ancestors.map(escapeJS).mkString("[", ", ", "]")) + } + + if (optimizerHints != OptimizerHints.empty) + println("optimizerHints: ", optimizerHints) + + print("methods:") + indent(); println() + methods.foreach(printMethodInfo) + undent(); println() + } + + def printMethodInfo(methodInfo: MethodInfo): Unit = { + import methodInfo._ + print(escapeJS(encodedName), ":") + indent(); println() + + if (isAbstract) + println("isAbstract: ", isAbstract) + if (isExported) + println("isExported: ", isExported) + if (calledMethods.nonEmpty) { + print("calledMethods:") + indent(); println() + printSeq(calledMethods.toList) { case (caller, callees) => + print(escapeJS(caller), ": ") + print(callees.map(escapeJS).mkString("[", ", ", "]")) + } { _ => println() } + undent(); println() + } + if (calledMethodsStatic.nonEmpty) { + print("calledMethodsStatic:") + indent(); println() + printSeq(calledMethodsStatic.toList) { case (caller, callees) => + print(escapeJS(caller), ": ") + print(callees.map(escapeJS).mkString("[", ", ", "]")) + } { _ => println() } + undent(); println() + } + if (instantiatedClasses.nonEmpty) { + println("instantiatedClasses: ", + instantiatedClasses.map(escapeJS).mkString("[", ", ", "]")) + } + if (accessedModules.nonEmpty) { + println("accessedModules: ", + accessedModules.map(escapeJS).mkString("[", ", ", "]")) + } + if (accessedClassData.nonEmpty) { + println("accessedClassData: ", + accessedClassData.map(escapeJS).mkString("[", ", ", "]")) + } + if (optimizerHints != OptimizerHints.empty) + println("optimizerHints: ", optimizerHints) + + undent(); println() + } + + private def println(arg1: Any, args: Any*): Unit = { + print((arg1 +: args): _*) + println() + } + + protected def printOne(arg: Any): Unit = arg match { + case classInfo: ClassInfo => printClassInfo(classInfo) + case methodInfo: MethodInfo => printMethodInfo(methodInfo) + case arg => out.write(arg.toString()) + } + + def complete(): Unit = () + } + +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/ScalaJSVersions.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/ScalaJSVersions.scala new file mode 100644 index 0000000..2690939 --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/ScalaJSVersions.scala @@ -0,0 +1,25 @@ +package scala.scalajs.ir + +object ScalaJSVersions { + + /** the Scala.js version of this build */ + final val current = "0.6.0-SNAPSHOT" + + /** true iff the Scala.js version of this build is a snapshot version. */ + final val currentIsSnapshot = current endsWith "-SNAPSHOT" + + /** Version of binary IR this Scala.js version emits + * + * This should be either of: + * - a prior release version (i.e. "0.5.0", *not* "0.5.0-SNAPSHOT") + * - `current` + */ + final val binaryEmitted = current + + /** Versions whose binary files we can support (used by deserializer) */ + val binarySupported: Set[String] = Set(binaryEmitted) + + // Just to be extra safe + assert(binarySupported contains binaryEmitted) + +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Serializers.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Serializers.scala new file mode 100644 index 0000000..04ec5c2 --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Serializers.scala @@ -0,0 +1,790 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +import scala.annotation.switch + +import java.io._ +import java.net.URI + +import scala.collection.mutable + +import Position._ +import Trees._ +import Types._ +import Tags._ + +import Utils.JumpBackByteArrayOutputStream + +object Serializers { + def serialize(stream: OutputStream, tree: Tree): Unit = { + new Serializer().serialize(stream, tree) + } + + def deserialize(stream: InputStream, version: String): Tree = { + new Deserializer(stream, version).deserialize() + } + + // true for easier debugging (not for "production", it adds 8 bytes per node) + private final val UseDebugMagic = false + private final val DebugMagic = 0x3fa8ef84 + private final val PosDebugMagic = 0x65f0ec32 + + private object PositionFormat { + /* Positions are serialized incrementally as diffs wrt the last position. + * + * Formats are (the first byte is decomposed in bits): + * + * 1st byte | next bytes | description + * ----------------------------------------- + * ccccccc0 | | Column diff (7-bit signed) + * llllll01 | CC | Line diff (6-bit signed), column (8-bit unsigned) + * ____0011 | LL LL CC | Line diff (16-bit signed), column (8-bit unsigned) + * ____0111 | 12 bytes | File index, line, column (all 32-bit signed) + * 11111111 | | NoPosition (is not compared/stored in last position) + * + * Underscores are irrelevant and must be set to 0. + */ + + final val Format1Mask = 0x01 + final val Format1MaskValue = 0x00 + final val Format1Shift = 1 + + final val Format2Mask = 0x03 + final val Format2MaskValue = 0x01 + final val Format2Shift = 2 + + final val Format3Mask = 0x0f + final val Format3MaskValue = 0x03 + + final val FormatFullMask = 0x0f + final val FormatFullMaskValue = 0x7 + + final val FormatNoPositionValue = -1 + } + + private final class Serializer { + private[this] val bufferUnderlying = new JumpBackByteArrayOutputStream + private[this] val buffer = new DataOutputStream(bufferUnderlying) + + private[this] val files = mutable.ListBuffer.empty[URI] + private[this] val fileIndexMap = mutable.Map.empty[URI, Int] + private def fileToIndex(file: URI): Int = + fileIndexMap.getOrElseUpdate(file, (files += file).size - 1) + + private[this] val strings = mutable.ListBuffer.empty[String] + private[this] val stringIndexMap = mutable.Map.empty[String, Int] + private def stringToIndex(str: String): Int = + stringIndexMap.getOrElseUpdate(str, (strings += str).size - 1) + + private[this] var lastPosition: Position = Position.NoPosition + + def serialize(stream: OutputStream, tree: Tree): Unit = { + // Write tree to buffer and record files and strings + writeTree(tree) + + val s = new DataOutputStream(stream) + + // Emit the files + s.writeInt(files.size) + files.foreach(f => s.writeUTF(f.toString)) + + // Emit the strings + s.writeInt(strings.size) + strings.foreach(s.writeUTF) + + // Paste the buffer + bufferUnderlying.writeTo(s) + + s.flush() + } + + def writeTree(tree: Tree): Unit = { + import buffer._ + writePosition(tree.pos) + tree match { + case EmptyTree => + writeByte(TagEmptyTree) + + case VarDef(ident, vtpe, mutable, rhs) => + writeByte(TagVarDef) + writeIdent(ident); writeType(vtpe); writeBoolean(mutable); writeTree(rhs) + + case ParamDef(ident, ptpe, mutable) => + writeByte(TagParamDef) + writeIdent(ident); writeType(ptpe); writeBoolean(mutable) + + case Skip() => + writeByte(TagSkip) + + case Block(stats) => + writeByte(TagBlock) + writeTrees(stats) + + case Labeled(label, tpe, body) => + writeByte(TagLabeled) + writeIdent(label); writeType(tpe); writeTree(body) + + case Assign(lhs, rhs) => + writeByte(TagAssign) + writeTree(lhs); writeTree(rhs) + + case Return(expr, label) => + writeByte(TagReturn) + writeTree(expr); writeOptIdent(label) + + case If(cond, thenp, elsep) => + writeByte(TagIf) + writeTree(cond); writeTree(thenp); writeTree(elsep) + writeType(tree.tpe) + + case While(cond, body, label) => + writeByte(TagWhile) + writeTree(cond); writeTree(body); writeOptIdent(label) + + case DoWhile(body, cond, label) => + writeByte(TagDoWhile) + writeTree(body); writeTree(cond); writeOptIdent(label) + + case Try(block, errVar, handler, finalizer) => + writeByte(TagTry) + writeTree(block); writeIdent(errVar); writeTree(handler); writeTree(finalizer) + writeType(tree.tpe) + + case Throw(expr) => + writeByte(TagThrow) + writeTree(expr) + + case Continue(label) => + writeByte(TagContinue) + writeOptIdent(label) + + case Match(selector, cases, default) => + writeByte(TagMatch) + writeTree(selector) + writeInt(cases.size) + cases foreach { caze => + writeTrees(caze._1); writeTree(caze._2) + } + writeTree(default) + writeType(tree.tpe) + + case Debugger() => + writeByte(TagDebugger) + + case New(cls, ctor, args) => + writeByte(TagNew) + writeClassType(cls); writeIdent(ctor); writeTrees(args) + + case LoadModule(cls) => + writeByte(TagLoadModule) + writeClassType(cls) + + case StoreModule(cls, value) => + writeByte(TagStoreModule) + writeClassType(cls); writeTree(value) + + case Select(qualifier, item, mutable) => + writeByte(TagSelect) + writeTree(qualifier); writeIdent(item); writeBoolean(mutable) + writeType(tree.tpe) + + case Apply(receiver, method, args) => + writeByte(TagApply) + writeTree(receiver); writeIdent(method); writeTrees(args) + writeType(tree.tpe) + + case StaticApply(receiver, cls, method, args) => + writeByte(TagStaticApply) + writeTree(receiver); writeClassType(cls); writeIdent(method); writeTrees(args) + writeType(tree.tpe) + + case TraitImplApply(impl, method, args) => + writeByte(TagTraitImplApply) + writeClassType(impl); writeIdent(method); writeTrees(args) + writeType(tree.tpe) + + case UnaryOp(op, lhs) => + writeByte(TagUnaryOp) + writeByte(op); writeTree(lhs) + + case BinaryOp(op, lhs, rhs) => + writeByte(TagBinaryOp) + writeByte(op); writeTree(lhs); writeTree(rhs) + + case NewArray(tpe, lengths) => + writeByte(TagNewArray) + writeArrayType(tpe); writeTrees(lengths) + + case ArrayValue(tpe, elems) => + writeByte(TagArrayValue) + writeArrayType(tpe); writeTrees(elems) + + case ArrayLength(array) => + writeByte(TagArrayLength) + writeTree(array) + + case ArraySelect(array, index) => + writeByte(TagArraySelect) + writeTree(array); writeTree(index) + writeType(tree.tpe) + + case RecordValue(tpe, elems) => + writeByte(TagRecordValue) + writeType(tpe); writeTrees(elems) + + case IsInstanceOf(expr, cls) => + writeByte(TagIsInstanceOf) + writeTree(expr); writeReferenceType(cls) + + case AsInstanceOf(expr, cls) => + writeByte(TagAsInstanceOf) + writeTree(expr); writeReferenceType(cls) + + case Unbox(expr, charCode) => + writeByte(TagUnbox) + writeTree(expr); writeByte(charCode.toByte) + + case GetClass(expr) => + writeByte(TagGetClass) + writeTree(expr) + + case CallHelper(helper, args) => + writeByte(TagCallHelper) + writeString(helper); writeTrees(args) + writeType(tree.tpe) + + case JSNew(ctor, args) => + writeByte(TagJSNew) + writeTree(ctor); writeTrees(args) + + case JSDotSelect(qualifier, item) => + writeByte(TagJSDotSelect) + writeTree(qualifier); writeIdent(item) + + case JSBracketSelect(qualifier, item) => + writeByte(TagJSBracketSelect) + writeTree(qualifier); writeTree(item) + + case JSFunctionApply(fun, args) => + writeByte(TagJSFunctionApply) + writeTree(fun); writeTrees(args) + + case JSDotMethodApply(receiver, method, args) => + writeByte(TagJSDotMethodApply) + writeTree(receiver); writeIdent(method); writeTrees(args) + + case JSBracketMethodApply(receiver, method, args) => + writeByte(TagJSBracketMethodApply) + writeTree(receiver); writeTree(method); writeTrees(args) + + case JSDelete(prop) => + writeByte(TagJSDelete) + writeTree(prop) + + case JSUnaryOp(op, lhs) => + writeByte(TagJSUnaryOp) + writeString(op); writeTree(lhs) + + case JSBinaryOp(op, lhs, rhs) => + writeByte(TagJSBinaryOp) + writeString(op); writeTree(lhs); writeTree(rhs) + + case JSArrayConstr(items) => + writeByte(TagJSArrayConstr) + writeTrees(items) + + case JSObjectConstr(fields) => + writeByte(TagJSObjectConstr) + writeInt(fields.size) + fields foreach { field => + writePropertyName(field._1); writeTree(field._2) + } + + case JSEnvInfo() => + writeByte(TagJSEnvInfo) + + // Literals + + case Undefined() => + writeByte(TagUndefined) + + case UndefinedParam() => + writeByte(TagUndefinedParam) + writeType(tree.tpe) + + case Null() => + writeByte(TagNull) + + case BooleanLiteral(value) => + writeByte(TagBooleanLiteral) + writeBoolean(value) + + case IntLiteral(value) => + writeByte(TagIntLiteral) + writeInt(value) + + case LongLiteral(value) => + writeByte(TagLongLiteral) + writeLong(value) + + case FloatLiteral(value) => + writeByte(TagFloatLiteral) + writeFloat(value) + + case DoubleLiteral(value) => + writeByte(TagDoubleLiteral) + writeDouble(value) + + case StringLiteral(value) => + writeByte(TagStringLiteral) + writeString(value) + + case ClassOf(cls) => + writeByte(TagClassOf) + writeReferenceType(cls) + + case VarRef(ident, mutable) => + writeByte(TagVarRef) + writeIdent(ident); writeBoolean(mutable) + writeType(tree.tpe) + + case This() => + writeByte(TagThis) + writeType(tree.tpe) + + case Closure(captureParams, params, body, captureValues) => + writeByte(TagClosure) + writeTrees(captureParams) + writeTrees(params) + writeTree(body) + writeTrees(captureValues) + + case ClassDef(name, kind, parent, ancestors, defs) => + writeByte(TagClassDef) + writeIdent(name) + writeByte(ClassKind.toByte(kind)) + writeOptIdent(parent) + writeIdents(ancestors) + writeTrees(defs) + + case methodDef: MethodDef => + val MethodDef(name, args, resultType, body) = methodDef + + writeByte(TagMethodDef) + writeOptHash(methodDef.hash) + + // Prepare for back-jump and write dummy length + bufferUnderlying.markJump() + writeInt(-1) + + // Write out method def + writePropertyName(name); writeTrees(args); writeType(resultType); writeTree(body) + + // Jump back and write true length + val length = bufferUnderlying.jumpBack() + writeInt(length) + bufferUnderlying.continue() + + case PropertyDef(name, getter, arg, setter) => + writeByte(TagPropertyDef) + writePropertyName(name); writeTree(getter); writeTree(arg); writeTree(setter) + + case ConstructorExportDef(fullName, args, body) => + writeByte(TagConstructorExportDef) + writeString(fullName); writeTrees(args); writeTree(body) + + case ModuleExportDef(fullName) => + writeByte(TagModuleExportDef) + writeString(fullName) + } + if (UseDebugMagic) + writeInt(DebugMagic) + } + + def writeTrees(trees: List[Tree]): Unit = { + buffer.writeInt(trees.size) + trees.foreach(writeTree) + } + + def writeIdent(ident: Ident): Unit = { + writePosition(ident.pos) + writeString(ident.name); writeString(ident.originalName.getOrElse("")) + } + + def writeIdents(idents: List[Ident]): Unit = { + buffer.writeInt(idents.size) + idents.foreach(writeIdent) + } + + def writeOptIdent(optIdent: Option[Ident]): Unit = { + buffer.writeBoolean(optIdent.isDefined) + optIdent.foreach(writeIdent) + } + + def writeType(tpe: Type): Unit = { + tpe match { + case AnyType => buffer.write(TagAnyType) + case NothingType => buffer.write(TagNothingType) + case UndefType => buffer.write(TagUndefType) + case BooleanType => buffer.write(TagBooleanType) + case IntType => buffer.write(TagIntType) + case LongType => buffer.write(TagLongType) + case FloatType => buffer.write(TagFloatType) + case DoubleType => buffer.write(TagDoubleType) + case StringType => buffer.write(TagStringType) + case NullType => buffer.write(TagNullType) + case NoType => buffer.write(TagNoType) + + case tpe: ClassType => + buffer.write(TagClassType) + writeClassType(tpe) + + case tpe: ArrayType => + buffer.write(TagArrayType) + writeArrayType(tpe) + + case RecordType(fields) => + buffer.write(TagRecordType) + buffer.writeInt(fields.size) + for (RecordType.Field(name, originalName, tpe, mutable) <- fields) { + writeString(name) + writeString(originalName.getOrElse("")) + writeType(tpe) + buffer.writeBoolean(mutable) + } + } + } + + def writeClassType(tpe: ClassType): Unit = + writeString(tpe.className) + + def writeArrayType(tpe: ArrayType): Unit = { + writeString(tpe.baseClassName) + buffer.writeInt(tpe.dimensions) + } + + def writeReferenceType(tpe: ReferenceType): Unit = + writeType(tpe) + + def writePropertyName(name: PropertyName): Unit = { + name match { + case name: Ident => buffer.writeBoolean(true); writeIdent(name) + case name: StringLiteral => buffer.writeBoolean(false); writeTree(name) + } + } + + def writePosition(pos: Position): Unit = { + import buffer._ + import PositionFormat._ + + def writeFull(): Unit = { + writeByte(FormatFullMaskValue) + writeInt(fileToIndex(pos.source)) + writeInt(pos.line) + writeInt(pos.column) + } + + if (pos == Position.NoPosition) { + writeByte(FormatNoPositionValue) + } else if (lastPosition == Position.NoPosition || + pos.source != lastPosition.source) { + writeFull() + lastPosition = pos + } else { + val line = pos.line + val column = pos.column + val lineDiff = line - lastPosition.line + val columnDiff = column - lastPosition.column + val columnIsByte = column >= 0 && column < 256 + + if (lineDiff == 0 && columnDiff >= -64 && columnDiff < 64) { + writeByte((columnDiff << Format1Shift) | Format1MaskValue) + } else if (lineDiff >= -32 && lineDiff < 32 && columnIsByte) { + writeByte((lineDiff << Format2Shift) | Format2MaskValue) + writeByte(column) + } else if (lineDiff >= Short.MinValue && lineDiff <= Short.MaxValue && columnIsByte) { + writeByte(Format3MaskValue) + writeShort(lineDiff) + writeByte(column) + } else { + writeFull() + } + + lastPosition = pos + } + + if (UseDebugMagic) + writeInt(PosDebugMagic) + } + + def writeOptHash(optHash: Option[TreeHash]): Unit = { + buffer.writeBoolean(optHash.isDefined) + for (hash <- optHash) { + buffer.write(hash.treeHash) + buffer.write(hash.posHash) + } + } + + def writeString(s: String): Unit = + buffer.writeInt(stringToIndex(s)) + } + + private final class Deserializer(stream: InputStream, sourceVersion: String) { + private[this] val input = new DataInputStream(stream) + + private[this] val files = + Array.fill(input.readInt())(new URI(input.readUTF())) + + private[this] val strings = + Array.fill(input.readInt())(input.readUTF()) + + private[this] var lastPosition: Position = Position.NoPosition + + def deserialize(): Tree = { + readTree() + } + + def readTree(): Tree = { + import input._ + implicit val pos = readPosition() + val tag = readByte() + val result = (tag: @switch) match { + case TagEmptyTree => EmptyTree + + case TagVarDef => VarDef(readIdent(), readType(), readBoolean(), readTree()) + case TagParamDef => ParamDef(readIdent(), readType(), readBoolean()) + + case TagSkip => Skip() + case TagBlock => Block(readTrees()) + case TagLabeled => Labeled(readIdent(), readType(), readTree()) + case TagAssign => Assign(readTree(), readTree()) + case TagReturn => Return(readTree(), readOptIdent()) + case TagIf => If(readTree(), readTree(), readTree())(readType()) + case TagWhile => While(readTree(), readTree(), readOptIdent()) + case TagDoWhile => DoWhile(readTree(), readTree(), readOptIdent()) + case TagTry => Try(readTree(), readIdent(), readTree(), readTree())(readType()) + case TagThrow => Throw(readTree()) + case TagContinue => Continue(readOptIdent()) + case TagMatch => + Match(readTree(), List.fill(readInt()) { + (readTrees().map(_.asInstanceOf[Literal]), readTree()) + }, readTree())(readType()) + case TagDebugger => Debugger() + + case TagNew => New(readClassType(), readIdent(), readTrees()) + case TagLoadModule => LoadModule(readClassType()) + case TagStoreModule => StoreModule(readClassType(), readTree()) + case TagSelect => Select(readTree(), readIdent(), readBoolean())(readType()) + case TagApply => Apply(readTree(), readIdent(), readTrees())(readType()) + case TagStaticApply => StaticApply(readTree(), readClassType(), readIdent(), readTrees())(readType()) + case TagTraitImplApply => TraitImplApply(readClassType(), readIdent(), readTrees())(readType()) + case TagUnaryOp => UnaryOp(readByte(), readTree()) + case TagBinaryOp => BinaryOp(readByte(), readTree(), readTree()) + case TagNewArray => NewArray(readArrayType(), readTrees()) + case TagArrayValue => ArrayValue(readArrayType(), readTrees()) + case TagArrayLength => ArrayLength(readTree()) + case TagArraySelect => ArraySelect(readTree(), readTree())(readType()) + case TagRecordValue => RecordValue(readType().asInstanceOf[RecordType], readTrees()) + case TagIsInstanceOf => IsInstanceOf(readTree(), readReferenceType()) + case TagAsInstanceOf => AsInstanceOf(readTree(), readReferenceType()) + case TagUnbox => Unbox(readTree(), readByte().toChar) + case TagGetClass => GetClass(readTree()) + case TagCallHelper => CallHelper(readString(), readTrees())(readType()) + + case TagJSNew => JSNew(readTree(), readTrees()) + case TagJSDotSelect => JSDotSelect(readTree(), readIdent()) + case TagJSBracketSelect => JSBracketSelect(readTree(), readTree()) + case TagJSFunctionApply => JSFunctionApply(readTree(), readTrees()) + case TagJSDotMethodApply => JSDotMethodApply(readTree(), readIdent(), readTrees()) + case TagJSBracketMethodApply => JSBracketMethodApply(readTree(), readTree(), readTrees()) + case TagJSDelete => JSDelete(readTree()) + case TagJSUnaryOp => JSUnaryOp(readString(), readTree()) + case TagJSBinaryOp => JSBinaryOp(readString(), readTree(), readTree()) + case TagJSArrayConstr => JSArrayConstr(readTrees()) + case TagJSObjectConstr => + JSObjectConstr(List.fill(readInt())((readPropertyName(), readTree()))) + case TagJSEnvInfo => JSEnvInfo() + + case TagUndefined => Undefined() + case TagUndefinedParam => UndefinedParam()(readType()) + case TagNull => Null() + case TagBooleanLiteral => BooleanLiteral(readBoolean()) + case TagIntLiteral => IntLiteral(readInt()) + case TagLongLiteral => LongLiteral(readLong()) + case TagFloatLiteral => FloatLiteral(readFloat()) + case TagDoubleLiteral => DoubleLiteral(readDouble()) + case TagStringLiteral => StringLiteral(readString()) + case TagClassOf => ClassOf(readReferenceType()) + + case TagVarRef => VarRef(readIdent(), readBoolean())(readType()) + case TagThis => This()(readType()) + case TagClosure => + Closure(readParamDefs(), readParamDefs(), readTree(), readTrees()) + + case TagClassDef => + val name = readIdent() + val kind = ClassKind.fromByte(readByte()) + val parent = readOptIdent() + val ancestors = readIdents() + val defs = readTrees() + ClassDef(name, kind, parent, ancestors, defs) + + case TagMethodDef => + val optHash = readOptHash() + // read and discard the length + val len = readInt() + assert(len >= 0) + MethodDef(readPropertyName(), readParamDefs(), readType(), readTree())(optHash) + case TagPropertyDef => + PropertyDef(readPropertyName(), readTree(), + readTree().asInstanceOf[ParamDef], readTree()) + case TagConstructorExportDef => + ConstructorExportDef(readString(), readParamDefs(), readTree()) + case TagModuleExportDef => + ModuleExportDef(readString()) + } + if (UseDebugMagic) { + val magic = readInt() + assert(magic == DebugMagic, + s"Bad magic after reading a ${result.getClass}!") + } + result + } + + def readTrees(): List[Tree] = + List.fill(input.readInt())(readTree()) + + def readParamDefs(): List[ParamDef] = + readTrees().map(_.asInstanceOf[ParamDef]) + + def readIdent(): Ident = { + implicit val pos = readPosition() + val name = readString() + val originalName = readString() + Ident(name, if (originalName.isEmpty) None else Some(originalName)) + } + + def readIdents(): List[Ident] = + List.fill(input.readInt())(readIdent()) + + def readOptIdent(): Option[Ident] = { + if (input.readBoolean()) Some(readIdent()) + else None + } + + def readType(): Type = { + val tag = input.readByte() + (tag: @switch) match { + case TagAnyType => AnyType + case TagNothingType => NothingType + case TagUndefType => UndefType + case TagBooleanType => BooleanType + case TagIntType => IntType + case TagLongType => LongType + case TagFloatType => FloatType + case TagDoubleType => DoubleType + case TagStringType => StringType + case TagNullType => NullType + case TagNoType => NoType + + case TagClassType => readClassType() + case TagArrayType => readArrayType() + + case TagRecordType => + RecordType(List.fill(input.readInt()) { + val name = readString() + val originalName = readString() + val tpe = readType() + val mutable = input.readBoolean() + RecordType.Field(name, + if (originalName.isEmpty) None else Some(originalName), + tpe, mutable) + }) + } + } + + def readClassType(): ClassType = + ClassType(readString()) + + def readArrayType(): ArrayType = + ArrayType(readString(), input.readInt()) + + def readReferenceType(): ReferenceType = + readType().asInstanceOf[ReferenceType] + + def readPropertyName(): PropertyName = { + if (input.readBoolean()) readIdent() + else readTree().asInstanceOf[StringLiteral] + } + + def readPosition(): Position = { + import input._ + import PositionFormat._ + + val first = readByte() + + val result = if (first == FormatNoPositionValue) { + Position.NoPosition + } else { + val result = if ((first & FormatFullMask) == FormatFullMaskValue) { + val file = files(readInt()) + val line = readInt() + val column = readInt() + Position(file, line, column) + } else { + assert(lastPosition != NoPosition, + "Position format error: first position must be full") + if ((first & Format1Mask) == Format1MaskValue) { + val columnDiff = first >> Format1Shift + Position(lastPosition.source, lastPosition.line, + lastPosition.column + columnDiff) + } else if ((first & Format2Mask) == Format2MaskValue) { + val lineDiff = first >> Format2Shift + val column = readByte() & 0xff // unsigned + Position(lastPosition.source, + lastPosition.line + lineDiff, column) + } else { + assert((first & Format3Mask) == Format3MaskValue, + s"Position format error: first byte $first does not match any format") + val lineDiff = readShort() + val column = readByte() & 0xff // unsigned + Position(lastPosition.source, + lastPosition.line + lineDiff, column) + } + } + lastPosition = result + result + } + + if (UseDebugMagic) { + val magic = readInt() + assert(magic == PosDebugMagic, + s"Bad magic after reading position with first byte $first") + } + + result + } + + def readOptHash(): Option[TreeHash] = { + if (input.readBoolean()) { + val treeHash = new Array[Byte](20) + val posHash = new Array[Byte](20) + input.readFully(treeHash) + input.readFully(posHash) + Some(new TreeHash(treeHash, posHash)) + } else None + } + + def readString(): String = { + strings(input.readInt()) + } + } +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Tags.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Tags.scala new file mode 100644 index 0000000..a03926c --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Tags.scala @@ -0,0 +1,107 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +/** Serialization and hashing tags for trees and types */ +private[ir] object Tags { + + // Tags for Trees + + final val TagEmptyTree = 1 + + final val TagVarDef = TagEmptyTree + 1 + final val TagParamDef = TagVarDef + 1 + + final val TagSkip = TagParamDef + 1 + final val TagBlock = TagSkip + 1 + final val TagLabeled = TagBlock + 1 + final val TagAssign = TagLabeled + 1 + final val TagReturn = TagAssign + 1 + final val TagIf = TagReturn + 1 + final val TagWhile = TagIf + 1 + final val TagDoWhile = TagWhile + 1 + final val TagTry = TagDoWhile + 1 + final val TagThrow = TagTry + 1 + final val TagContinue = TagThrow + 1 + final val TagMatch = TagContinue + 1 + final val TagDebugger = TagMatch + 1 + + final val TagNew = TagDebugger + 1 + final val TagLoadModule = TagNew + 1 + final val TagStoreModule = TagLoadModule + 1 + final val TagSelect = TagStoreModule + 1 + final val TagApply = TagSelect + 1 + final val TagStaticApply = TagApply + 1 + final val TagTraitImplApply = TagStaticApply + 1 + final val TagUnaryOp = TagTraitImplApply + 1 + final val TagBinaryOp = TagUnaryOp + 1 + final val TagNewArray = TagBinaryOp + 1 + final val TagArrayValue = TagNewArray + 1 + final val TagArrayLength = TagArrayValue + 1 + final val TagArraySelect = TagArrayLength + 1 + final val TagRecordValue = TagArraySelect + 1 + final val TagIsInstanceOf = TagRecordValue + 1 + final val TagAsInstanceOf = TagIsInstanceOf + 1 + final val TagUnbox = TagAsInstanceOf + 1 + final val TagGetClass = TagUnbox + 1 + final val TagCallHelper = TagGetClass + 1 + + final val TagJSNew = TagCallHelper + 1 + final val TagJSDotSelect = TagJSNew + 1 + final val TagJSBracketSelect = TagJSDotSelect + 1 + final val TagJSFunctionApply = TagJSBracketSelect + 1 + final val TagJSDotMethodApply = TagJSFunctionApply + 1 + final val TagJSBracketMethodApply = TagJSDotMethodApply + 1 + final val TagJSDelete = TagJSBracketMethodApply + 1 + final val TagJSUnaryOp = TagJSDelete + 1 + final val TagJSBinaryOp = TagJSUnaryOp + 1 + final val TagJSArrayConstr = TagJSBinaryOp + 1 + final val TagJSObjectConstr = TagJSArrayConstr + 1 + final val TagJSEnvInfo = TagJSObjectConstr + 1 + + final val TagUndefined = TagJSEnvInfo + 1 + final val TagUndefinedParam = TagUndefined + 1 + final val TagNull = TagUndefinedParam + 1 + final val TagBooleanLiteral = TagNull + 1 + final val TagIntLiteral = TagBooleanLiteral + 1 + final val TagLongLiteral = TagIntLiteral + 1 + final val TagFloatLiteral = TagLongLiteral + 1 + final val TagDoubleLiteral = TagFloatLiteral + 1 + final val TagStringLiteral = TagDoubleLiteral + 1 + final val TagClassOf = TagStringLiteral + 1 + + final val TagVarRef = TagClassOf + 1 + final val TagThis = TagVarRef + 1 + final val TagClosure = TagThis + 1 + + final val TagClassDef = TagClosure + 1 + final val TagMethodDef = TagClassDef + 1 + final val TagPropertyDef = TagMethodDef + 1 + final val TagConstructorExportDef = TagPropertyDef + 1 + final val TagModuleExportDef = TagConstructorExportDef + 1 + + // Tags for Types + + final val TagAnyType = 1 + final val TagNothingType = TagAnyType + 1 + final val TagUndefType = TagNothingType + 1 + final val TagBooleanType = TagUndefType + 1 + final val TagIntType = TagBooleanType + 1 + final val TagLongType = TagIntType + 1 + final val TagFloatType = TagLongType + 1 + final val TagDoubleType = TagFloatType + 1 + final val TagStringType = TagDoubleType + 1 + final val TagNullType = TagStringType + 1 + final val TagClassType = TagNullType + 1 + final val TagArrayType = TagClassType + 1 + final val TagRecordType = TagArrayType + 1 + final val TagNoType = TagRecordType + 1 + +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Transformers.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Transformers.scala new file mode 100644 index 0000000..5e4f40c --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Transformers.scala @@ -0,0 +1,218 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +import Trees._ + +object Transformers { + + abstract class Transformer { + final def transformStat(tree: Tree): Tree = + transform(tree, isStat = true) + + final def transformExpr(tree: Tree): Tree = + transform(tree, isStat = false) + + def transform(tree: Tree, isStat: Boolean): Tree = { + implicit val pos = tree.pos + + tree match { + // Definitions + + case VarDef(ident, vtpe, mutable, rhs) => + VarDef(ident, vtpe, mutable, transformExpr(rhs)) + + // Control flow constructs + + case Block(stats :+ expr) => + Block(stats.map(transformStat) :+ transform(expr, isStat)) + + case Labeled(label, tpe, body) => + Labeled(label, tpe, transform(body, isStat)) + + case Assign(lhs, rhs) => + Assign(transformExpr(lhs), transformExpr(rhs)) + + case Return(expr, label) => + Return(transformExpr(expr), label) + + case If(cond, thenp, elsep) => + If(transformExpr(cond), transform(thenp, isStat), + transform(elsep, isStat))(tree.tpe) + + case While(cond, body, label) => + While(transformExpr(cond), transformStat(body), label) + + case DoWhile(body, cond, label) => + DoWhile(transformStat(body), transformExpr(cond), label) + + case Try(block, errVar, handler, finalizer) => + Try(transform(block, isStat), errVar, transform(handler, isStat), + transformStat(finalizer))(tree.tpe) + + case Throw(expr) => + Throw(transformExpr(expr)) + + case Match(selector, cases, default) => + Match(transformExpr(selector), + cases map (c => (c._1, transform(c._2, isStat))), + transform(default, isStat))(tree.tpe) + + // Scala expressions + + case New(cls, ctor, args) => + New(cls, ctor, args map transformExpr) + + case StoreModule(cls, value) => + StoreModule(cls, transformExpr(value)) + + case Select(qualifier, item, mutable) => + Select(transformExpr(qualifier), item, mutable)(tree.tpe) + + case Apply(receiver, method, args) => + Apply(transformExpr(receiver), method, + args map transformExpr)(tree.tpe) + + case StaticApply(receiver, cls, method, args) => + StaticApply(transformExpr(receiver), cls, method, + args map transformExpr)(tree.tpe) + + case TraitImplApply(impl, method, args) => + TraitImplApply(impl, method, args map transformExpr)(tree.tpe) + + case UnaryOp(op, lhs) => + UnaryOp(op, transformExpr(lhs)) + + case BinaryOp(op, lhs, rhs) => + BinaryOp(op, transformExpr(lhs), transformExpr(rhs)) + + case NewArray(tpe, lengths) => + NewArray(tpe, lengths map transformExpr) + + case ArrayValue(tpe, elems) => + ArrayValue(tpe, elems map transformExpr) + + case ArrayLength(array) => + ArrayLength(transformExpr(array)) + + case ArraySelect(array, index) => + ArraySelect(transformExpr(array), transformExpr(index))(tree.tpe) + + case RecordValue(tpe, elems) => + RecordValue(tpe, elems map transformExpr) + + case IsInstanceOf(expr, cls) => + IsInstanceOf(transformExpr(expr), cls) + + case AsInstanceOf(expr, cls) => + AsInstanceOf(transformExpr(expr), cls) + + case Unbox(expr, charCode) => + Unbox(transformExpr(expr), charCode) + + case GetClass(expr) => + GetClass(transformExpr(expr)) + + case CallHelper(helper, args) => + CallHelper(helper, args map transformExpr)(tree.tpe) + + // JavaScript expressions + + case JSNew(ctor, args) => + JSNew(transformExpr(ctor), args map transformExpr) + + case JSDotSelect(qualifier, item) => + JSDotSelect(transformExpr(qualifier), item) + + case JSBracketSelect(qualifier, item) => + JSBracketSelect(transformExpr(qualifier), transformExpr(item)) + + case JSFunctionApply(fun, args) => + JSFunctionApply(transformExpr(fun), args map transformExpr) + + case JSDotMethodApply(receiver, method, args) => + JSDotMethodApply(transformExpr(receiver), method, + args map transformExpr) + + case JSBracketMethodApply(receiver, method, args) => + JSBracketMethodApply(transformExpr(receiver), transformExpr(method), + args map transformExpr) + + case JSDelete(prop) => + JSDelete(transformExpr(prop)) + + case JSUnaryOp(op, lhs) => + JSUnaryOp(op, transformExpr(lhs)) + + case JSBinaryOp(op, lhs, rhs) => + JSBinaryOp(op, transformExpr(lhs), transformExpr(rhs)) + + case JSArrayConstr(items) => + JSArrayConstr(items map transformExpr) + + case JSObjectConstr(fields) => + JSObjectConstr(fields map { + case (name, value) => (name, transformExpr(value)) + }) + + // Atomic expressions + + case Closure(captureParams, params, body, captureValues) => + Closure(captureParams, params, transformExpr(body), + captureValues.map(transformExpr)) + + // Trees that need not be transformed + + case _:Skip | _:Continue | _:LoadModule | _:JSEnvInfo | + _:Literal | _:VarRef | _:This | EmptyTree => + tree + + case _ => + sys.error(s"Invalid tree in transform() of class ${tree.getClass}") + } + } + } + + abstract class ClassTransformer extends Transformer { + def transformClassDef(tree: ClassDef): ClassDef = { + val ClassDef(name, kind, parent, ancestors, defs) = tree + ClassDef(name, kind, parent, ancestors, defs.map(transformDef))(tree.pos) + } + + def transformDef(tree: Tree): Tree = { + implicit val pos = tree.pos + + tree match { + case VarDef(name, vtpe, mutable, rhs) => + VarDef(name, vtpe, mutable, transformExpr(rhs)) + + case MethodDef(name, args, resultType, body) => + MethodDef(name, args, resultType, transformStat(body))(None) + + case PropertyDef(name, getterBody, setterArg, setterBody) => + PropertyDef( + name, + transformStat(getterBody), + setterArg, + transformStat(setterBody)) + + case ConstructorExportDef(fullName, args, body) => + ConstructorExportDef(fullName, args, transformStat(body)) + + case ModuleExportDef(_) => + tree + + case _ => + sys.error(s"Invalid tree in transformDef() of class ${tree.getClass}") + } + } + } + +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Traversers.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Traversers.scala new file mode 100644 index 0000000..1b77e5e --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Traversers.scala @@ -0,0 +1,197 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +import Trees._ + +object Traversers { + + class Traverser { + def traverse(tree: Tree): Unit = tree match { + // Definitions + + case VarDef(ident, vtpe, mutable, rhs) => + traverse(rhs) + + // Control flow constructs + + case Block(stats) => + stats foreach traverse + + case Labeled(label, tpe, body) => + traverse(body) + + case Assign(lhs, rhs) => + traverse(lhs) + traverse(rhs) + + case Return(expr, label) => + traverse(expr) + + case If(cond, thenp, elsep) => + traverse(cond) + traverse(thenp) + traverse(elsep) + + case While(cond, body, label) => + traverse(cond) + traverse(body) + + case DoWhile(body, cond, label) => + traverse(body) + traverse(cond) + + case Try(block, errVar, handler, finalizer) => + traverse(block) + traverse(handler) + traverse(finalizer) + + case Throw(expr) => + traverse(expr) + + case Match(selector, cases, default) => + traverse(selector) + cases foreach (c => (c._1 map traverse, traverse(c._2))) + traverse(default) + + // Scala expressions + + case New(cls, ctor, args) => + args foreach traverse + + case StoreModule(cls, value) => + traverse(value) + + case Select(qualifier, item, mutable) => + traverse(qualifier) + + case Apply(receiver, method, args) => + traverse(receiver) + args foreach traverse + + case StaticApply(receiver, cls, method, args) => + traverse(receiver) + args foreach traverse + + case TraitImplApply(impl, method, args) => + args foreach traverse + + case UnaryOp(op, lhs) => + traverse(lhs) + + case BinaryOp(op, lhs, rhs) => + traverse(lhs) + traverse(rhs) + + case NewArray(tpe, lengths) => + lengths foreach traverse + + case ArrayValue(tpe, elems) => + elems foreach traverse + + case ArrayLength(array) => + traverse(array) + + case ArraySelect(array, index) => + traverse(array) + traverse(index) + + case RecordValue(tpe, elems) => + elems foreach traverse + + case IsInstanceOf(expr, cls) => + traverse(expr) + + case AsInstanceOf(expr, cls) => + traverse(expr) + + case Unbox(expr, charCode) => + traverse(expr) + + case GetClass(expr) => + traverse(expr) + + case CallHelper(helper, args) => + args foreach traverse + + // JavaScript expressions + + case JSNew(ctor, args) => + traverse(ctor) + args foreach traverse + + case JSDotSelect(qualifier, item) => + traverse(qualifier) + + case JSBracketSelect(qualifier, item) => + traverse(qualifier) + traverse(item) + + case JSFunctionApply(fun, args) => + traverse(fun) + args foreach traverse + + case JSDotMethodApply(receiver, method, args) => + traverse(receiver) + args foreach traverse + + case JSBracketMethodApply(receiver, method, args) => + traverse(receiver) + traverse(method) + args foreach traverse + + case JSDelete(prop) => + traverse(prop) + + case JSUnaryOp(op, lhs) => + traverse(lhs) + + case JSBinaryOp(op, lhs, rhs) => + traverse(lhs) + traverse(rhs) + + case JSArrayConstr(items) => + items foreach traverse + + case JSObjectConstr(fields) => + fields foreach { f => traverse(f._2) } + + // Atomic expressions + + case Closure(captureParams, params, body, captureValues) => + traverse(body) + captureValues.foreach(traverse) + + // Classes + + case ClassDef(name, kind, parent, ancestors, defs) => + defs foreach traverse + + case MethodDef(name, args, resultType, body) => + traverse(body) + + case PropertyDef(name, getterBody, setterArg, setterBody) => + traverse(getterBody) + traverse(setterBody) + + case ConstructorExportDef(fullName, args, body) => + traverse(body) + + // Trees that need not be traversed + + case _:Skip | _:Continue | _:LoadModule | _:JSEnvInfo | + _:Literal | _:VarRef | _:This | _:ModuleExportDef | EmptyTree => + + case _ => + sys.error(s"Invalid tree in traverse() of class ${tree.getClass}") + } + } + +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Trees.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Trees.scala new file mode 100644 index 0000000..4f58ece --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Trees.scala @@ -0,0 +1,536 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +import scala.annotation.switch + +import Position.NoPosition +import Types._ + +object Trees { + /** AST node of the IR. */ + abstract sealed class Tree { + val pos: Position + val tpe: Type + + def show: String = { + val writer = new java.io.StringWriter + val printer = new Printers.IRTreePrinter(writer) + printer.printTree(this) + writer.toString() + } + } + + case object EmptyTree extends Tree { + val pos = NoPosition + val tpe = NoType + } + + // Identifiers and properties + + sealed trait PropertyName { + def name: String + def pos: Position + } + + case class Ident(name: String, originalName: Option[String])( + implicit val pos: Position) extends PropertyName { + requireValidIdent(name) + } + + object Ident { + def apply(name: String)(implicit pos: Position): Ident = + new Ident(name, Some(name)) + } + + final def isValidIdentifier(name: String): Boolean = { + val c = name.head + (c == '$' || c == '_' || c.isUnicodeIdentifierStart) && + name.tail.forall(c => (c == '$') || c.isUnicodeIdentifierPart) && + !isKeyword(name) + } + + @inline final def requireValidIdent(name: String) { + require(isValidIdentifier(name), s"${name} is not a valid identifier") + } + + final val isKeyword: Set[String] = Set( + // Value keywords + "true", "false", "null", "undefined", + + // Current JavaScript keywords + "break", "case", "catch", "continue", "debugger", "default", "delete", + "do", "else", "finally", "for", "function", "if", "in", "instanceof", + "new", "return", "switch", "this", "throw", "try", "typeof", "var", + "void", "while", "with", + + // Future reserved keywords + "class", "const", "enum", "export", "extends", "import", "super", + + // Future reserved keywords in Strict mode + "implements", "interface", "let", "package", "private", "protected", + "public", "static", "yield", + + // Other reserved keywords found on the Web but not in the spec + "abstract", "boolean", "byte", "char", "double", "final", "float", + "goto", "int", "long", "native", "short", "synchronized", "throws", + "transient", "volatile" + ) + + // Definitions + + case class VarDef(name: Ident, vtpe: Type, mutable: Boolean, rhs: Tree)(implicit val pos: Position) extends Tree { + val tpe = NoType // cannot be in expression position + + def ref(implicit pos: Position): VarRef = + VarRef(name, mutable = mutable)(vtpe) + } + + case class ParamDef(name: Ident, ptpe: Type, mutable: Boolean)(implicit val pos: Position) extends Tree { + val tpe = NoType + + def ref(implicit pos: Position): VarRef = + VarRef(name, mutable = mutable)(ptpe) + } + + // Control flow constructs + + case class Skip()(implicit val pos: Position) extends Tree { + val tpe = NoType // cannot be in expression position + } + + class Block private (val stats: List[Tree])(implicit val pos: Position) extends Tree { + val tpe = stats.last.tpe + + override def toString(): String = + stats.mkString("Block(", ",", ")") + } + + object Block { + def apply(stats: List[Tree])(implicit pos: Position): Tree = { + val flattenedStats = stats flatMap { + case Skip() => Nil + case Block(subStats) => subStats + case other => other :: Nil + } + flattenedStats match { + case Nil => Skip() + case only :: Nil => only + case _ => new Block(flattenedStats) + } + } + + def apply(stats: Tree*)(implicit pos: Position): Tree = + apply(stats.toList) + + def unapply(block: Block): Some[List[Tree]] = Some(block.stats) + } + + case class Labeled(label: Ident, tpe: Type, body: Tree)(implicit val pos: Position) extends Tree + + case class Assign(lhs: Tree, rhs: Tree)(implicit val pos: Position) extends Tree { + require(lhs match { + case _:VarRef | _:Select | _:ArraySelect | + _:JSDotSelect | _:JSBracketSelect => true + case _ => false + }, s"Invalid lhs for Assign: $lhs") + + val tpe = NoType // cannot be in expression position + } + + case class Return(expr: Tree, label: Option[Ident] = None)(implicit val pos: Position) extends Tree { + val tpe = NothingType + } + + case class If(cond: Tree, thenp: Tree, elsep: Tree)(val tpe: Type)(implicit val pos: Position) extends Tree + + case class While(cond: Tree, body: Tree, label: Option[Ident] = None)(implicit val pos: Position) extends Tree { + // cannot be in expression position, unless it is infinite + val tpe = cond match { + case BooleanLiteral(true) => NothingType + case _ => NoType + } + } + + case class DoWhile(body: Tree, cond: Tree, label: Option[Ident] = None)(implicit val pos: Position) extends Tree { + val tpe = NoType // cannot be in expression position + } + + case class Try(block: Tree, errVar: Ident, handler: Tree, finalizer: Tree)(val tpe: Type)(implicit val pos: Position) extends Tree + + case class Throw(expr: Tree)(implicit val pos: Position) extends Tree { + val tpe = NothingType + } + + case class Continue(label: Option[Ident] = None)(implicit val pos: Position) extends Tree { + val tpe = NothingType + } + + /** A break-free switch (without fallthrough behavior). + * Unlike a JavaScript switch, it can be used in expression position. + * It supports alternatives explicitly (hence the List[Tree] in cases), + * whereas in a switch one would use the fallthrough behavior to + * implement alternatives. + * (This is not a pattern matching construct like in Scala.) + */ + case class Match(selector: Tree, cases: List[(List[Literal], Tree)], default: Tree)(val tpe: Type)(implicit val pos: Position) extends Tree + + case class Debugger()(implicit val pos: Position) extends Tree { + val tpe = NoType // cannot be in expression position + } + + // Scala expressions + + case class New(cls: ClassType, ctor: Ident, args: List[Tree])(implicit val pos: Position) extends Tree { + val tpe = cls + } + + case class LoadModule(cls: ClassType)(implicit val pos: Position) extends Tree { + val tpe = cls + } + + case class StoreModule(cls: ClassType, value: Tree)(implicit val pos: Position) extends Tree { + val tpe = NoType // cannot be in expression position + } + + case class Select(qualifier: Tree, item: Ident, mutable: Boolean)(val tpe: Type)(implicit val pos: Position) extends Tree + + case class Apply(receiver: Tree, method: Ident, args: List[Tree])(val tpe: Type)(implicit val pos: Position) extends Tree + + case class StaticApply(receiver: Tree, cls: ClassType, method: Ident, args: List[Tree])(val tpe: Type)(implicit val pos: Position) extends Tree + + case class TraitImplApply(impl: ClassType, method: Ident, args: List[Tree])(val tpe: Type)(implicit val pos: Position) extends Tree + + /** Unary operation (always preserves pureness). */ + case class UnaryOp(op: UnaryOp.Code, lhs: Tree)(implicit val pos: Position) extends Tree { + import UnaryOp._ + val tpe = (op: @switch) match { + case `typeof` => StringType + case LongToInt | DoubleToInt => IntType + case IntToLong | DoubleToLong => LongType + case DoubleToFloat => FloatType + case LongToDouble => DoubleType + case Boolean_! => BooleanType + } + } + + object UnaryOp { + /** Codes are raw Ints to be able to write switch matches on them. */ + type Code = Int + + final val typeof = 1 + + final val Boolean_! = 2 + + final val IntToLong = 3 + final val LongToInt = 4 + final val LongToDouble = 5 + final val DoubleToInt = 6 + final val DoubleToFloat = 7 + final val DoubleToLong = 8 + } + + /** Binary operation (always preserves pureness). */ + case class BinaryOp(op: BinaryOp.Code, lhs: Tree, rhs: Tree)(implicit val pos: Position) extends Tree { + import BinaryOp._ + val tpe = (op: @switch) match { + case === | !== | + `in` | `instanceof` | + Num_== | Num_!= | Num_< | Num_<= | Num_> | Num_>= | + Long_== | Long_!= | Long_< | Long_<= | Long_> | Long_>= | + Boolean_== | Boolean_!= | Boolean_| | Boolean_& => + BooleanType + case String_+ => + StringType + case Int_+ | Int_- | Int_* | Int_/ | Int_% | + Int_| | Int_& | Int_^ | Int_<< | Int_>>> | Int_>> => + IntType + case Float_+ | Float_- | Float_* | Float_/ | Float_% => + FloatType + case Double_+ | Double_- | Double_* | Double_/ | Double_% => + DoubleType + case Long_+ | Long_- | Long_* | Long_/ | Long_% | + Long_| | Long_& | Long_^ | Long_<< | Long_>>> | Long_>> => + LongType + } + } + + object BinaryOp { + /** Codes are raw Ints to be able to write switch matches on them. */ + type Code = Int + + final val === = 1 + final val !== = 2 + + final val String_+ = 3 + + final val in = 4 + final val instanceof = 5 + + final val Int_+ = 6 + final val Int_- = 7 + final val Int_* = 8 + final val Int_/ = 9 + final val Int_% = 10 + + final val Int_| = 11 + final val Int_& = 12 + final val Int_^ = 13 + final val Int_<< = 14 + final val Int_>>> = 15 + final val Int_>> = 16 + + final val Float_+ = 17 + final val Float_- = 18 + final val Float_* = 19 + final val Float_/ = 20 + final val Float_% = 21 + + final val Double_+ = 22 + final val Double_- = 23 + final val Double_* = 24 + final val Double_/ = 25 + final val Double_% = 26 + + final val Num_== = 27 + final val Num_!= = 28 + final val Num_< = 29 + final val Num_<= = 30 + final val Num_> = 31 + final val Num_>= = 32 + + final val Long_+ = 33 + final val Long_- = 34 + final val Long_* = 35 + final val Long_/ = 36 + final val Long_% = 37 + + final val Long_| = 38 + final val Long_& = 39 + final val Long_^ = 40 + final val Long_<< = 41 + final val Long_>>> = 42 + final val Long_>> = 43 + + final val Long_== = 44 + final val Long_!= = 45 + final val Long_< = 46 + final val Long_<= = 47 + final val Long_> = 48 + final val Long_>= = 49 + + final val Boolean_== = 50 + final val Boolean_!= = 51 + final val Boolean_| = 52 + final val Boolean_& = 53 + } + + case class NewArray(tpe: ArrayType, lengths: List[Tree])(implicit val pos: Position) extends Tree { + require(lengths.nonEmpty && lengths.size <= tpe.dimensions) + } + + case class ArrayValue(tpe: ArrayType, elems: List[Tree])(implicit val pos: Position) extends Tree + + case class ArrayLength(array: Tree)(implicit val pos: Position) extends Tree { + val tpe = IntType + } + + case class ArraySelect(array: Tree, index: Tree)(val tpe: Type)(implicit val pos: Position) extends Tree + + case class RecordValue(tpe: RecordType, elems: List[Tree])(implicit val pos: Position) extends Tree + + case class IsInstanceOf(expr: Tree, cls: ReferenceType)(implicit val pos: Position) extends Tree { + val tpe = BooleanType + } + + case class AsInstanceOf(expr: Tree, cls: ReferenceType)(implicit val pos: Position) extends Tree { + val tpe = cls match { + case ClassType(Definitions.RuntimeNullClass) => NullType + case ClassType(Definitions.RuntimeNothingClass) => NothingType + case _ => cls + } + } + + case class Unbox(expr: Tree, charCode: Char)(implicit val pos: Position) extends Tree { + val tpe = (charCode: @switch) match { + case 'Z' => BooleanType + case 'B' | 'S' | 'I' => IntType + case 'J' => LongType + case 'F' => FloatType + case 'D' => DoubleType + } + } + + case class GetClass(expr: Tree)(implicit val pos: Position) extends Tree { + val tpe = ClassType(Definitions.ClassClass) + } + + case class CallHelper(helper: String, args: List[Tree])(val tpe: Type)(implicit val pos: Position) extends Tree + + object CallHelper { + def apply(helper: String, args: Tree*)(tpe: Type)( + implicit pos: Position): CallHelper = { + CallHelper(helper, args.toList)(tpe) + } + } + + // JavaScript expressions + + case class JSNew(ctor: Tree, args: List[Tree])(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + case class JSDotSelect(qualifier: Tree, item: Ident)(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + case class JSBracketSelect(qualifier: Tree, item: Tree)(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + case class JSFunctionApply(fun: Tree, args: List[Tree])(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + case class JSDotMethodApply(receiver: Tree, method: Ident, args: List[Tree])(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + case class JSBracketMethodApply(receiver: Tree, method: Tree, args: List[Tree])(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + case class JSDelete(prop: Tree)(implicit val pos: Position) extends Tree { + require(prop match { + case _:JSDotSelect | _:JSBracketSelect => true + case _ => false + }, s"Invalid prop for JSDelete: $prop") + + val tpe = NoType // cannot be in expression position + } + + /** Unary operation (always preserves pureness). + * + * Operations which do not preserve pureness are not allowed in this tree. + * These are notably ++ and -- + */ + case class JSUnaryOp(op: String, lhs: Tree)(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + /** Binary operation (always preserves pureness). + * + * Operations which do not preserve pureness are not allowed in this tree. + * These are notably +=, -=, *=, /= and %= + */ + case class JSBinaryOp(op: String, lhs: Tree, rhs: Tree)(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + case class JSArrayConstr(items: List[Tree])(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + case class JSObjectConstr(fields: List[(PropertyName, Tree)])(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + case class JSEnvInfo()(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + // Literals + + /** Marker for literals. Literals are always pure. */ + sealed trait Literal extends Tree + + case class Undefined()(implicit val pos: Position) extends Literal { + val tpe = UndefType + } + + case class UndefinedParam()(val tpe: Type)(implicit val pos: Position) extends Literal + + case class Null()(implicit val pos: Position) extends Literal { + val tpe = NullType + } + + case class BooleanLiteral(value: Boolean)(implicit val pos: Position) extends Literal { + val tpe = BooleanType + } + + case class IntLiteral(value: Int)(implicit val pos: Position) extends Literal { + val tpe = IntType + } + + case class LongLiteral(value: Long)(implicit val pos: Position) extends Literal { + val tpe = LongType + } + + case class FloatLiteral(value: Float)(implicit val pos: Position) extends Literal { + val tpe = FloatType + } + + case class DoubleLiteral(value: Double)(implicit val pos: Position) extends Literal { + val tpe = DoubleType + } + + case class StringLiteral(value: String)( + implicit val pos: Position) extends Literal with PropertyName { + val tpe = StringType + override def name = value + } + + case class ClassOf(cls: ReferenceType)(implicit val pos: Position) extends Literal { + val tpe = ClassType(Definitions.ClassClass) + } + + // Atomic expressions + + case class VarRef(ident: Ident, mutable: Boolean)(val tpe: Type)(implicit val pos: Position) extends Tree + + case class This()(val tpe: Type)(implicit val pos: Position) extends Tree + + /** Closure with explicit captures. + * The n captures map to the n first formal arguments. + */ + case class Closure(captureParams: List[ParamDef], params: List[ParamDef], + body: Tree, captureValues: List[Tree])(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + + // Classes + + case class ClassDef(name: Ident, kind: ClassKind, parent: Option[Ident], ancestors: List[Ident], defs: List[Tree])(implicit val pos: Position) extends Tree { + val tpe = NoType + } + + case class MethodDef(name: PropertyName, args: List[ParamDef], resultType: Type, body: Tree)( + val hash: Option[TreeHash])(implicit val pos: Position) extends Tree { + val tpe = NoType + } + + case class PropertyDef(name: PropertyName, getterBody: Tree, setterArg: ParamDef, setterBody: Tree)(implicit val pos: Position) extends Tree { + val tpe = NoType + } + + case class ConstructorExportDef(name: String, args: List[ParamDef], body: Tree)(implicit val pos: Position) extends Tree { + val tpe = NoType + } + + case class ModuleExportDef(fullName: String)(implicit val pos: Position) extends Tree { + val tpe = NoType + } + + /** A hash of a tree (usually a MethodDef). Contains two SHA-1 hashes */ + final class TreeHash(val treeHash: Array[Byte], val posHash: Array[Byte]) { + assert(treeHash.length == 20) + assert(posHash.length == 20) + } +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Types.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Types.scala new file mode 100644 index 0000000..4af493a --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Types.scala @@ -0,0 +1,182 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +import scala.annotation.tailrec + +object Types { + + /** Type of an expression in the IR. */ + abstract sealed class Type { + def show(): String = { + val writer = new java.io.StringWriter + val printer = new Printers.IRTreePrinter(writer) + printer.printType(this) + writer.toString() + } + } + + /** Any type (the top type of this type system). + * A variable of this type can contain any value, including `undefined` + * and `null` and any raw JS value. This type supports a very limited set + * of Scala operations, the ones common to all values. Basically only + * reference equality tests and instance tests. It also supports all + * JavaScript operations, since all Scala objects are also genuine + * JavaScript objects. + * The type java.lang.Object in the back-end maps to [[AnyType]] because it + * can hold raw JS values (not only instances of Scala.js classes). + */ + case object AnyType extends Type + + /** Nothing type (the bottom type of this type system). + * Expressions from which one can never come back are typed as [[Nothing]]. + * For example, `throw` and `return`. + */ + case object NothingType extends Type + + /** The type of `undefined`. */ + case object UndefType extends Type + + /** Boolean type. + * It does not accept `null` nor `undefined`. + */ + case object BooleanType extends Type + + /** 32-bit signed integer type. + * It does not accept `null` nor `undefined`. + */ + case object IntType extends Type + + /** 64-bit signed integer type. + * It does not accept `null` nor `undefined`. + */ + case object LongType extends Type + + /** Float type (32-bit). + * It does not accept `null` nor `undefined`. + */ + case object FloatType extends Type + + /** Double type (64-bit). + * It does not accept `null` nor `undefined`. + */ + case object DoubleType extends Type + + /** String type. + * It does not accept `null` nor `undefined`. + */ + case object StringType extends Type + + /** The type of `null`. + * It does not accept `undefined`. + * The null type is a subtype of all class types and array types. + */ + case object NullType extends Type + + /** Reference types (allowed for classOf[], is/asInstanceOf[]). */ + sealed abstract class ReferenceType extends Type + + /** Class (or interface) type. */ + final case class ClassType(className: String) extends ReferenceType + + /** Array type. */ + final case class ArrayType(baseClassName: String, dimensions: Int) extends ReferenceType + + object ArrayType { + def apply(innerType: ReferenceType): ArrayType = innerType match { + case ClassType(className) => ArrayType(className, 1) + case ArrayType(className, dim) => ArrayType(className, dim + 1) + } + } + + /** Record type. + * Used by the optimizer to inline classes as records with multiple fields. + * They are desugared as several local variables by JSDesugaring. + * Record types cannot cross method boundaries, so they cannot appear as + * the type of fields or parameters, nor as result types of methods. + * The compiler itself never generates record types. + */ + final case class RecordType(fields: List[RecordType.Field]) extends Type { + def findField(name: String): RecordType.Field = + fields.find(_.name == name).get + } + + object RecordType { + final case class Field(name: String, originalName: Option[String], + tpe: Type, mutable: Boolean) + } + + /** No type. */ + case object NoType extends Type + + /** Tests whether a type `lhs` is a subtype of `rhs` (or equal). + * [[NoType]] is never a subtype or supertype of anything (including + * itself). All other types are subtypes of themselves. + * @param isSubclass A function testing whether a class/interface is a + * subclass of another class/interface. + */ + def isSubtype(lhs: Type, rhs: Type)( + isSubclass: (String, String) => Boolean): Boolean = { + import Definitions._ + + (lhs != NoType && rhs != NoType) && { + (lhs == rhs) || + ((lhs, rhs) match { + case (_, AnyType) => true + case (NothingType, _) => true + + case (ClassType(lhsClass), ClassType(rhsClass)) => + isSubclass(lhsClass, rhsClass) + + case (NullType, ClassType(_)) => true + case (NullType, ArrayType(_, _)) => true + + case (UndefType, ClassType(cls)) => + isSubclass(BoxedUnitClass, cls) + case (BooleanType, ClassType(cls)) => + isSubclass(BoxedBooleanClass, cls) + case (IntType, ClassType(cls)) => + isSubclass(BoxedIntegerClass, cls) || + cls == BoxedByteClass || + cls == BoxedShortClass || + cls == BoxedDoubleClass + case (LongType, ClassType(cls)) => + isSubclass(BoxedLongClass, cls) + case (FloatType, ClassType(cls)) => + isSubclass(BoxedFloatClass, cls) || + cls == BoxedDoubleClass + case (DoubleType, ClassType(cls)) => + isSubclass(BoxedDoubleClass, cls) + case (StringType, ClassType(cls)) => + isSubclass(StringClass, cls) + + case (IntType, DoubleType) => true + case (FloatType, DoubleType) => true + + case (ArrayType(lhsBase, lhsDims), ArrayType(rhsBase, rhsDims)) => + if (lhsDims < rhsDims) { + false // because Array[A] </: Array[Array[A]] + } else if (lhsDims > rhsDims) { + rhsBase == ObjectClass // because Array[Array[A]] <: Array[Object] + } else { // lhsDims == rhsDims + // lhsBase must be <: rhsBase + if (isPrimitiveClass(lhsBase) || isPrimitiveClass(rhsBase)) { + lhsBase == rhsBase + } else { + isSubclass(lhsBase, rhsBase) + } + } + + case _ => + false + }) + } + } +} diff --git a/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Utils.scala b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Utils.scala new file mode 100644 index 0000000..d4769dc --- /dev/null +++ b/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Utils.scala @@ -0,0 +1,110 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js IR ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.ir + +import java.net.URI + +object Utils { + + /** Relativize target URI w.r.t. base URI */ + def relativize(base0: URI, trgt0: URI): URI = { + val base = base0.normalize + val trgt = trgt0.normalize + + if (base.isOpaque || !base.isAbsolute || base.getRawPath == null || + trgt.isOpaque || !trgt.isAbsolute || trgt.getRawPath == null || + base.getScheme != trgt.getScheme || + base.getRawAuthority != trgt.getRawAuthority) + trgt + else { + val trgtCmps = trgt.getRawPath.split('/') + val baseCmps = base.getRawPath.split('/') + + val prefixLen = (trgtCmps zip baseCmps).takeWhile(t => t._1 == t._2).size + + val newPathCmps = + List.fill(baseCmps.size - prefixLen)("..") ++ trgtCmps.drop(prefixLen) + + val newPath = newPathCmps.mkString("/") + + // Relative URI does not have scheme or authority + new URI(null, null, newPath, trgt.getRawQuery, trgt.getRawFragment) + } + } + + /** Adds an empty authority to URIs with the "file" scheme without authority. + * Some browsers don't fetch URIs without authority correctly. + */ + def fixFileURI(uri: URI): URI = + if (uri.getScheme() != "file" || uri.getAuthority() != null) uri + else new URI("file", "", uri.getPath(), uri.getQuery(), uri.getFragment()) + + def escapeJS(str: String): String = { + /* Note that Java and JavaScript happen to use the same encoding for + * Unicode, namely UTF-16, which means that 1 char from Java always equals + * 1 char in JavaScript. */ + val builder = new StringBuilder + str foreach { + case '\\' => builder.append("\\\\") + case '"' => builder.append("\\\"") + case '\u0007' => builder.append("\\a") + case '\u0008' => builder.append("\\b") + case '\u0009' => builder.append("\\t") + case '\u000A' => builder.append("\\n") + case '\u000B' => builder.append("\\v") + case '\u000C' => builder.append("\\f") + case '\u000D' => builder.append("\\r") + case c => + if (c >= 32 && c <= 126) builder.append(c.toChar) // ASCII printable characters + else builder.append(f"\\u$c%04x") + } + builder.result() + } + + /** A ByteArrayOutput stream that allows to jump back to a given + * position and complete some bytes. Methods must be called in the + * following order only: + * - [[markJump]] + * - [[jumpBack]] + * - [[continue]] + */ + private[ir] class JumpBackByteArrayOutputStream + extends java.io.ByteArrayOutputStream { + protected var jumpBackPos: Int = -1 + protected var headPos: Int = -1 + + /** Marks the current location for a jumpback */ + def markJump(): Unit = { + assert(jumpBackPos == -1) + assert(headPos == -1) + jumpBackPos = count + } + + /** Jumps back to the mark. Returns the number of bytes jumped */ + def jumpBack(): Int = { + assert(jumpBackPos >= 0) + assert(headPos == -1) + val jumped = count - jumpBackPos + headPos = count + count = jumpBackPos + jumpBackPos = -1 + jumped + } + + /** Continues to write at the head. */ + def continue(): Unit = { + assert(jumpBackPos == -1) + assert(headPos >= 0) + count = headPos + headPos = -1 + } + } + +} |