diff options
author | Felix Mulder <felix.mulder@gmail.com> | 2016-11-02 11:08:28 +0100 |
---|---|---|
committer | Guillaume Martres <smarter@ubuntu.com> | 2016-11-22 01:35:07 +0100 |
commit | 8a61ff432543a29234193cd1f7c14abd3f3d31a0 (patch) | |
tree | a8147561d307af862c295cfc8100d271063bb0dd /compiler/src/dotty/tools/backend | |
parent | 6a455fe6da5ff9c741d91279a2dc6fe2fb1b472f (diff) | |
download | dotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.tar.gz dotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.tar.bz2 dotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.zip |
Move compiler and compiler tests to compiler dir
Diffstat (limited to 'compiler/src/dotty/tools/backend')
6 files changed, 2342 insertions, 0 deletions
diff --git a/compiler/src/dotty/tools/backend/jvm/CollectEntryPoints.scala b/compiler/src/dotty/tools/backend/jvm/CollectEntryPoints.scala new file mode 100644 index 000000000..2ee1b6011 --- /dev/null +++ b/compiler/src/dotty/tools/backend/jvm/CollectEntryPoints.scala @@ -0,0 +1,125 @@ +package dotty.tools.backend.jvm + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Types +import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransform, MiniPhase, MiniPhaseTransform} +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc +import dotty.tools.dotc.backend.jvm.DottyPrimitives +import dotty.tools.dotc.core.Flags.FlagSet +import dotty.tools.dotc.transform.Erasure +import dotty.tools.dotc.transform.SymUtils._ +import java.io.{File => JFile} + +import scala.collection.generic.Clearable +import scala.collection.mutable +import scala.reflect.ClassTag +import scala.reflect.internal.util.WeakHashSet +import scala.reflect.io.{Directory, PlainDirectory, AbstractFile} +import scala.tools.asm.{ClassVisitor, FieldVisitor, MethodVisitor} +import scala.tools.nsc.backend.jvm.{BCodeHelpers, BackendInterface} +import dotty.tools.dotc.core._ +import Periods._ +import SymDenotations._ +import Contexts._ +import Types._ +import Symbols._ +import Denotations._ +import Phases._ +import java.lang.AssertionError +import dotty.tools.dotc.util.Positions.Position +import Decorators._ +import tpd._ +import StdNames.nme + +/** + * Created by dark on 26/11/14. + */ +class CollectEntryPoints extends MiniPhaseTransform { + def phaseName: String = "Collect entry points" + + override def transformDefDef(tree: tpd.DefDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + if ((tree.symbol ne NoSymbol) && CollectEntryPoints.isJavaEntryPoint(tree.symbol)) { + ctx.genBCodePhase.asInstanceOf[GenBCode].registerEntryPoint(tree.symbol) + } + tree + } +} + +object CollectEntryPoints{ + def isJavaMainMethod(sym: Symbol)(implicit ctx: Context) = { + (sym.name == nme.main) && (sym.info match { + case r@MethodType(_, List(defn.ArrayOf(t))) => + (t.widenDealias =:= defn.StringType) && ( + r.resultType.widenDealias =:= defn.UnitType) + case _ => false + }) + } + + def isJavaEntryPoint(sym: Symbol)(implicit ctx: Context): Boolean = { + import Types.MethodType + val d = ctx.definitions + val StringType = d.StringType + // The given class has a main method. + def hasJavaMainMethod(sym: Symbol): Boolean = + (toDenot(sym).info member nme.main).alternatives exists(x => isJavaMainMethod(x.symbol)) + + def fail(msg: String, pos: Position = sym.pos) = { + ctx.warning( sym.name + + s" has a main method with parameter type Array[String], but ${toDenot(sym).fullName} will not be a runnable program.\n Reason: $msg", + sourcePos(sym.pos) + // TODO: make this next claim true, if possible + // by generating valid main methods as static in module classes + // not sure what the jvm allows here + // + " You can still run the program by calling it as " + javaName(sym) + " instead." + ) + false + } + def failNoForwarder(msg: String) = { + fail(s"$msg, which means no static forwarder can be generated.\n") + } + val possibles = if (sym.flags is Flags.Module) (toDenot(sym).info nonPrivateMember nme.main).alternatives else Nil + val hasApproximate = possibles exists { m => + m.info match { + case MethodType(_, p :: Nil) => + p.typeSymbol == defn.ArrayClass + case _ => false + } + } + // At this point it's a module with a main-looking method, so either succeed or warn that it isn't. + hasApproximate && { + // Before erasure so we can identify generic mains. + { + // implicit val c = ctx.withPhase(ctx.erasurePhase) + + val companion = sym.asClass.moduleClass + + if (hasJavaMainMethod(companion)) + failNoForwarder("companion contains its own main method") + else if (toDenot(companion).info.member(nme.main) != NoDenotation) + // this is only because forwarders aren't smart enough yet + failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)") + else if (companion.flags is Flags.Trait) + failNoForwarder("companion is a trait") + // Now either succeed, or issue some additional warnings for things which look like + // attempts to be java main methods. + else (possibles exists(x=> isJavaMainMethod(x.symbol))) || { + possibles exists { m => + toDenot(m.symbol).info match { + case t: PolyType => + fail("main methods cannot be generic.") + case t@MethodType(paramNames, paramTypes) => + if (t.resultType :: paramTypes exists (_.typeSymbol.isAbstractType)) + fail("main methods cannot refer to type parameters or abstract types.", m.symbol.pos) + else + isJavaMainMethod(m.symbol) || fail("main method must have exact signature (Array[String])Unit", m.symbol.pos) + case tp => + fail(s"don't know what this is: $tp", m.symbol.pos) + } + } + } + } + } + } +} diff --git a/compiler/src/dotty/tools/backend/jvm/CollectSuperCalls.scala b/compiler/src/dotty/tools/backend/jvm/CollectSuperCalls.scala new file mode 100644 index 000000000..8285bfe4b --- /dev/null +++ b/compiler/src/dotty/tools/backend/jvm/CollectSuperCalls.scala @@ -0,0 +1,42 @@ +package dotty.tools.backend.jvm + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Flags.Trait +import dotty.tools.dotc.transform.TreeTransforms.{MiniPhaseTransform, TransformerInfo} + +/** Collect all super calls to trait members. + * + * For each super reference to trait member, register a call from the current class to the + * owner of the referenced member. + * + * This information is used to know if it is safe to remove a redundant mixin class. + * A redundant mixin class is one that is implemented by another mixin class. As the + * methods in a redundant mixin class could be implemented with a default abstract method, + * the redundant mixin class could be required as a parent by the JVM. + */ +class CollectSuperCalls extends MiniPhaseTransform { + import tpd._ + + def phaseName: String = "collectSuperCalls" + + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo): Tree = { + tree.qualifier match { + case sup: Super => + if (tree.symbol.owner.is(Trait)) + registerSuperCall(ctx.owner.enclosingClass.asClass, tree.symbol.owner.asClass) + case _ => + } + tree + } + + private def registerSuperCall(sym: ClassSymbol, calls: ClassSymbol)(implicit ctx: Context) = { + ctx.genBCodePhase match { + case genBCodePhase: GenBCode => + genBCodePhase.registerSuperCall(sym, calls) + case _ => + } + } +} diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala new file mode 100644 index 000000000..a7c449947 --- /dev/null +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -0,0 +1,1102 @@ +package dotty.tools.backend.jvm + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc +import dotty.tools.dotc.backend.jvm.DottyPrimitives +import dotty.tools.dotc.core.Flags.FlagSet +import dotty.tools.dotc.transform.Erasure +import dotty.tools.dotc.transform.SymUtils._ +import java.io.{File => JFile} + +import scala.collection.generic.Clearable +import scala.collection.mutable +import scala.reflect.ClassTag +import scala.reflect.internal.util.WeakHashSet +import scala.reflect.io.{AbstractFile, Directory, PlainDirectory} +import scala.tools.asm.{AnnotationVisitor, ClassVisitor, FieldVisitor, MethodVisitor} +import scala.tools.nsc.backend.jvm.{BCodeHelpers, BackendInterface} +import dotty.tools.dotc.core._ +import Periods._ +import SymDenotations._ +import Contexts._ +import Types._ +import Symbols._ +import Denotations._ +import Phases._ +import java.lang.AssertionError + +import dotty.tools.dotc.util.{DotClass, Positions} +import Decorators._ +import tpd._ + +import scala.tools.asm +import NameOps._ +import StdNames.nme +import NameOps._ +import dotty.tools.dotc.core +import dotty.tools.dotc.core.Names.TypeName + +import scala.annotation.tailrec + +class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Map[Symbol, Set[ClassSymbol]])(implicit ctx: Context) extends BackendInterface{ + type Symbol = Symbols.Symbol + type Type = Types.Type + type Tree = tpd.Tree + type CompilationUnit = dotc.CompilationUnit + type Constant = Constants.Constant + type Literal = tpd.Literal + type Position = Positions.Position + type Name = Names.Name + type ClassDef = tpd.TypeDef + type TypeDef = tpd.TypeDef + type Apply = tpd.Apply + type TypeApply = tpd.TypeApply + type Try = tpd.Try + type Assign = tpd.Assign + type Ident = tpd.Ident + type If = tpd.If + type ValDef = tpd.ValDef + type Throw = tpd.Apply + type Return = tpd.Return + type Block = tpd.Block + type Typed = tpd.Typed + type Match = tpd.Match + type This = tpd.This + type CaseDef = tpd.CaseDef + type Alternative = tpd.Alternative + type DefDef = tpd.DefDef + type Template = tpd.Template + type Select = tpd.Tree // Actually tpd.Select || tpd.Ident + type Bind = tpd.Bind + type New = tpd.New + type Super = tpd.Super + type Modifiers = Null + type Annotation = Annotations.Annotation + type ArrayValue = tpd.JavaSeqLiteral + type ApplyDynamic = Null + type ModuleDef = Null + type LabelDef = tpd.DefDef + type Closure = tpd.Closure + + val NoSymbol = Symbols.NoSymbol + val NoPosition: Position = Positions.NoPosition + val EmptyTree: Tree = tpd.EmptyTree + + + val UnitTag: ConstantTag = Constants.UnitTag + val IntTag: ConstantTag = Constants.IntTag + val FloatTag: ConstantTag = Constants.FloatTag + val NullTag: ConstantTag = Constants.NullTag + val BooleanTag: ConstantTag = Constants.BooleanTag + val ByteTag: ConstantTag = Constants.ByteTag + val ShortTag: ConstantTag = Constants.ShortTag + val CharTag: ConstantTag = Constants.CharTag + val DoubleTag: ConstantTag = Constants.DoubleTag + val LongTag: ConstantTag = Constants.LongTag + val StringTag: ConstantTag = Constants.StringTag + val ClazzTag: ConstantTag = Constants.ClazzTag + val EnumTag: ConstantTag = Constants.EnumTag + + val nme_This: Name = StdNames.nme.This + val nme_EMPTY_PACKAGE_NAME: Name = StdNames.nme.EMPTY_PACKAGE + val nme_CONSTRUCTOR: Name = StdNames.nme.CONSTRUCTOR + val nme_WILDCARD: Name = StdNames.nme.WILDCARD + val nme_THIS: Name = StdNames.nme.THIS + val nme_PACKAGE: Name = StdNames.nme.PACKAGE + val nme_EQEQ_LOCAL_VAR: Name = StdNames.nme.EQEQ_LOCAL_VAR + + // require LambdaMetafactory: scalac uses getClassIfDefined, but we need those always. + override lazy val LambdaMetaFactory = ctx.requiredClass("java.lang.invoke.LambdaMetafactory") + override lazy val MethodHandle = ctx.requiredClass("java.lang.invoke.MethodHandle") + + val nme_valueOf: Name = StdNames.nme.valueOf + val nme_apply = StdNames.nme.apply + val NothingClass: Symbol = defn.NothingClass + val NullClass: Symbol = defn.NullClass + val ObjectClass: Symbol = defn.ObjectClass + val Object_Type: Type = defn.ObjectType + val Throwable_Type: Type = defn.ThrowableType + val Object_isInstanceOf: Symbol = defn.Any_isInstanceOf + val Object_asInstanceOf: Symbol = defn.Any_asInstanceOf + val Object_equals: Symbol = defn.Any_equals + val ArrayClass: Symbol = defn.ArrayClass + val UnitClass: Symbol = defn.UnitClass + val BooleanClass: Symbol = defn.BooleanClass + val CharClass: Symbol = defn.CharClass + val ShortClass: Symbol = defn.ShortClass + val ClassClass: Symbol = defn.ClassClass + val ByteClass: Symbol = defn.ByteClass + val IntClass: Symbol = defn.IntClass + val LongClass: Symbol = defn.LongClass + val FloatClass: Symbol = defn.FloatClass + val DoubleClass: Symbol = defn.DoubleClass + def isArrayClone(tree: Tree) = tree match { + case Select(qual, StdNames.nme.clone_) if qual.tpe.widen.isInstanceOf[JavaArrayType] => true + case _ => false + } + + val hashMethodSym: Symbol = NoSymbol // used to dispatch ## on primitives to ScalaRuntime.hash. Should be implemented by a miniphase + val externalEqualsNumNum: Symbol = defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumNum) + val externalEqualsNumChar: Symbol = NoSymbol // ctx.requiredMethod(BoxesRunTimeTypeRef, nme.equalsNumChar) // this method is private + val externalEqualsNumObject: Symbol = defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumObject) + val externalEquals: Symbol = defn.BoxesRunTimeClass.info.decl(nme.equals_).suchThat(toDenot(_).info.firstParamTypes.size == 2).symbol + val MaxFunctionArity: Int = Definitions.MaxFunctionArity + val FunctionClass: Array[Symbol] = defn.FunctionClassPerRun() + val AbstractFunctionClass: Array[Symbol] = defn.AbstractFunctionClassPerRun() + val PartialFunctionClass: Symbol = defn.PartialFunctionClass + val AbstractPartialFunctionClass: Symbol = defn.AbstractPartialFunctionClass + val String_valueOf: Symbol = defn.String_valueOf_Object + lazy val Predef_classOf: Symbol = defn.ScalaPredefModule.requiredMethod(nme.classOf) + + lazy val AnnotationRetentionAttr = ctx.requiredClass("java.lang.annotation.Retention") + lazy val AnnotationRetentionSourceAttr = ctx.requiredClass("java.lang.annotation.RetentionPolicy").linkedClass.requiredValue("SOURCE") + lazy val AnnotationRetentionClassAttr = ctx.requiredClass("java.lang.annotation.RetentionPolicy").linkedClass.requiredValue("CLASS") + lazy val AnnotationRetentionRuntimeAttr = ctx.requiredClass("java.lang.annotation.RetentionPolicy").linkedClass.requiredValue("RUNTIME") + lazy val JavaAnnotationClass = ctx.requiredClass("java.lang.annotation.Annotation") + + def boxMethods: Map[Symbol, Symbol] = defn.ScalaValueClasses().map{x => // @darkdimius Are you sure this should be a def? + (x, Erasure.Boxing.boxMethod(x.asClass)) + }.toMap + def unboxMethods: Map[Symbol, Symbol] = defn.ScalaValueClasses().map(x => (x, Erasure.Boxing.unboxMethod(x.asClass))).toMap + + override def isSyntheticArrayConstructor(s: Symbol) = { + s eq defn.newArrayMethod + } + + def isBox(sym: Symbol): Boolean = Erasure.Boxing.isBox(sym) + def isUnbox(sym: Symbol): Boolean = Erasure.Boxing.isUnbox(sym) + + val primitives: Primitives = new Primitives { + val primitives = new DottyPrimitives(ctx) + def getPrimitive(app: Apply, reciever: Type): Int = primitives.getPrimitive(app, reciever) + + def getPrimitive(sym: Symbol): Int = primitives.getPrimitive(sym) + + def isPrimitive(fun: Tree): Boolean = primitives.isPrimitive(fun) + } + implicit val TypeDefTag: ClassTag[TypeDef] = ClassTag[TypeDef](classOf[TypeDef]) + implicit val ApplyTag: ClassTag[Apply] = ClassTag[Apply](classOf[Apply]) + implicit val SelectTag: ClassTag[Select] = ClassTag[Select](classOf[Select]) + implicit val TypeApplyTag: ClassTag[TypeApply] = ClassTag[TypeApply](classOf[TypeApply]) + implicit val ClassDefTag: ClassTag[ClassDef] = ClassTag[TypeDef](classOf[TypeDef]) + implicit val TryTag: ClassTag[Try] = ClassTag[Try](classOf[Try]) + implicit val AssignTag: ClassTag[Assign] = ClassTag[Assign](classOf[Assign]) + implicit val IdentTag: ClassTag[Ident] = ClassTag[Ident](classOf[Ident]) + implicit val IfTag: ClassTag[If] = ClassTag[If](classOf[If]) + implicit val LabelDefTag: ClassTag[LabelDef] = ClassTag[LabelDef](classOf[LabelDef]) + implicit val ValDefTag: ClassTag[ValDef] = ClassTag[ValDef](classOf[ValDef]) + implicit val ThrowTag: ClassTag[Throw] = ClassTag[Throw](classOf[Throw]) + implicit val ReturnTag: ClassTag[Return] = ClassTag[Return](classOf[Return]) + implicit val LiteralTag: ClassTag[Literal] = ClassTag[Literal](classOf[Literal]) + implicit val BlockTag: ClassTag[Block] = ClassTag[Block](classOf[Block]) + implicit val TypedTag: ClassTag[Typed] = ClassTag[Typed](classOf[Typed]) + implicit val ArrayValueTag: ClassTag[ArrayValue] = ClassTag[ArrayValue](classOf[ArrayValue]) + implicit val MatchTag: ClassTag[Match] = ClassTag[Match](classOf[Match]) + implicit val CaseDefTag: ClassTag[CaseDef] = ClassTag[CaseDef](classOf[CaseDef]) + implicit val ThisTag: ClassTag[This] = ClassTag[This](classOf[This]) + implicit val AlternativeTag: ClassTag[Alternative] = ClassTag[Alternative](classOf[Alternative]) + implicit val DefDefTag: ClassTag[DefDef] = ClassTag[DefDef](classOf[DefDef]) + implicit val ModuleDefTag: ClassTag[ModuleDef] = ClassTag[ModuleDef](classOf[ModuleDef]) + implicit val NameTag: ClassTag[Name] = ClassTag[Name](classOf[Name]) + implicit val TemplateTag: ClassTag[Template] = ClassTag[Template](classOf[Template]) + implicit val BindTag: ClassTag[Bind] = ClassTag[Bind](classOf[Bind]) + implicit val NewTag: ClassTag[New] = ClassTag[New](classOf[New]) + implicit val ApplyDynamicTag: ClassTag[ApplyDynamic] = ClassTag[ApplyDynamic](classOf[ApplyDynamic]) + implicit val SuperTag: ClassTag[Super] = ClassTag[Super](classOf[Super]) + implicit val ConstantClassTag: ClassTag[Constant] = ClassTag[Constant](classOf[Constant]) + implicit val ClosureTag: ClassTag[Closure] = ClassTag[Closure](classOf[Closure]) + + /* dont emit any annotations for now*/ + def isRuntimeVisible(annot: Annotation): Boolean = { + annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr) match { + case Some(retentionAnnot) => + retentionAnnot.tree.find(_.symbol == AnnotationRetentionRuntimeAttr).isDefined + case _ => + // SI-8926: if the annotation class symbol doesn't have a @RetentionPolicy annotation, the + // annotation is emitted with visibility `RUNTIME` + // dotty bug: #389 + true + } + } + + def shouldEmitAnnotation(annot: Annotation): Boolean = { + annot.symbol.isJavaDefined && + retentionPolicyOf(annot) != AnnotationRetentionSourceAttr && + annot.args.isEmpty + } + + private def retentionPolicyOf(annot: Annotation): Symbol = + annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr). + flatMap(_.argument(0).map(_.symbol)).getOrElse(AnnotationRetentionClassAttr) + + private def emitArgument(av: AnnotationVisitor, + name: String, + arg: Tree, bcodeStore: BCodeHelpers)(innerClasesStore: bcodeStore.BCInnerClassGen): Unit = { + (arg: @unchecked) match { + + case Literal(const @ Constant(_)) => + const.tag match { + case BooleanTag | ByteTag | ShortTag | CharTag | IntTag | LongTag | FloatTag | DoubleTag => av.visit(name, const.value) + case StringTag => + assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant` + av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag + case ClazzTag => av.visit(name, const.typeValue.toTypeKind(bcodeStore)(innerClasesStore).toASMType) + case EnumTag => + val edesc = innerClasesStore.typeDescriptor(const.tpe.asInstanceOf[bcodeStore.int.Type]) // the class descriptor of the enumeration class. + val evalue = const.symbolValue.name.toString // value the actual enumeration value. + av.visitEnum(name, edesc, evalue) + } + case t: TypeApply if (t.fun.symbol == Predef_classOf) => + av.visit(name, t.args.head.tpe.classSymbol.denot.info.toTypeKind(bcodeStore)(innerClasesStore).toASMType) + case t: tpd.Select => + if (t.symbol.denot.is(Flags.Enum)) { + val edesc = innerClasesStore.typeDescriptor(t.tpe.asInstanceOf[bcodeStore.int.Type]) // the class descriptor of the enumeration class. + val evalue = t.symbol.name.toString // value the actual enumeration value. + av.visitEnum(name, edesc, evalue) + } else { + assert(toDenot(t.symbol).name.toTermName.defaultGetterIndex >= 0) // this should be default getter. do not emmit. + } + case t: SeqLiteral => + val arrAnnotV: AnnotationVisitor = av.visitArray(name) + for(arg <- t.elems) { emitArgument(arrAnnotV, null, arg, bcodeStore)(innerClasesStore) } + arrAnnotV.visitEnd() + + case Apply(fun, args) if (fun.symbol == defn.ArrayClass.primaryConstructor || + (toDenot(fun.symbol).owner == defn.ArrayClass.linkedClass && fun.symbol.name == nme_apply)) => + val arrAnnotV: AnnotationVisitor = av.visitArray(name) + + var actualArgs = if (fun.tpe.isInstanceOf[ImplicitMethodType]) { + // generic array method, need to get implicit argument out of the way + fun.asInstanceOf[Apply].args + } else args + + val flatArgs = actualArgs.flatMap { + case t: tpd.SeqLiteral => t.elems + case e => List(e) + } + for(arg <- flatArgs) { emitArgument(arrAnnotV, null, arg, bcodeStore)(innerClasesStore) } + arrAnnotV.visitEnd() +/* + case sb @ ScalaSigBytes(bytes) => + // see http://www.scala-lang.org/sid/10 (Storage of pickled Scala signatures in class files) + // also JVMS Sec. 4.7.16.1 The element_value structure and JVMS Sec. 4.4.7 The CONSTANT_Utf8_info Structure. + if (sb.fitsInOneString) { + av.visit(name, BCodeAsmCommon.strEncode(sb)) + } else { + val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name) + for(arg <- BCodeAsmCommon.arrEncode(sb)) { arrAnnotV.visit(name, arg) } + arrAnnotV.visitEnd() + } // for the lazy val in ScalaSigBytes to be GC'ed, the invoker of emitAnnotations() should hold the ScalaSigBytes in a method-local var that doesn't escape. +*/ + case t @ Apply(constr, args) if t.tpe.derivesFrom(JavaAnnotationClass) => + val typ = t.tpe.classSymbol.denot.info + val assocs = assocsFromApply(t) + val desc = innerClasesStore.typeDescriptor(typ.asInstanceOf[bcodeStore.int.Type]) // the class descriptor of the nested annotation class + val nestedVisitor = av.visitAnnotation(name, desc) + emitAssocs(nestedVisitor, assocs, bcodeStore)(innerClasesStore) + } + } + + override def emitAnnotations(cw: asm.ClassVisitor, annotations: List[Annotation], bcodeStore: BCodeHelpers) + (innerClasesStore: bcodeStore.BCInnerClassGen) = { + for(annot <- annotations; if shouldEmitAnnotation(annot)) { + val typ = annot.atp + val assocs = annot.assocs + val av = cw.visitAnnotation(innerClasesStore.typeDescriptor(typ.asInstanceOf[bcodeStore.int.Type]), isRuntimeVisible(annot)) + emitAssocs(av, assocs, bcodeStore)(innerClasesStore) + } + } + + private def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, Object)], bcodeStore: BCodeHelpers) + (innerClasesStore: bcodeStore.BCInnerClassGen) = { + for ((name, value) <- assocs) + emitArgument(av, name.toString, value.asInstanceOf[Tree], bcodeStore)(innerClasesStore) + av.visitEnd() + } + + override def emitAnnotations(mw: asm.MethodVisitor, annotations: List[Annotation], bcodeStore: BCodeHelpers) + (innerClasesStore: bcodeStore.BCInnerClassGen) = { + for(annot <- annotations; if shouldEmitAnnotation(annot)) { + val typ = annot.atp + val assocs = annot.assocs + val av = mw.visitAnnotation(innerClasesStore.typeDescriptor(typ.asInstanceOf[bcodeStore.int.Type]), isRuntimeVisible(annot)) + emitAssocs(av, assocs, bcodeStore)(innerClasesStore) + } + } + + override def emitAnnotations(fw: asm.FieldVisitor, annotations: List[Annotation], bcodeStore: BCodeHelpers) + (innerClasesStore: bcodeStore.BCInnerClassGen) = { + for(annot <- annotations; if shouldEmitAnnotation(annot)) { + val typ = annot.atp + val assocs = annot.assocs + val av = fw.visitAnnotation(innerClasesStore.typeDescriptor(typ.asInstanceOf[bcodeStore.int.Type]), isRuntimeVisible(annot)) + emitAssocs(av, assocs, bcodeStore)(innerClasesStore) + } + } + + override def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[Annotation]], bcodeStore: BCodeHelpers) + (innerClasesStore: bcodeStore.BCInnerClassGen): Unit = { + val annotationss = pannotss map (_ filter shouldEmitAnnotation) + if (annotationss forall (_.isEmpty)) return + for ((annots, idx) <- annotationss.zipWithIndex; + annot <- annots) { + val typ = annot.atp + val assocs = annot.assocs + val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, innerClasesStore.typeDescriptor(typ.asInstanceOf[bcodeStore.int.Type]), isRuntimeVisible(annot)) + emitAssocs(pannVisitor, assocs, bcodeStore)(innerClasesStore) + } + } + + def getAnnotPickle(jclassName: String, sym: Symbol): Option[Annotation] = None + + + def getRequiredClass(fullname: String): Symbol = ctx.requiredClass(fullname.toTermName) + + def getClassIfDefined(fullname: String): Symbol = NoSymbol // used only for android. todo: implement + + private def erasureString(clazz: Class[_]): String = { + if (clazz.isArray) "Array[" + erasureString(clazz.getComponentType) + "]" + else clazz.getName + } + + def requiredClass[T](implicit evidence: ClassTag[T]): Symbol = { + ctx.requiredClass(erasureString(evidence.runtimeClass).toTermName) + } + + def requiredModule[T](implicit evidence: ClassTag[T]): Symbol = { + val moduleName = erasureString(evidence.runtimeClass) + val className = if (moduleName.endsWith("$")) moduleName.dropRight(1) else moduleName + ctx.requiredModule(className.toTermName) + } + + + def debuglog(msg: => String): Unit = ctx.debuglog(msg) + def informProgress(msg: String): Unit = ctx.informProgress(msg) + def log(msg: => String): Unit = ctx.log(msg) + def error(pos: Position, msg: String): Unit = ctx.error(msg, pos) + def warning(pos: Position, msg: String): Unit = ctx.warning(msg, pos) + def abort(msg: String): Nothing = { + ctx.error(msg) + throw new RuntimeException(msg) + } + + def emitAsmp: Option[String] = None + + def shouldEmitJumpAfterLabels = true + + def dumpClasses: Option[String] = + if (ctx.settings.Ydumpclasses.isDefault) None + else Some(ctx.settings.Ydumpclasses.value) + + def mainClass: Option[String] = + if (ctx.settings.mainClass.isDefault) None + else Some(ctx.settings.mainClass.value) + def setMainClass(name: String): Unit = ctx.settings.mainClass.update(name) + + + def noForwarders: Boolean = ctx.settings.noForwarders.value + def debuglevel: Int = 3 // 0 -> no debug info; 1-> filename; 2-> lines; 3-> varnames + def settings_debug: Boolean = ctx.settings.debug.value + def targetPlatform: String = ctx.settings.target.value + + val perRunCaches: Caches = new Caches { + def newAnyRefMap[K <: AnyRef, V](): mutable.AnyRefMap[K, V] = new mutable.AnyRefMap[K, V]() + def newWeakMap[K, V](): mutable.WeakHashMap[K, V] = new mutable.WeakHashMap[K, V]() + def recordCache[T <: Clearable](cache: T): T = cache + def newWeakSet[K <: AnyRef](): WeakHashSet[K] = new WeakHashSet[K]() + def newMap[K, V](): mutable.HashMap[K, V] = new mutable.HashMap[K, V]() + def newSet[K](): mutable.Set[K] = new mutable.HashSet[K] + } + + + + val MODULE_INSTANCE_FIELD: String = nme.MODULE_INSTANCE_FIELD.toString + + def internalNameString(offset: Int, length: Int): String = new String(Names.chrs, offset, length) + + def newTermName(prefix: String): Name = prefix.toTermName + + val Flag_SYNTHETIC: Flags = Flags.Synthetic.bits + val Flag_METHOD: Flags = Flags.Method.bits + val ExcludedForwarderFlags: Flags = { + Flags.Specialized | Flags.Lifted | Flags.Protected | Flags.JavaStatic | + Flags.ExpandedName | Flags.Bridge | Flags.VBridge | Flags.Private | Flags.Macro + }.bits + + + def isQualifierSafeToElide(qual: Tree): Boolean = tpd.isIdempotentExpr(qual) + def desugarIdent(i: Ident): Option[tpd.Select] = { + i.tpe match { + case TermRef(prefix: TermRef, name) => + Some(tpd.ref(prefix).select(i.symbol)) + case TermRef(prefix: ThisType, name) => + Some(tpd.This(prefix.cls).select(i.symbol)) + case TermRef(NoPrefix, name) => + if (i.symbol is Flags.Method) Some(This(i.symbol.topLevelClass).select(i.symbol)) // workaround #342 todo: remove after fixed + else None + case _ => None + } + } + def getLabelDefOwners(tree: Tree): Map[Tree, List[LabelDef]] = { + // for each rhs of a defdef returns LabelDefs inside this DefDef + val res = new collection.mutable.HashMap[Tree, List[LabelDef]]() + + val t = new TreeTraverser { + var outerRhs: Tree = tree + + def traverse(tree: tpd.Tree)(implicit ctx: Context): Unit = tree match { + case t: DefDef => + if (t.symbol is Flags.Label) + res.put(outerRhs, t :: res.getOrElse(outerRhs, Nil)) + else outerRhs = t + traverseChildren(t) + case _ => traverseChildren(tree) + } + } + + t.traverse(tree) + res.toMap + } + + // todo: remove + def isMaybeBoxed(sym: Symbol) = { + (sym == ObjectClass) || + (sym == JavaSerializableClass) || + (sym == defn.ComparableClass) || + (sym derivesFrom BoxedNumberClass) || + (sym derivesFrom BoxedCharacterClass) || + (sym derivesFrom BoxedBooleanClass) + } + + def getSingleOutput: Option[AbstractFile] = None // todo: implement + + + def getGenericSignature(sym: Symbol, owner: Symbol): String = null // todo: implement + + def getStaticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol): String = null // todo: implement + + + def sourceFileFor(cu: CompilationUnit): String = cu.source.file.name + + + + implicit def positionHelper(a: Position): PositionHelper = new PositionHelper { + def isDefined: Boolean = a.exists + def line: Int = sourcePos(a).line + 1 + def finalPosition: Position = a + } + + implicit def constantHelper(a: Constant): ConstantHelper = new ConstantHelper { + def booleanValue: Boolean = a.booleanValue + def longValue: Long = a.longValue + def byteValue: Byte = a.byteValue + def stringValue: String = a.stringValue + def symbolValue: Symbol = a.symbolValue + def floatValue: Float = a.floatValue + def value: Any = a.value + def tag: ConstantTag = a.tag + def typeValue: Type = a.typeValue + def shortValue: Short = a.shortValue + def intValue: Int = a.intValue + def doubleValue: Double = a.doubleValue + def charValue: Char = a.charValue + } + + + implicit def treeHelper(a: Tree): TreeHelper = new TreeHelper { + def symbol: Symbol = a.symbol + + def pos: Position = a.pos + + def isEmpty: Boolean = a.isEmpty + + def tpe: Type = a.tpe + + def exists(pred: (Tree) => Boolean): Boolean = a.find(pred).isDefined + } + + + implicit def annotHelper(a: Annotation): AnnotationHelper = new AnnotationHelper { + def atp: Type = a.tree.tpe + + def assocs: List[(Name, Tree)] = assocsFromApply(a.tree) + + def symbol: Symbol = a.tree.symbol + + def args: List[Tree] = List.empty // those arguments to scala-defined annotations. they are never emmited + } + + def assocsFromApply(tree: Tree) = { + tree match { + case Apply(fun, args) => + fun.tpe.widen match { + case MethodType(names, _) => + names zip args + } + } + } + + + implicit def nameHelper(n: Name): NameHelper = new NameHelper { + def toTypeName: Name = n.toTypeName + def isTypeName: Boolean = n.isTypeName + def toTermName: Name = n.toTermName + def dropModule: Name = n.stripModuleClassSuffix + + def len: Int = n.length + def offset: Int = n.start + def isTermName: Boolean = n.isTermName + def startsWith(s: String): Boolean = n.startsWith(s) + } + + + implicit def symHelper(sym: Symbol): SymbolHelper = new SymbolHelper { + // names + def fullName(sep: Char): String = sym.showFullName + def fullName: String = sym.showFullName + def simpleName: Name = sym.name + def javaSimpleName: Name = toDenot(sym).name // addModuleSuffix(simpleName.dropLocal) + def javaBinaryName: Name = toDenot(sym).fullNameSeparated("/") // addModuleSuffix(fullNameInternal('/')) + def javaClassName: String = toDenot(sym).fullName.toString// addModuleSuffix(fullNameInternal('.')).toString + def name: Name = sym.name + def rawname: Name = sym.name // todo ???? + + // types + def info: Type = toDenot(sym).info + def tpe: Type = toDenot(sym).info // todo whats the differentce between tpe and info? + def thisType: Type = toDenot(sym).thisType + + // tests + def isClass: Boolean = { + sym.isPackageObject || (sym.isClass) + } + def isType: Boolean = sym.isType + def isAnonymousClass: Boolean = toDenot(sym).isAnonymousClass + def isConstructor: Boolean = toDenot(sym).isConstructor + def isAnonymousFunction: Boolean = toDenot(sym).isAnonymousFunction + def isMethod: Boolean = sym is Flags.Method + def isPublic: Boolean = sym.flags.is(Flags.EmptyFlags, Flags.Private | Flags.Protected) + def isSynthetic: Boolean = sym is Flags.Synthetic + def isPackageClass: Boolean = sym is Flags.PackageClass + def isModuleClass: Boolean = sym is Flags.ModuleClass + def isModule: Boolean = sym is Flags.Module + def isStrictFP: Boolean = false // todo: implement + def isLabel: Boolean = sym is Flags.Label + def hasPackageFlag: Boolean = sym is Flags.Package + def isImplClass: Boolean = sym is Flags.ImplClass + def isInterface: Boolean = (sym is Flags.PureInterface) || (sym is Flags.Trait) + def hasGetter: Boolean = false // used only for generaration of beaninfo todo: implement + def isGetter: Boolean = toDenot(sym).isGetter + def isSetter: Boolean = toDenot(sym).isSetter + def isGetClass: Boolean = sym eq defn.Any_getClass + def isJavaDefined: Boolean = sym is Flags.JavaDefined + def isJavaDefaultMethod: Boolean = !((sym is Flags.Deferred) || toDenot(sym).isClassConstructor) + def isDeferred: Boolean = sym is Flags.Deferred + def isPrivate: Boolean = sym is Flags.Private + def getsJavaFinalFlag: Boolean = + isFinal && !toDenot(sym).isClassConstructor && !(sym is Flags.Mutable) && !(sym.enclosingClass is Flags.Trait) + + def getsJavaPrivateFlag: Boolean = + isPrivate //|| (sym.isPrimaryConstructor && sym.owner.isTopLevelModuleClass) + + def isFinal: Boolean = sym is Flags.Final + def isStaticMember: Boolean = (sym ne NoSymbol) && + ((sym is Flags.JavaStatic) || (owner is Flags.ImplClass) || toDenot(sym).hasAnnotation(ctx.definitions.ScalaStaticAnnot)) + // guard against no sumbol cause this code is executed to select which call type(static\dynamic) to use to call array.clone + + def isBottomClass: Boolean = (sym ne defn.NullClass) && (sym ne defn.NothingClass) + def isBridge: Boolean = sym is Flags.Bridge + def isArtifact: Boolean = sym is Flags.Artifact + def hasEnumFlag: Boolean = sym is Flags.Enum + def hasAccessBoundary: Boolean = sym.accessBoundary(defn.RootClass) ne defn.RootClass + def isVarargsMethod: Boolean = sym is Flags.JavaVarargs + def isDeprecated: Boolean = false + def isMutable: Boolean = sym is Flags.Mutable + def hasAbstractFlag: Boolean = + (sym is Flags.Abstract) || (sym is Flags.JavaInterface) || (sym is Flags.Trait) + def hasModuleFlag: Boolean = sym is Flags.Module + def isSynchronized: Boolean = sym is Flags.Synchronized + def isNonBottomSubClass(other: Symbol): Boolean = sym.derivesFrom(other) + def hasAnnotation(ann: Symbol): Boolean = toDenot(sym).hasAnnotation(ann) + def shouldEmitForwarders: Boolean = + (sym is Flags.Module) && !(sym is Flags.ImplClass) && sym.isStatic + def isJavaEntryPoint: Boolean = CollectEntryPoints.isJavaEntryPoint(sym) + + def isClassConstructor: Boolean = toDenot(sym).isClassConstructor + + /** + * True for module classes of modules that are top-level or owned only by objects. Module classes + * for such objects will get a MODULE$ flag and a corresponding static initializer. + */ + def isStaticModuleClass: Boolean = + (sym is Flags.Module) && { + // scalac uses atPickling here + // this would not work if modules are created after pickling + // for example by specialization + val original = toDenot(sym).initial + val validity = original.validFor + val shiftedContext = ctx.withPhase(validity.phaseId) + toDenot(sym)(shiftedContext).isStatic(shiftedContext) + } + + def isStaticConstructor: Boolean = (isStaticMember && isClassConstructor) || (sym.name eq core.Names.STATIC_CONSTRUCTOR) + + + // navigation + def owner: Symbol = toDenot(sym).owner + def rawowner: Symbol = { + originalOwner + } + def originalOwner: Symbol = + // used to populate the EnclosingMethod attribute. + // it is very tricky in presence of classes(and annonymous classes) defined inside supper calls. + if (sym.exists) { + val original = toDenot(sym).initial + val validity = original.validFor + val shiftedContext = ctx.withPhase(validity.phaseId) + val r = toDenot(sym)(shiftedContext).maybeOwner.lexicallyEnclosingClass(shiftedContext) + r + } else NoSymbol + def parentSymbols: List[Symbol] = toDenot(sym).info.parents.map(_.typeSymbol) + def superClass: Symbol = { + val t = toDenot(sym).asClass.superClass + if (t.exists) t + else if (sym is Flags.ModuleClass) { + // workaround #371 + + println(s"Warning: mocking up superclass for $sym") + ObjectClass + } + else t + } + def enclClass: Symbol = toDenot(sym).enclosingClass + def linkedClassOfClass: Symbol = linkedClass + def linkedClass: Symbol = { + toDenot(sym)(ctx).linkedClass(ctx) + } //exitingPickler(sym.linkedClassOfClass) + def companionClass: Symbol = toDenot(sym).companionClass + def companionModule: Symbol = toDenot(sym).companionModule + def companionSymbol: Symbol = if (sym is Flags.Module) companionClass else companionModule + def moduleClass: Symbol = toDenot(sym).moduleClass + def enclosingClassSym: Symbol = { + if (this.isClass) { + val ct = ctx.withPhase(ctx.flattenPhase.prev) + toDenot(sym)(ct).owner.enclosingClass(ct) + } + else sym.enclosingClass(ctx.withPhase(ctx.flattenPhase.prev)) + } //todo is handled specially for JavaDefined symbols in scalac + + + + // members + def primaryConstructor: Symbol = toDenot(sym).primaryConstructor + + /** For currently compiled classes: All locally defined classes including local classes. + * The empty list for classes that are not currently compiled. + */ + def nestedClasses: List[Symbol] = definedClasses(ctx.flattenPhase) + + /** For currently compiled classes: All classes that are declared as members of this class + * (but not inherited ones). The empty list for classes that are not currently compiled. + */ + def memberClasses: List[Symbol] = definedClasses(ctx.lambdaLiftPhase) + + private def definedClasses(phase: Phase) = + if (sym.isDefinedInCurrentRun) + ctx.atPhase(phase) { implicit ctx => + toDenot(sym).info.decls.filter(_.isClass).toList + } + else Nil + + def annotations: List[Annotation] = Nil + def companionModuleMembers: List[Symbol] = { + // phase travel to exitingPickler: this makes sure that memberClassesOf only sees member classes, + // not local classes of the companion module (E in the exmaple) that were lifted by lambdalift. + if (linkedClass.isTopLevelModuleClass) /*exitingPickler*/ linkedClass.memberClasses + else Nil + } + def fieldSymbols: List[Symbol] = { + toDenot(sym).info.decls.filter(p => p.isTerm && !p.is(Flags.Method)).toList + } + def methodSymbols: List[Symbol] = + for (f <- toDenot(sym).info.decls.toList if f.isMethod && f.isTerm && !f.isModule) yield f + def serialVUID: Option[Long] = None + + + def freshLocal(cunit: CompilationUnit, name: String, tpe: Type, pos: Position, flags: Flags): Symbol = { + ctx.newSymbol(sym, name.toTermName, FlagSet(flags), tpe, NoSymbol, pos) + } + + def getter(clz: Symbol): Symbol = decorateSymbol(sym).getter + def setter(clz: Symbol): Symbol = decorateSymbol(sym).setter + + def moduleSuffix: String = "" // todo: validate that names already have $ suffix + def outputDirectory: AbstractFile = DottyBackendInterface.this.outputDirectory + def pos: Position = sym.pos + + def throwsAnnotations: List[Symbol] = Nil + + /** + * All interfaces implemented by a class, except for those inherited through the superclass. + * Redundant interfaces are removed unless there is a super call to them. + */ + def superInterfaces: List[Symbol] = { + val directlyInheritedTraits = decorateSymbol(sym).directlyInheritedTraits + val directlyInheritedTraitsSet = directlyInheritedTraits.toSet + val allBaseClasses = directlyInheritedTraits.iterator.flatMap(_.symbol.asClass.baseClasses.drop(1)).toSet + val superCalls = superCallsMap.getOrElse(sym, Set.empty) + val additional = (superCalls -- directlyInheritedTraitsSet).filter(_.is(Flags.Trait)) +// if (additional.nonEmpty) +// println(s"$fullName: adding supertraits $additional") + directlyInheritedTraits.filter(t => !allBaseClasses(t) || superCalls(t)) ++ additional + } + + /** + * True for module classes of package level objects. The backend will generate a mirror class for + * such objects. + */ + def isTopLevelModuleClass: Boolean = sym.isModuleClass && sym.isStatic + + /** + * This is basically a re-implementation of sym.isStaticOwner, but using the originalOwner chain. + * + * The problem is that we are interested in a source-level property. Various phases changed the + * symbol's properties in the meantime, mostly lambdalift modified (destructively) the owner. + * Therefore, `sym.isStatic` is not what we want. For example, in + * object T { def f { object U } } + * the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here. + */ + def isOriginallyStaticOwner: Boolean = sym.isStatic + + + def addRemoteRemoteExceptionAnnotation: Unit = () + + def samMethod(): Symbol = + toDenot(sym).info.abstractTermMembers.headOption.getOrElse(toDenot(sym).info.member(nme.apply)).symbol + } + + + implicit def typeHelper(tp: Type): TypeHelper = new TypeHelper { + def member(string: Name): Symbol = tp.member(string.toTermName).symbol + + def isFinalType: Boolean = tp.typeSymbol is Flags.Final //in scalac checks for type parameters. Why? Aren't they gone by backend? + + def underlying: Type = tp match { + case t: TypeProxy => t.underlying + case _ => tp + } + + def paramTypes: List[Type] = tp.firstParamTypes + + def <:<(other: Type): Boolean = tp <:< other + + def memberInfo(s: Symbol): Type = tp.memberInfo(s) + + def decls: List[Symbol] = tp.decls.map(_.symbol).toList + + def members: List[Symbol] = + tp.memberDenots(takeAllFilter, (name, buf) => buf ++= tp.member(name).alternatives).map(_.symbol).toList + + def typeSymbol: Symbol = tp.widenDealias.typeSymbol + + def =:=(other: Type): Boolean = tp =:= other + + def membersBasedOnFlags(excludedFlags: Flags, requiredFlags: Flags): List[Symbol] = + tp.membersBasedOnFlags(FlagSet(requiredFlags), FlagSet(excludedFlags)).map(_.symbol).toList + + def resultType: Type = tp.resultType + + def toTypeKind(ct: BCodeHelpers)(storage: ct.BCInnerClassGen): ct.bTypes.BType = { + import ct.bTypes._ + val defn = ctx.definitions + import coreBTypes._ + import Types._ + /** + * Primitive types are represented as TypeRefs to the class symbol of, for example, scala.Int. + * The `primitiveTypeMap` maps those class symbols to the corresponding PrimitiveBType. + */ + def primitiveOrClassToBType(sym: Symbol): BType = { + assert(sym.isClass, sym) + assert(sym != ArrayClass || isCompilingArray, sym) + primitiveTypeMap.getOrElse(sym.asInstanceOf[ct.bTypes.coreBTypes.bTypes.int.Symbol], + storage.getClassBTypeAndRegisterInnerClass(sym.asInstanceOf[ct.int.Symbol])).asInstanceOf[BType] + } + + /** + * When compiling Array.scala, the type parameter T is not erased and shows up in method + * signatures, e.g. `def apply(i: Int): T`. A TyperRef to T is replaced by ObjectReference. + */ + def nonClassTypeRefToBType(sym: Symbol): ClassBType = { + assert(sym.isType && isCompilingArray, sym) + ObjectReference.asInstanceOf[ct.bTypes.ClassBType] + } + + tp.widenDealias match { + case JavaArrayType(el) =>ArrayBType(el.toTypeKind(ct)(storage)) // Array type such as Array[Int] (kept by erasure) + case t: TypeRef => + t.info match { + + case _ => + if (!t.symbol.isClass) nonClassTypeRefToBType(t.symbol) // See comment on nonClassTypeRefToBType + else primitiveOrClassToBType(t.symbol) // Common reference to a type such as scala.Int or java.lang.String + } + case Types.ClassInfo(_, sym, _, _, _) => primitiveOrClassToBType(sym) // We get here, for example, for genLoadModule, which invokes toTypeKind(moduleClassSymbol.info) + + case t: MethodType => // triggers for LabelDefs + t.resultType.toTypeKind(ct)(storage) + + /* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for + * meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning. + * The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala. + */ + case a @ AnnotatedType(t, _) => + debuglog(s"typeKind of annotated type $a") + t.toTypeKind(ct)(storage) + + /* ExistentialType should (probably) be eliminated by erasure. We know they get here for + * classOf constants: + * class C[T] + * class T { final val k = classOf[C[_]] } + */ + /* case e @ ExistentialType(_, t) => + debuglog(s"typeKind of existential type $e") + t.toTypeKind(ctx)(storage)*/ + + /* The cases below should probably never occur. They are kept for now to avoid introducing + * new compiler crashes, but we added a warning. The compiler / library bootstrap and the + * test suite don't produce any warning. + */ + + case tp => + ctx.warning( + s"an unexpected type representation reached the compiler backend while compiling $currentUnit: $tp. " + + "If possible, please file a bug on issues.scala-lang.org.") + + tp match { + case tp: ThisType if tp.cls == ArrayClass => ObjectReference.asInstanceOf[ct.bTypes.ClassBType] // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test + case tp: ThisType => storage.getClassBTypeAndRegisterInnerClass(tp.cls.asInstanceOf[ct.int.Symbol]) + // case t: SingletonType => primitiveOrClassToBType(t.classSymbol) + case t: SingletonType => t.underlying.toTypeKind(ct)(storage) + case t: RefinedType => t.parent.toTypeKind(ct)(storage) //parents.map(_.toTypeKind(ct)(storage).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b)) + } + } + } + + def summaryString: String = tp.showSummary + + def params: List[Symbol] = + Nil // backend uses this to emmit annotations on parameter lists of forwarders + // to static methods of companion class + // in Dotty this link does not exists: there is no way to get from method type + // to inner symbols of DefDef + // todo: somehow handle. + + def parents: List[Type] = tp.parents + } + + + + object Assign extends AssignDeconstructor { + def _1: Tree = field.lhs + def _2: Tree = field.rhs + } + + object Select extends SelectDeconstructor { + + var desugared: tpd.Select = null + + override def isEmpty: Boolean = + desugared eq null + + def _1: Tree = desugared.qualifier + + def _2: Name = desugared.name + + override def unapply(s: Select): this.type = { + s match { + case t: tpd.Select => desugared = t + case t: Ident => + desugarIdent(t) match { + case Some(t) => desugared = t + case None => desugared = null + } + case _ => desugared = null + } + + this + } + } + + object Apply extends ApplyDeconstructor { + def _1: Tree = field.fun + def _2: List[Tree] = field.args + } + + object If extends IfDeconstructor { + def _1: Tree = field.cond + def _2: Tree = field.thenp + def _3: Tree = field.elsep + } + + object ValDef extends ValDefDeconstructor { + def _1: Modifiers = null + def _2: Name = field.name + def _3: Tree = field.tpt + def _4: Tree = field.rhs + } + + object ApplyDynamic extends ApplyDynamicDeconstructor { + def _1: Tree = ??? + def _2: List[Tree] = ??? + } + + // todo: this product1s should also eventually become name-based pattn matching + object Literal extends LiteralDeconstructor { + def get = field.const + } + + object Throw extends ThrowDeconstructor { + def get = field.args.head + + override def unapply(s: Throw): DottyBackendInterface.this.Throw.type = { + if (s.fun.symbol eq defn.throwMethod) { + field = s + } else { + field = null + } + this + } + } + + object New extends NewDeconstructor { + def get = field.tpt.tpe + } + + object This extends ThisDeconstructor { + def get = field.qual.name + def apply(s: Symbol): This = tpd.This(s.asClass) + } + + object Return extends ReturnDeconstructor { + def get = field.expr + } + + object Ident extends IdentDeconstructor { + def get = field.name + } + + object Alternative extends AlternativeDeconstructor { + def get = field.trees + } + + object Constant extends ConstantDeconstructor { + def get = field.value + } + object ThrownException extends ThrownException { + def unapply(a: Annotation): Option[Symbol] = None // todo + } + + object Try extends TryDeconstructor { + def _1: Tree = field.expr + def _2: List[Tree] = field.cases + def _3: Tree = field.finalizer + } + + object LabelDef extends LabelDeconstructor { + def _1: Name = field.name + def _2: List[Symbol] = field.vparamss.flatMap(_.map(_.symbol)) + def _3: Tree = field.rhs + + override def unapply(s: LabelDef): DottyBackendInterface.this.LabelDef.type = { + if (s.symbol is Flags.Label) this.field = s + else this.field = null + this + } + } + + object Typed extends TypedDeconstrutor { + def _1: Tree = field.expr + def _2: Tree = field.tpt + } + object Super extends SuperDeconstructor { + def _1: Tree = field.qual + def _2: Name = field.mix.name + } + object ArrayValue extends ArrayValueDeconstructor { + def _1: Type = field.tpe match { + case JavaArrayType(elem) => elem + case _ => + ctx.error(s"JavaSeqArray with type ${field.tpe} reached backend: $field", field.pos) + ErrorType + } + def _2: List[Tree] = field.elems + } + object Match extends MatchDeconstructor { + def _1: Tree = field.selector + def _2: List[Tree] = field.cases + } + object Block extends BlockDeconstructor { + def _1: List[Tree] = field.stats + def _2: Tree = field.expr + } + object TypeApply extends TypeApplyDeconstructor { + def _1: Tree = field.fun + def _2: List[Tree] = field.args + } + object CaseDef extends CaseDeconstructor { + def _1: Tree = field.pat + def _2: Tree = field.guard + def _3: Tree = field.body + } + + object DefDef extends DefDefDeconstructor { + def _1: Modifiers = null + def _2: Name = field.name + def _3: List[TypeDef] = field.tparams + def _4: List[List[ValDef]] = field.vparamss + def _5: Tree = field.tpt + def _6: Tree = field.rhs + } + + object ModuleDef extends ModuleDefDeconstructor { + def _1: Modifiers = ??? + def _2: Name = ??? + def _3: Tree = ??? + } + + object Template extends TemplateDeconstructor { + def _1: List[Tree] = field.parents + def _2: ValDef = field.self + def _3: List[Tree] = field.constr :: field.body + } + + object Bind extends BindDeconstructor { + def _1: Name = field.name + def _2: Tree = field.body + } + + object ClassDef extends ClassDefDeconstructor { + def _1: Modifiers = null + def _2: Name = field.name + def _4: Template = field.rhs.asInstanceOf[Template] + def _3: List[TypeDef] = Nil + } + + object Closure extends ClosureDeconstructor { + def _1 = field.env + def _2 = field.meth + def _3 = { + val t = field.tpt.tpe.typeSymbol + if (t.exists) t + else { + val arity = field.meth.tpe.widenDealias.paramTypes.size - _1.size + val returnsUnit = field.meth.tpe.widenDealias.resultType.classSymbol == UnitClass + if (returnsUnit) + ctx.requiredClass(("scala.compat.java8.JProcedure" + arity).toTermName) + else ctx.requiredClass(("scala.compat.java8.JFunction" + arity).toTermName) + } + } + } + + def currentUnit = ctx.compilationUnit +} diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala new file mode 100644 index 000000000..65dcb6c79 --- /dev/null +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -0,0 +1,433 @@ +package dotty.tools.backend.jvm + +import dotty.tools.dotc.CompilationUnit +import dotty.tools.dotc.ast.Trees.{ValDef, PackageDef} +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Phases.Phase +import dotty.tools.dotc.core.Names.TypeName + +import scala.collection.mutable +import scala.tools.asm.{CustomAttr, ClassVisitor, MethodVisitor, FieldVisitor} +import scala.tools.nsc.Settings +import scala.tools.nsc.backend.jvm._ +import dotty.tools.dotc +import dotty.tools.dotc.backend.jvm.DottyPrimitives +import dotty.tools.dotc.transform.Erasure + +import dotty.tools.dotc.interfaces +import java.util.Optional + +import scala.reflect.ClassTag +import dotty.tools.dotc.core._ +import Periods._ +import SymDenotations._ +import Contexts._ +import Types._ +import Symbols._ +import Denotations._ +import Phases._ +import java.lang.AssertionError +import java.io.{ File => JFile } +import scala.tools.asm +import scala.tools.asm.tree._ +import dotty.tools.dotc.util.{Positions, DotClass} +import tpd._ +import StdNames._ +import scala.reflect.io.{Directory, PlainDirectory, AbstractFile} + +import scala.tools.nsc.backend.jvm.opt.LocalOpt + +class GenBCode extends Phase { + def phaseName: String = "genBCode" + private val entryPoints = new mutable.HashSet[Symbol]() + def registerEntryPoint(sym: Symbol) = entryPoints += sym + + private val superCallsMap = new mutable.HashMap[Symbol, Set[ClassSymbol]]() + def registerSuperCall(sym: Symbol, calls: ClassSymbol) = { + val old = superCallsMap.getOrElse(sym, Set.empty) + superCallsMap.put(sym, old + calls) + } + + def outputDir(implicit ctx: Context): AbstractFile = + new PlainDirectory(new Directory(new JFile(ctx.settings.d.value))) + + def run(implicit ctx: Context): Unit = { + new GenBCodePipeline(entryPoints.toList, + new DottyBackendInterface(outputDir, superCallsMap.toMap)(ctx))(ctx).run(ctx.compilationUnit.tpdTree) + entryPoints.clear() + } +} + +class GenBCodePipeline(val entryPoints: List[Symbol], val int: DottyBackendInterface)(implicit val ctx: Context) extends BCodeSyncAndTry{ + + var tree: Tree = _ + + val sourceFile = ctx.compilationUnit.source + + /** Convert a `scala.reflect.io.AbstractFile` into a + * `dotty.tools.dotc.interfaces.AbstractFile`. + */ + private[this] def convertAbstractFile(absfile: scala.reflect.io.AbstractFile): interfaces.AbstractFile = + new interfaces.AbstractFile { + override def name = absfile.name + override def path = absfile.path + override def jfile = Optional.ofNullable(absfile.file) + } + + final class PlainClassBuilder(cunit: CompilationUnit) extends SyncAndTryBuilder(cunit) + + +// class BCodePhase() { + + private var bytecodeWriter : BytecodeWriter = null + private var mirrorCodeGen : JMirrorBuilder = null + private var beanInfoCodeGen : JBeanInfoBuilder = null + + /* ---------------- q1 ---------------- */ + + case class Item1(arrivalPos: Int, cd: TypeDef, cunit: CompilationUnit) { + def isPoison = { arrivalPos == Int.MaxValue } + } + private val poison1 = Item1(Int.MaxValue, null, ctx.compilationUnit) + private val q1 = new java.util.LinkedList[Item1] + + /* ---------------- q2 ---------------- */ + + case class Item2(arrivalPos: Int, + mirror: asm.tree.ClassNode, + plain: asm.tree.ClassNode, + bean: asm.tree.ClassNode, + outFolder: scala.tools.nsc.io.AbstractFile) { + def isPoison = { arrivalPos == Int.MaxValue } + } + + private val poison2 = Item2(Int.MaxValue, null, null, null, null) + private val q2 = new _root_.java.util.LinkedList[Item2] + + /* ---------------- q3 ---------------- */ + + /* + * An item of queue-3 (the last queue before serializing to disk) contains three of these + * (one for each of mirror, plain, and bean classes). + * + * @param jclassName internal name of the class + * @param jclassBytes bytecode emitted for the class SubItem3 represents + */ + case class SubItem3( + jclassName: String, + jclassBytes: Array[Byte] + ) + + case class Item3(arrivalPos: Int, + mirror: SubItem3, + plain: SubItem3, + bean: SubItem3, + outFolder: scala.tools.nsc.io.AbstractFile) { + + def isPoison = { arrivalPos == Int.MaxValue } + } + private val i3comparator = new java.util.Comparator[Item3] { + override def compare(a: Item3, b: Item3) = { + if (a.arrivalPos < b.arrivalPos) -1 + else if (a.arrivalPos == b.arrivalPos) 0 + else 1 + } + } + private val poison3 = Item3(Int.MaxValue, null, null, null, null) + private val q3 = new java.util.PriorityQueue[Item3](1000, i3comparator) + + /* + * Pipeline that takes ClassDefs from queue-1, lowers them into an intermediate form, placing them on queue-2 + */ + class Worker1(needsOutFolder: Boolean) { + + val caseInsensitively = scala.collection.mutable.Map.empty[String, Symbol] + + def run(): Unit = { + while (true) { + val item = q1.poll + if (item.isPoison) { + q2 add poison2 + return + } + else { + try { /*withCurrentUnit(item.cunit)*/(visit(item)) } + catch { + case ex: Throwable => + ex.printStackTrace() + ctx.error(s"Error while emitting ${int.sourceFileFor(item.cunit)}\n${ex.getMessage}") + } + } + } + } + + /* + * Checks for duplicate internal names case-insensitively, + * builds ASM ClassNodes for mirror, plain, and bean classes; + * enqueues them in queue-2. + * + */ + def visit(item: Item1) = { + val Item1(arrivalPos, cd, cunit) = item + val claszSymbol = cd.symbol + + // GenASM checks this before classfiles are emitted, https://github.com/scala/scala/commit/e4d1d930693ac75d8eb64c2c3c69f2fc22bec739 + // todo: add back those checks + /*val lowercaseJavaClassName = claszSymbol.javaClassName.toLowerCase + caseInsensitively.get(lowercaseJavaClassName) match { + case None => + caseInsensitively.put(lowercaseJavaClassName, claszSymbol) + case Some(dupClassSym) => + reporter.warning( + claszSymbol.pos, + s"Class ${claszSymbol.javaClassName} differs only in case from ${dupClassSym.javaClassName}. " + + "Such classes will overwrite one another on case-insensitive filesystems." + ) + }*/ + + // -------------- mirror class, if needed -------------- + val mirrorC = + if (int.symHelper(claszSymbol).isTopLevelModuleClass) { + if (claszSymbol.companionClass == NoSymbol) { + mirrorCodeGen.genMirrorClass(claszSymbol, cunit) + } else { + ctx.log(s"No mirror class for module with linked class: ${claszSymbol.fullName}") + null + } + } else null + + // -------------- "plain" class -------------- + val pcb = new PlainClassBuilder(cunit) + pcb.genPlainClass(cd) + val outF = if (needsOutFolder) getOutFolder(claszSymbol, pcb.thisName) else null; + val plainC = pcb.cnode + + if (claszSymbol.isClass) // @DarkDimius is this test needed here? + for (binary <- ctx.compilationUnit.pickled.get(claszSymbol.asClass)) { + val dataAttr = new CustomAttr(nme.TASTYATTR.toString, binary) + (if (mirrorC ne null) mirrorC else plainC).visitAttribute(dataAttr) + } + + // -------------- bean info class, if needed -------------- + val beanC = + if (claszSymbol hasAnnotation int.BeanInfoAttr) { + beanInfoCodeGen.genBeanInfoClass( + claszSymbol, cunit, + int.symHelper(claszSymbol).fieldSymbols, + int.symHelper(claszSymbol).methodSymbols + ) + } else null + + // ----------- hand over to pipeline-2 + + val item2 = + Item2(arrivalPos, + mirrorC, plainC, beanC, + outF) + + q2 add item2 // at the very end of this method so that no Worker2 thread starts mutating before we're done. + + } // end of method visit(Item1) + + } // end of class BCodePhase.Worker1 + + /* + * Pipeline that takes ClassNodes from queue-2. The unit of work depends on the optimization level: + * + * (a) no optimization involves: + * - converting the plain ClassNode to byte array and placing it on queue-3 + */ + class Worker2 { + lazy val localOpt = new LocalOpt(new Settings()) + + def localOptimizations(classNode: ClassNode): Unit = { + /*BackendStats.timed(BackendStats.methodOptTimer)*/(localOpt.methodOptimizations(classNode)) + } + + def run(): Unit = { + while (true) { + val item = q2.poll + if (item.isPoison) { + q3 add poison3 + return + } + else { + try { + localOptimizations(item.plain) + addToQ3(item) + } catch { + case ex: Throwable => + ex.printStackTrace() + ctx.error(s"Error while emitting ${item.plain.name}\n${ex.getMessage}") + } + } + } + } + + private def addToQ3(item: Item2) = { + + def getByteArray(cn: asm.tree.ClassNode): Array[Byte] = { + val cw = new CClassWriter(extraProc) + cn.accept(cw) + cw.toByteArray + } + + val Item2(arrivalPos, mirror, plain, bean, outFolder) = item + + val mirrorC = if (mirror == null) null else SubItem3(mirror.name, getByteArray(mirror)) + val plainC = SubItem3(plain.name, getByteArray(plain)) + val beanC = if (bean == null) null else SubItem3(bean.name, getByteArray(bean)) + + if (AsmUtils.traceSerializedClassEnabled && plain.name.contains(AsmUtils.traceSerializedClassPattern)) { + if (mirrorC != null) AsmUtils.traceClass(mirrorC.jclassBytes) + AsmUtils.traceClass(plainC.jclassBytes) + if (beanC != null) AsmUtils.traceClass(beanC.jclassBytes) + } + + q3 add Item3(arrivalPos, mirrorC, plainC, beanC, outFolder) + + } + + } // end of class BCodePhase.Worker2 + + var arrivalPos = 0 + + /* + * A run of the BCodePhase phase comprises: + * + * (a) set-up steps (most notably supporting maps in `BCodeTypes`, + * but also "the" writer where class files in byte-array form go) + * + * (b) building of ASM ClassNodes, their optimization and serialization. + * + * (c) tear down (closing the classfile-writer and clearing maps) + * + */ + def run(t: Tree) = { + this.tree = t + + // val bcodeStart = Statistics.startTimer(BackendStats.bcodeTimer) + + // val initStart = Statistics.startTimer(BackendStats.bcodeInitTimer) + arrivalPos = 0 // just in case + // scalaPrimitives.init() + bTypes.intializeCoreBTypes() + // Statistics.stopTimer(BackendStats.bcodeInitTimer, initStart) + + // initBytecodeWriter invokes fullName, thus we have to run it before the typer-dependent thread is activated. + bytecodeWriter = initBytecodeWriter(entryPoints) + mirrorCodeGen = new JMirrorBuilder + beanInfoCodeGen = new JBeanInfoBuilder + + val needsOutfileForSymbol = bytecodeWriter.isInstanceOf[ClassBytecodeWriter] + buildAndSendToDisk(needsOutfileForSymbol) + + // closing output files. + bytecodeWriter.close() + // Statistics.stopTimer(BackendStats.bcodeTimer, bcodeStart) + + if (ctx.compilerCallback != null) + ctx.compilerCallback.onSourceCompiled(sourceFile) + + /* TODO Bytecode can be verified (now that all classfiles have been written to disk) + * + * (1) asm.util.CheckAdapter.verify() + * public static void verify(ClassReader cr, ClassLoader loader, boolean dump, PrintWriter pw) + * passing a custom ClassLoader to verify inter-dependent classes. + * Alternatively, + * - an offline-bytecode verifier could be used (e.g. Maxine brings one as separate tool). + * - -Xverify:all + * + * (2) if requested, check-java-signatures, over and beyond the syntactic checks in `getGenericSignature()` + * + */ + } + + /* + * Sequentially: + * (a) place all ClassDefs in queue-1 + * (b) dequeue one at a time from queue-1, convert it to ASM ClassNode, place in queue-2 + * (c) dequeue one at a time from queue-2, convert it to byte-array, place in queue-3 + * (d) serialize to disk by draining queue-3. + */ + private def buildAndSendToDisk(needsOutFolder: Boolean) = { + + feedPipeline1() + // val genStart = Statistics.startTimer(BackendStats.bcodeGenStat) + (new Worker1(needsOutFolder)).run() + // Statistics.stopTimer(BackendStats.bcodeGenStat, genStart) + + (new Worker2).run() + + // val writeStart = Statistics.startTimer(BackendStats.bcodeWriteTimer) + drainQ3() + // Statistics.stopTimer(BackendStats.bcodeWriteTimer, writeStart) + + } + + /* Feed pipeline-1: place all ClassDefs on q1, recording their arrival position. */ + private def feedPipeline1() = { + def gen(tree: Tree): Unit = { + tree match { + case EmptyTree => () + case PackageDef(_, stats) => stats foreach gen + case ValDef(name, tpt, rhs) => () // module val not emitted + case cd: TypeDef => + q1 add Item1(arrivalPos, cd, int.currentUnit) + arrivalPos += 1 + } + } + gen(tree) + q1 add poison1 + } + + /* Pipeline that writes classfile representations to disk. */ + private def drainQ3() = { + + def sendToDisk(cfr: SubItem3, outFolder: scala.tools.nsc.io.AbstractFile): Unit = { + if (cfr != null){ + val SubItem3(jclassName, jclassBytes) = cfr + try { + val outFile = + if (outFolder == null) null + else getFileForClassfile(outFolder, jclassName, ".class") + bytecodeWriter.writeClass(jclassName, jclassName, jclassBytes, outFile) + + val className = jclassName.replace('/', '.') + if (ctx.compilerCallback != null) + ctx.compilerCallback.onClassGenerated(sourceFile, convertAbstractFile(outFile), className) + if (ctx.sbtCallback != null) + ctx.sbtCallback.generatedClass(sourceFile.jfile.orElse(null), outFile.file, className) + } + catch { + case e: FileConflictException => + ctx.error(s"error writing $jclassName: ${e.getMessage}") + } + } + } + + var moreComing = true + // `expected` denotes the arrivalPos whose Item3 should be serialized next + var expected = 0 + + while (moreComing) { + val incoming = q3.poll + moreComing = !incoming.isPoison + if (moreComing) { + val item = incoming + val outFolder = item.outFolder + sendToDisk(item.mirror, outFolder) + sendToDisk(item.plain, outFolder) + sendToDisk(item.bean, outFolder) + expected += 1 + } + } + + // we're done + assert(q1.isEmpty, s"Some ClassDefs remained in the first queue: $q1") + assert(q2.isEmpty, s"Some classfiles remained in the second queue: $q2") + assert(q3.isEmpty, s"Some classfiles weren't written to disk: $q3") + + } + //} // end of class BCodePhase +} diff --git a/compiler/src/dotty/tools/backend/jvm/LabelDefs.scala b/compiler/src/dotty/tools/backend/jvm/LabelDefs.scala new file mode 100644 index 000000000..371396e36 --- /dev/null +++ b/compiler/src/dotty/tools/backend/jvm/LabelDefs.scala @@ -0,0 +1,223 @@ +package dotty.tools.backend.jvm + +import dotty.tools.dotc.ast.Trees.Thicket +import dotty.tools.dotc.ast.{Trees, tpd} +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Types +import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransform, MiniPhase, MiniPhaseTransform} +import dotty.tools.dotc +import dotty.tools.dotc.backend.jvm.DottyPrimitives +import dotty.tools.dotc.core.Flags.FlagSet +import dotty.tools.dotc.transform.Erasure +import dotty.tools.dotc.transform.SymUtils._ +import java.io.{File => JFile} + +import scala.collection.generic.Clearable +import scala.collection.mutable +import scala.collection.mutable.{ListBuffer, ArrayBuffer} +import scala.reflect.ClassTag +import scala.reflect.internal.util.WeakHashSet +import scala.reflect.io.{Directory, PlainDirectory, AbstractFile} +import scala.tools.asm.{ClassVisitor, FieldVisitor, MethodVisitor} +import scala.tools.nsc.backend.jvm.{BCodeHelpers, BackendInterface} +import dotty.tools.dotc.core._ +import Periods._ +import SymDenotations._ +import Contexts._ +import Types._ +import Symbols._ +import Denotations._ +import Phases._ +import java.lang.AssertionError +import dotty.tools.dotc.util.Positions.Position +import Decorators._ +import tpd._ +import Flags._ +import StdNames.nme + +/** + * Verifies that each Label DefDef has only a single address to jump back and + * reorders them such that they are not nested and this address is a fall-through address for JVM + * + * ei such code + * + * + * <label> def foo(i: Int) = { + * <label> def bar = 0 + * <label> def dough(i: Int) = if (i == 0) bar else foo(i-1) + * dough(i) + * } + * + * foo(100) + * + * will get rewritten to + * + * \ + * <label> def foo(i: Int) = dough(i) + * <label> def dough(i: Int) = if (i == 0) bar else foo(i-1) + * <label> def bar = 2 + * foo(100) + * + * Proposed way to generate this pattern in backend is: + * + * foo(100) + * <jump foo> + * <label> def foo(i: Int) = dough(i) + * // <jump a> // unreachable + * <label> def dough(i: Int) = if (i == 0) bar else foo(i-1) + * // <jump a> // unreachable + * <label> def bar = 2 + * // <jump a> // unreachable + * <asm point a> + * + * Unreachable jumps will be eliminated by local dead code analysis. + * After JVM is smart enough to remove next-line jumps + * + * Note that Label DefDefs can be only nested in Block, otherwise no one would be able to call them + * Other DefDefs are eliminated + */ +class LabelDefs extends MiniPhaseTransform { + def phaseName: String = "labelDef" + + val queue = new ArrayBuffer[Tree]() + val beingAppended = new mutable.HashSet[Symbol]() + var labelLevel = 0 + + override def transformDefDef(tree: tpd.DefDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + if (tree.symbol is Flags.Label) tree + else { + collectLabelDefs.clear + val newRhs = collectLabelDefs.transform(tree.rhs) + val labelCalls = collectLabelDefs.labelCalls + var entryPoints = collectLabelDefs.parentLabelCalls + var labelDefs = collectLabelDefs.labelDefs + var callCounts = collectLabelDefs.callCounts + + // make sure that for every label there's a single location it should return and single entry point + // if theres already a location that it returns to that's a failure + val disallowed = new mutable.HashMap[Symbol, Tree]() + queue.sizeHint(labelCalls.size + entryPoints.size) + + def putLabelDefsNearCallees = new TreeMap() { + + override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = { + tree match { + case t: Apply if (entryPoints.contains(t)) => + entryPoints = entryPoints - t + labelLevel = labelLevel + 1 + val r = Block(moveLabels(t), t) + labelLevel = labelLevel - 1 + if (labelLevel == 0) beingAppended.clear() + r + case _ => if (entryPoints.nonEmpty && labelDefs.nonEmpty) super.transform(tree) else tree + } + + } + } + + def moveLabels(entryPoint: Apply): List[Tree] = { + val entrySym = entryPoint.symbol + if ((entrySym is Flags.Label) && labelDefs.contains(entrySym)) { + val visitedNow = new mutable.HashMap[Symbol, Tree]() + val treesToAppend = new ArrayBuffer[Tree]() // order matters. parents should go first + treesToAppend += labelDefs(entrySym) + queue.clear() + + var visited = 0 + queue += entryPoint + while (visited < queue.size) { + val owningLabelDefSym = queue(visited).symbol + for (call <- labelCalls(owningLabelDefSym)) { + val callSym = call.symbol + if (!beingAppended.contains(callSym)) { + if (disallowed.contains(callSym)) { + val oldCall = disallowed(callSym) + ctx.error(s"Multiple return locations for Label $oldCall and $call", callSym.pos) + } else { + if ((!visitedNow.contains(callSym)) && labelDefs.contains(callSym)) { + val defTree = labelDefs(callSym) + visitedNow.put(callSym, defTree) + val callCount = callCounts(callSym) + if (callCount > 1) { + if (!treesToAppend.contains(defTree)) { + treesToAppend += defTree + queue += call + + } + } else if (entryPoint.symbol ne callSym) entryPoints += call + } + } + } + } + + visited += 1 + } + beingAppended ++= treesToAppend.map(_.symbol) + treesToAppend.toList.map(putLabelDefsNearCallees.transform) + } else Nil + } + + + val res = cpy.DefDef(tree)(rhs = putLabelDefsNearCallees.transform(newRhs)) + + res + } + } + + object collectLabelDefs extends TreeMap() { + + // label calls from this DefDef + var parentLabelCalls: mutable.Set[Tree] = new mutable.HashSet[Tree]() + var callCounts: mutable.Map[Symbol, Int] = new mutable.HashMap[Symbol, Int]().withDefaultValue(0) + + def shouldMoveLabel = true + + // labelSymbol -> Defining tree + val labelDefs = new mutable.HashMap[Symbol, Tree]() + // owner -> all calls by this owner + val labelCalls = new mutable.HashMap[Symbol, mutable.Set[Tree]]() + var owner: Symbol = null + + def clear = { + parentLabelCalls.clear() + labelDefs.clear() + labelCalls.clear() + } + + override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { + case t: Template => t + case t: Block => + val r = super.transform(t) + r match { + case t: Block if t.stats.isEmpty => t.expr + case _ => r + } + case t: DefDef => + assert(t.symbol is Flags.Label) + + val st = parentLabelCalls + parentLabelCalls = new mutable.HashSet[Tree]() + val symt = owner + owner = t.symbol + + val r = super.transform(tree) + + owner = symt + labelCalls(r.symbol) = parentLabelCalls + parentLabelCalls = st + + if (shouldMoveLabel) { + labelDefs(r.symbol) = r + EmptyTree + } else r + case t: Apply if t.symbol is Flags.Label => + val sym = t.symbol + parentLabelCalls = parentLabelCalls + t + if (owner != sym) callCounts(sym) = callCounts(sym) + 1 + super.transform(tree) + case _ => + super.transform(tree) + + } + } +} diff --git a/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala b/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala new file mode 100644 index 000000000..0027defa7 --- /dev/null +++ b/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala @@ -0,0 +1,417 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + +package dotty.tools.dotc +package backend.jvm + +import dotty.tools.backend.jvm.GenBCodePipeline +import dotty.tools.dotc.ast.Trees.Select +import dotty.tools.dotc.ast.tpd._ +import dotty.tools.dotc.core.Names.TermName +import dotty.tools.dotc.core.StdNames +import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.core.Types.{JavaArrayType, ErrorType, Type} + +import scala.collection.{ mutable, immutable } + +import core.Contexts.Context +import core.Symbols.{Symbol, NoSymbol} + +/** Scala primitive operations are represented as methods in `Any` and + * `AnyVal` subclasses. Here we demultiplex them by providing a mapping + * from their symbols to integers. Different methods exist for + * different value types, but with the same meaning (like plus, minus, + * etc.). They will all be mapped to the same int. + * + * Note: The three equal methods have the following semantics: + * - `"=="` checks for `null`, and if non-null, calls + * `java.lang.Object.equals` + * `(class: Any; modifier: final)`. Primitive: `EQ` + * - `"eq"` usual reference comparison + * `(class: AnyRef; modifier: final)`. Primitive: `ID` + * - `"equals"` user-defined equality (Java semantics) + * `(class: Object; modifier: none)`. Primitive: `EQUALS` + * + * Inspired from the `scalac` compiler. + */ +class DottyPrimitives(ctx: Context) { + import scala.tools.nsc.backend.ScalaPrimitives._ + + private lazy val primitives: immutable.Map[Symbol, Int] = init + + /** Return the code for the given symbol. */ + def getPrimitive(sym: Symbol): Int = { + primitives(sym) + } + + /** + * Return the primitive code of the given operation. If the + * operation is an array get/set, we inspect the type of the receiver + * to demux the operation. + * + * @param fun The method symbol + * @param tpe The type of the receiver object. It is used only for array + * operations + */ + def getPrimitive(app: Apply, tpe: Type)(implicit ctx: Context): Int = { + val fun = app.fun.symbol + val defn = ctx.definitions + val code = app.fun match { + case Select(_, nme.primitive.arrayLength) => + LENGTH + case Select(_, nme.primitive.arrayUpdate) => + UPDATE + case Select(_, nme.primitive.arrayApply) => + APPLY + case _ => getPrimitive(fun) + } + + def elementType: Type = tpe.widenDealias match { + case defn.ArrayOf(el) => el + case JavaArrayType(el) => el + case _ => + ctx.error(s"expected Array $tpe") + ErrorType + } + + code match { + + case APPLY => + defn.scalaClassName(elementType) match { + case tpnme.Boolean => ZARRAY_GET + case tpnme.Byte => BARRAY_GET + case tpnme.Short => SARRAY_GET + case tpnme.Char => CARRAY_GET + case tpnme.Int => IARRAY_GET + case tpnme.Long => LARRAY_GET + case tpnme.Float => FARRAY_GET + case tpnme.Double => DARRAY_GET + case _ => OARRAY_GET + } + + case UPDATE => + defn.scalaClassName(elementType) match { + case tpnme.Boolean => ZARRAY_SET + case tpnme.Byte => BARRAY_SET + case tpnme.Short => SARRAY_SET + case tpnme.Char => CARRAY_SET + case tpnme.Int => IARRAY_SET + case tpnme.Long => LARRAY_SET + case tpnme.Float => FARRAY_SET + case tpnme.Double => DARRAY_SET + case _ => OARRAY_SET + } + + case LENGTH => + defn.scalaClassName(elementType) match { + case tpnme.Boolean => ZARRAY_LENGTH + case tpnme.Byte => BARRAY_LENGTH + case tpnme.Short => SARRAY_LENGTH + case tpnme.Char => CARRAY_LENGTH + case tpnme.Int => IARRAY_LENGTH + case tpnme.Long => LARRAY_LENGTH + case tpnme.Float => FARRAY_LENGTH + case tpnme.Double => DARRAY_LENGTH + case _ => OARRAY_LENGTH + } + + case _ => + code + } + } + + /** Initialize the primitive map */ + private def init: immutable.Map[Symbol, Int] = { + + implicit val ctx: Context = this.ctx + + import core.Symbols.defn + val primitives = new mutable.HashMap[Symbol, Int]() + + /** Add a primitive operation to the map */ + def addPrimitive(s: Symbol, code: Int): Unit = { + assert(!(primitives contains s), "Duplicate primitive " + s) + primitives(s) = code + } + + def addPrimitives(cls: Symbol, method: TermName, code: Int)(implicit ctx: Context): Unit = { + val alts = cls.info.member(method).alternatives.map(_.symbol) + if (alts.isEmpty) + ctx.error(s"Unknown primitive method $cls.$method") + else alts foreach (s => + addPrimitive(s, + s.info.paramTypess match { + case List(tp :: _) if code == ADD && tp =:= ctx.definitions.StringType => CONCAT + case _ => code + } + ) + ) + } + + // scala.Any + addPrimitive(defn.Any_==, EQ) + addPrimitive(defn.Any_!=, NE) + addPrimitive(defn.Any_isInstanceOf, IS) + addPrimitive(defn.Any_asInstanceOf, AS) + addPrimitive(defn.Any_##, HASH) + + // java.lang.Object + addPrimitive(defn.Object_eq, ID) + addPrimitive(defn.Object_ne, NI) + /* addPrimitive(defn.Any_==, EQ) + addPrimitive(defn.Any_!=, NE)*/ + addPrimitive(defn.Object_synchronized, SYNCHRONIZED) + /*addPrimitive(defn.Any_isInstanceOf, IS) + addPrimitive(defn.Any_asInstanceOf, AS)*/ + + // java.lang.String + addPrimitive(defn.String_+, CONCAT) + + import core.StdNames.nme + + // scala.Array + lazy val ArrayClass = defn.ArrayClass + addPrimitives(ArrayClass, nme.length, LENGTH) + addPrimitives(ArrayClass, nme.apply, APPLY) + addPrimitives(ArrayClass, nme.update, UPDATE) + + // scala.Boolean + lazy val BooleanClass = defn.BooleanClass + addPrimitives(BooleanClass, nme.EQ, EQ) + addPrimitives(BooleanClass, nme.NE, NE) + addPrimitives(BooleanClass, nme.UNARY_!, ZNOT) + addPrimitives(BooleanClass, nme.ZOR, ZOR) + addPrimitives(BooleanClass, nme.ZAND, ZAND) + addPrimitives(BooleanClass, nme.OR, OR) + addPrimitives(BooleanClass, nme.AND, AND) + addPrimitives(BooleanClass, nme.XOR, XOR) + + // scala.Byte + lazy val ByteClass = defn.ByteClass + addPrimitives(ByteClass, nme.EQ, EQ) + addPrimitives(ByteClass, nme.NE, NE) + addPrimitives(ByteClass, nme.ADD, ADD) + addPrimitives(ByteClass, nme.SUB, SUB) + addPrimitives(ByteClass, nme.MUL, MUL) + addPrimitives(ByteClass, nme.DIV, DIV) + addPrimitives(ByteClass, nme.MOD, MOD) + addPrimitives(ByteClass, nme.LT, LT) + addPrimitives(ByteClass, nme.LE, LE) + addPrimitives(ByteClass, nme.GT, GT) + addPrimitives(ByteClass, nme.GE, GE) + addPrimitives(ByteClass, nme.XOR, XOR) + addPrimitives(ByteClass, nme.OR, OR) + addPrimitives(ByteClass, nme.AND, AND) + addPrimitives(ByteClass, nme.LSL, LSL) + addPrimitives(ByteClass, nme.LSR, LSR) + addPrimitives(ByteClass, nme.ASR, ASR) + // conversions + addPrimitives(ByteClass, nme.toByte, B2B) + addPrimitives(ByteClass, nme.toShort, B2S) + addPrimitives(ByteClass, nme.toChar, B2C) + addPrimitives(ByteClass, nme.toInt, B2I) + addPrimitives(ByteClass, nme.toLong, B2L) + // unary methods + addPrimitives(ByteClass, nme.UNARY_+, POS) + addPrimitives(ByteClass, nme.UNARY_-, NEG) + addPrimitives(ByteClass, nme.UNARY_~, NOT) + + addPrimitives(ByteClass, nme.toFloat, B2F) + addPrimitives(ByteClass, nme.toDouble, B2D) + + // scala.Short + lazy val ShortClass = defn.ShortClass + addPrimitives(ShortClass, nme.EQ, EQ) + addPrimitives(ShortClass, nme.NE, NE) + addPrimitives(ShortClass, nme.ADD, ADD) + addPrimitives(ShortClass, nme.SUB, SUB) + addPrimitives(ShortClass, nme.MUL, MUL) + addPrimitives(ShortClass, nme.DIV, DIV) + addPrimitives(ShortClass, nme.MOD, MOD) + addPrimitives(ShortClass, nme.LT, LT) + addPrimitives(ShortClass, nme.LE, LE) + addPrimitives(ShortClass, nme.GT, GT) + addPrimitives(ShortClass, nme.GE, GE) + addPrimitives(ShortClass, nme.XOR, XOR) + addPrimitives(ShortClass, nme.OR, OR) + addPrimitives(ShortClass, nme.AND, AND) + addPrimitives(ShortClass, nme.LSL, LSL) + addPrimitives(ShortClass, nme.LSR, LSR) + addPrimitives(ShortClass, nme.ASR, ASR) + // conversions + addPrimitives(ShortClass, nme.toByte, S2B) + addPrimitives(ShortClass, nme.toShort, S2S) + addPrimitives(ShortClass, nme.toChar, S2C) + addPrimitives(ShortClass, nme.toInt, S2I) + addPrimitives(ShortClass, nme.toLong, S2L) + // unary methods + addPrimitives(ShortClass, nme.UNARY_+, POS) + addPrimitives(ShortClass, nme.UNARY_-, NEG) + addPrimitives(ShortClass, nme.UNARY_~, NOT) + + addPrimitives(ShortClass, nme.toFloat, S2F) + addPrimitives(ShortClass, nme.toDouble, S2D) + + // scala.Char + lazy val CharClass = defn.CharClass + addPrimitives(CharClass, nme.EQ, EQ) + addPrimitives(CharClass, nme.NE, NE) + addPrimitives(CharClass, nme.ADD, ADD) + addPrimitives(CharClass, nme.SUB, SUB) + addPrimitives(CharClass, nme.MUL, MUL) + addPrimitives(CharClass, nme.DIV, DIV) + addPrimitives(CharClass, nme.MOD, MOD) + addPrimitives(CharClass, nme.LT, LT) + addPrimitives(CharClass, nme.LE, LE) + addPrimitives(CharClass, nme.GT, GT) + addPrimitives(CharClass, nme.GE, GE) + addPrimitives(CharClass, nme.XOR, XOR) + addPrimitives(CharClass, nme.OR, OR) + addPrimitives(CharClass, nme.AND, AND) + addPrimitives(CharClass, nme.LSL, LSL) + addPrimitives(CharClass, nme.LSR, LSR) + addPrimitives(CharClass, nme.ASR, ASR) + // conversions + addPrimitives(CharClass, nme.toByte, C2B) + addPrimitives(CharClass, nme.toShort, C2S) + addPrimitives(CharClass, nme.toChar, C2C) + addPrimitives(CharClass, nme.toInt, C2I) + addPrimitives(CharClass, nme.toLong, C2L) + // unary methods + addPrimitives(CharClass, nme.UNARY_+, POS) + addPrimitives(CharClass, nme.UNARY_-, NEG) + addPrimitives(CharClass, nme.UNARY_~, NOT) + addPrimitives(CharClass, nme.toFloat, C2F) + addPrimitives(CharClass, nme.toDouble, C2D) + + // scala.Int + lazy val IntClass = defn.IntClass + addPrimitives(IntClass, nme.EQ, EQ) + addPrimitives(IntClass, nme.NE, NE) + addPrimitives(IntClass, nme.ADD, ADD) + addPrimitives(IntClass, nme.SUB, SUB) + addPrimitives(IntClass, nme.MUL, MUL) + addPrimitives(IntClass, nme.DIV, DIV) + addPrimitives(IntClass, nme.MOD, MOD) + addPrimitives(IntClass, nme.LT, LT) + addPrimitives(IntClass, nme.LE, LE) + addPrimitives(IntClass, nme.GT, GT) + addPrimitives(IntClass, nme.GE, GE) + addPrimitives(IntClass, nme.XOR, XOR) + addPrimitives(IntClass, nme.OR, OR) + addPrimitives(IntClass, nme.AND, AND) + addPrimitives(IntClass, nme.LSL, LSL) + addPrimitives(IntClass, nme.LSR, LSR) + addPrimitives(IntClass, nme.ASR, ASR) + // conversions + addPrimitives(IntClass, nme.toByte, I2B) + addPrimitives(IntClass, nme.toShort, I2S) + addPrimitives(IntClass, nme.toChar, I2C) + addPrimitives(IntClass, nme.toInt, I2I) + addPrimitives(IntClass, nme.toLong, I2L) + // unary methods + addPrimitives(IntClass, nme.UNARY_+, POS) + addPrimitives(IntClass, nme.UNARY_-, NEG) + addPrimitives(IntClass, nme.UNARY_~, NOT) + addPrimitives(IntClass, nme.toFloat, I2F) + addPrimitives(IntClass, nme.toDouble, I2D) + + // scala.Long + lazy val LongClass = defn.LongClass + addPrimitives(LongClass, nme.EQ, EQ) + addPrimitives(LongClass, nme.NE, NE) + addPrimitives(LongClass, nme.ADD, ADD) + addPrimitives(LongClass, nme.SUB, SUB) + addPrimitives(LongClass, nme.MUL, MUL) + addPrimitives(LongClass, nme.DIV, DIV) + addPrimitives(LongClass, nme.MOD, MOD) + addPrimitives(LongClass, nme.LT, LT) + addPrimitives(LongClass, nme.LE, LE) + addPrimitives(LongClass, nme.GT, GT) + addPrimitives(LongClass, nme.GE, GE) + addPrimitives(LongClass, nme.XOR, XOR) + addPrimitives(LongClass, nme.OR, OR) + addPrimitives(LongClass, nme.AND, AND) + addPrimitives(LongClass, nme.LSL, LSL) + addPrimitives(LongClass, nme.LSR, LSR) + addPrimitives(LongClass, nme.ASR, ASR) + // conversions + addPrimitives(LongClass, nme.toByte, L2B) + addPrimitives(LongClass, nme.toShort, L2S) + addPrimitives(LongClass, nme.toChar, L2C) + addPrimitives(LongClass, nme.toInt, L2I) + addPrimitives(LongClass, nme.toLong, L2L) + // unary methods + addPrimitives(LongClass, nme.UNARY_+, POS) + addPrimitives(LongClass, nme.UNARY_-, NEG) + addPrimitives(LongClass, nme.UNARY_~, NOT) + addPrimitives(LongClass, nme.toFloat, L2F) + addPrimitives(LongClass, nme.toDouble, L2D) + + // scala.Float + lazy val FloatClass = defn.FloatClass + addPrimitives(FloatClass, nme.EQ, EQ) + addPrimitives(FloatClass, nme.NE, NE) + addPrimitives(FloatClass, nme.ADD, ADD) + addPrimitives(FloatClass, nme.SUB, SUB) + addPrimitives(FloatClass, nme.MUL, MUL) + addPrimitives(FloatClass, nme.DIV, DIV) + addPrimitives(FloatClass, nme.MOD, MOD) + addPrimitives(FloatClass, nme.LT, LT) + addPrimitives(FloatClass, nme.LE, LE) + addPrimitives(FloatClass, nme.GT, GT) + addPrimitives(FloatClass, nme.GE, GE) + // conversions + addPrimitives(FloatClass, nme.toByte, F2B) + addPrimitives(FloatClass, nme.toShort, F2S) + addPrimitives(FloatClass, nme.toChar, F2C) + addPrimitives(FloatClass, nme.toInt, F2I) + addPrimitives(FloatClass, nme.toLong, F2L) + addPrimitives(FloatClass, nme.toFloat, F2F) + addPrimitives(FloatClass, nme.toDouble, F2D) + // unary methods + addPrimitives(FloatClass, nme.UNARY_+, POS) + addPrimitives(FloatClass, nme.UNARY_-, NEG) + + // scala.Double + lazy val DoubleClass = defn.DoubleClass + addPrimitives(DoubleClass, nme.EQ, EQ) + addPrimitives(DoubleClass, nme.NE, NE) + addPrimitives(DoubleClass, nme.ADD, ADD) + addPrimitives(DoubleClass, nme.SUB, SUB) + addPrimitives(DoubleClass, nme.MUL, MUL) + addPrimitives(DoubleClass, nme.DIV, DIV) + addPrimitives(DoubleClass, nme.MOD, MOD) + addPrimitives(DoubleClass, nme.LT, LT) + addPrimitives(DoubleClass, nme.LE, LE) + addPrimitives(DoubleClass, nme.GT, GT) + addPrimitives(DoubleClass, nme.GE, GE) + // conversions + addPrimitives(DoubleClass, nme.toByte, D2B) + addPrimitives(DoubleClass, nme.toShort, D2S) + addPrimitives(DoubleClass, nme.toChar, D2C) + addPrimitives(DoubleClass, nme.toInt, D2I) + addPrimitives(DoubleClass, nme.toLong, D2L) + addPrimitives(DoubleClass, nme.toFloat, D2F) + addPrimitives(DoubleClass, nme.toDouble, D2D) + // unary methods + addPrimitives(DoubleClass, nme.UNARY_+, POS) + addPrimitives(DoubleClass, nme.UNARY_-, NEG) + + + primitives.toMap + } + + def isPrimitive(fun: Tree): Boolean = { + (primitives contains fun.symbol(ctx)) || + (fun.symbol(ctx) == NoSymbol // the only trees that do not have a symbol assigned are array.{update,select,length,clone}} + && (fun match { + case Select(_, StdNames.nme.clone_) => false // but array.clone is NOT a primitive op. + case _ => true + })) + } + +} + |