diff options
Diffstat (limited to 'src')
52 files changed, 1249 insertions, 800 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index a42332f7f2..adaf870c46 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -16,6 +16,7 @@ import java.lang.invoke.LambdaMetafactory import scala.tools.asm import GenBCode._ import BackendReporting._ +import scala.tools.asm.tree.MethodInsnNode /* * @@ -95,12 +96,12 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val thrownKind = tpeTK(expr) // `throw null` is valid although scala.Null (as defined in src/library-aux) isn't a subtype of Throwable. // Similarly for scala.Nothing (again, as defined in src/library-aux). - assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(ThrowableReference).get) + assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(jlThrowableRef).get) genLoad(expr, thrownKind) lineNumber(expr) emit(asm.Opcodes.ATHROW) // ICode enters here into enterIgnoreMode, we'll rely instead on DCE at ClassNode level. - RT_NOTHING // always returns the same, the invoker should know :) + srNothingRef // always returns the same, the invoker should know :) } /* Generate code for primitive arithmetic operations. */ @@ -324,7 +325,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { else { mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) generatedType = - if (tree.symbol == ArrayClass) ObjectReference + if (tree.symbol == ArrayClass) ObjectRef else classBTypeFromSymbol(claszSymbol) } @@ -367,7 +368,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { if (value.tag != UnitTag) (value.tag, expectedType) match { case (IntTag, LONG ) => bc.lconst(value.longValue); generatedType = LONG case (FloatTag, DOUBLE) => bc.dconst(value.doubleValue); generatedType = DOUBLE - case (NullTag, _ ) => bc.emit(asm.Opcodes.ACONST_NULL); generatedType = RT_NULL + case (NullTag, _ ) => bc.emit(asm.Opcodes.ACONST_NULL); generatedType = srNullRef case _ => genConstant(value); generatedType = tpeTK(tree) } @@ -463,7 +464,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case ClazzTag => val toPush: BType = { - toTypeKind(const.typeValue) match { + typeToBType(const.typeValue) match { case kind: PrimitiveBType => boxedClassOfPrimitive(kind) case kind => kind } @@ -474,7 +475,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val sym = const.symbolValue val ownerName = internalName(sym.owner) val fieldName = sym.javaSimpleName.toString - val fieldDesc = toTypeKind(sym.tpe.underlying).descriptor + val fieldDesc = typeToBType(sym.tpe.underlying).descriptor mnode.visitFieldInsn( asm.Opcodes.GETSTATIC, ownerName, @@ -552,8 +553,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { else if (l.isPrimitive) { bc drop l if (cast) { - mnode.visitTypeInsn(asm.Opcodes.NEW, classCastExceptionReference.internalName) - bc dup ObjectReference + mnode.visitTypeInsn(asm.Opcodes.NEW, jlClassCastExceptionRef.internalName) + bc dup ObjectRef emit(asm.Opcodes.ATHROW) } else { bc boolconst false @@ -587,7 +588,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) genLoadArguments(args, paramTKs(app)) genCallMethod(fun.symbol, invokeStyle, app.pos) - generatedType = asmMethodType(fun.symbol).returnType + generatedType = methodBTypeFromSymbol(fun.symbol).returnType // 'new' constructor call: Note: since constructors are // thought to return an instance of what they construct, @@ -637,21 +638,21 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val attachment = app.attachments.get[delambdafy.LambdaMetaFactoryCapable].get genLoadArguments(args, paramTKs(app)) genInvokeDynamicLambda(attachment.target, attachment.arity, attachment.functionalInterface) - generatedType = asmMethodType(fun.symbol).returnType + generatedType = methodBTypeFromSymbol(fun.symbol).returnType case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) => val nativeKind = tpeTK(expr) genLoad(expr, nativeKind) val MethodNameAndType(mname, methodType) = asmBoxTo(nativeKind) - bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, app.pos) - generatedType = boxResultType(fun.symbol) // was toTypeKind(fun.symbol.tpe.resultType) + bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, app.pos) + generatedType = boxResultType(fun.symbol) // was typeToBType(fun.symbol.tpe.resultType) case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) => genLoad(expr) - val boxType = unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe) + val boxType = unboxResultType(fun.symbol) // was typeToBType(fun.symbol.owner.linkedClassOfClass.tpe) generatedType = boxType val MethodNameAndType(mname, methodType) = asmUnboxTo(boxType) - bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, app.pos) + bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, app.pos) case app @ Apply(fun, args) => val sym = fun.symbol @@ -706,13 +707,28 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } else { genCallMethod(sym, invokeStyle, app.pos, hostClass) + // Check if the Apply tree has an InlineAnnotatedAttachment, added by the typer + // for callsites marked `f(): @inline/noinline`. For nullary calls, the attachment + // is on the Select node (not on the Apply node added by UnCurry). + def checkInlineAnnotated(t: Tree): Unit = { + if (t.hasAttachment[InlineAnnotatedAttachment]) bc.jmethod.instructions.getLast match { + case m: MethodInsnNode => + if (app.hasAttachment[NoInlineCallsiteAttachment.type]) noInlineAnnotatedCallsites += m + else inlineAnnotatedCallsites += m + case _ => + } else t match { + case Apply(fun, _) => checkInlineAnnotated(fun) + case _ => + } + } + checkInlineAnnotated(app) } } // end of genNormalMethodCall() genNormalMethodCall() - generatedType = asmMethodType(sym).returnType + generatedType = methodBTypeFromSymbol(sym).returnType } } @@ -942,7 +958,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { asm.Opcodes.GETSTATIC, mbt.internalName /* + "$" */ , strMODULE_INSTANCE_FIELD, - mbt.descriptor // for nostalgics: toTypeKind(module.tpe).descriptor + mbt.descriptor // for nostalgics: typeToBType(module.tpe).descriptor ) } } @@ -981,7 +997,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // Optimization for expressions of the form "" + x. We can avoid the StringBuilder. case List(Literal(Constant("")), arg) => - genLoad(arg, ObjectReference) + genLoad(arg, ObjectRef) genCallMethod(String_valueOf, icodes.opcodes.Static(onInstance = false), arg.pos) case concatenations => @@ -995,7 +1011,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } - StringReference + StringRef } def genCallMethod(method: Symbol, style: InvokeStyle, pos: Position, hostClass0: Symbol = null) { @@ -1021,7 +1037,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val receiver = if (useMethodOwner) methodOwner else hostSymbol val jowner = internalName(receiver) val jname = method.javaSimpleName.toString - val bmType = asmMethodType(method) + val bmType = methodBTypeFromSymbol(method) val mdescr = bmType.descriptor def initModule() { @@ -1060,7 +1076,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { /* Generate the scala ## method. */ def genScalaHash(tree: Tree, applyPos: Position): BType = { genLoadModule(ScalaRunTimeModule) // TODO why load ScalaRunTimeModule if ## has InvokeStyle of Static(false) ? - genLoad(tree, ObjectReference) + genLoad(tree, ObjectRef) genCallMethod(hashMethodSym, icodes.opcodes.Static(onInstance = false), applyPos) INT @@ -1148,8 +1164,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { if (scalaPrimitives.isReferenceEqualityOp(code) && { nonNullSide = ifOneIsNull(l, r); nonNullSide != null } ) { - genLoad(nonNullSide, ObjectReference) - genCZJUMP(success, failure, op, ObjectReference) + genLoad(nonNullSide, ObjectRef) + genCZJUMP(success, failure, op, ObjectRef) } else { val tk = tpeTK(l).maxType(tpeTK(r)) @@ -1236,42 +1252,42 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { else platform.externalEqualsNumObject } else platform.externalEquals } - genLoad(l, ObjectReference) - genLoad(r, ObjectReference) + genLoad(l, ObjectRef) + genLoad(r, ObjectRef) genCallMethod(equalsMethod, icodes.opcodes.Static(onInstance = false), pos) genCZJUMP(success, failure, icodes.NE, BOOL) } else { if (isNull(l)) { // null == expr -> expr eq null - genLoad(r, ObjectReference) - genCZJUMP(success, failure, icodes.EQ, ObjectReference) + genLoad(r, ObjectRef) + genCZJUMP(success, failure, icodes.EQ, ObjectRef) } else if (isNull(r)) { // expr == null -> expr eq null - genLoad(l, ObjectReference) - genCZJUMP(success, failure, icodes.EQ, ObjectReference) + genLoad(l, ObjectRef) + genCZJUMP(success, failure, icodes.EQ, ObjectRef) } else if (isNonNullExpr(l)) { // SI-7852 Avoid null check if L is statically non-null. - genLoad(l, ObjectReference) - genLoad(r, ObjectReference) + genLoad(l, ObjectRef) + genLoad(r, ObjectRef) genCallMethod(Object_equals, icodes.opcodes.Dynamic, pos) genCZJUMP(success, failure, icodes.NE, BOOL) } else { // l == r -> if (l eq null) r eq null else l.equals(r) - val eqEqTempLocal = locals.makeLocal(ObjectReference, nme.EQEQ_LOCAL_VAR.toString) + val eqEqTempLocal = locals.makeLocal(ObjectRef, nme.EQEQ_LOCAL_VAR.toString) val lNull = new asm.Label val lNonNull = new asm.Label - genLoad(l, ObjectReference) - genLoad(r, ObjectReference) + genLoad(l, ObjectRef) + genLoad(r, ObjectRef) locals.store(eqEqTempLocal) - bc dup ObjectReference - genCZJUMP(lNull, lNonNull, icodes.EQ, ObjectReference) + bc dup ObjectRef + genCZJUMP(lNull, lNonNull, icodes.EQ, ObjectRef) markProgramPoint(lNull) - bc drop ObjectReference + bc drop ObjectRef locals.load(eqEqTempLocal) - genCZJUMP(success, failure, icodes.EQ, ObjectReference) + genCZJUMP(success, failure, icodes.EQ, ObjectRef) markProgramPoint(lNonNull) locals.load(eqEqTempLocal) @@ -1293,16 +1309,16 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { new asm.Handle(if (lambdaTarget.hasFlag(Flags.STATIC)) asm.Opcodes.H_INVOKESTATIC else asm.Opcodes.H_INVOKEVIRTUAL, classBTypeFromSymbol(lambdaTarget.owner).internalName, lambdaTarget.name.toString, - asmMethodType(lambdaTarget).descriptor) + methodBTypeFromSymbol(lambdaTarget).descriptor) val receiver = if (isStaticMethod) Nil else lambdaTarget.owner :: Nil val (capturedParams, lambdaParams) = lambdaTarget.paramss.head.splitAt(lambdaTarget.paramss.head.length - arity) // Requires https://github.com/scala/scala-java8-compat on the runtime classpath - val invokedType = asm.Type.getMethodDescriptor(asmType(functionalInterface), (receiver ::: capturedParams).map(sym => toTypeKind(sym.info).toASMType): _*) + val invokedType = asm.Type.getMethodDescriptor(asmType(functionalInterface), (receiver ::: capturedParams).map(sym => typeToBType(sym.info).toASMType): _*) - val constrainedType = new MethodBType(lambdaParams.map(p => toTypeKind(p.tpe)), toTypeKind(lambdaTarget.tpe.resultType)).toASMType + val constrainedType = new MethodBType(lambdaParams.map(p => typeToBType(p.tpe)), typeToBType(lambdaTarget.tpe.resultType)).toASMType val sam = functionalInterface.info.decls.find(_.isDeferred).getOrElse(functionalInterface.info.member(nme.apply)) val samName = sam.name.toString - val samMethodType = asmMethodType(sam).toASMType + val samMethodType = methodBTypeFromSymbol(sam).toASMType val flags = LambdaMetafactory.FLAG_SERIALIZABLE | LambdaMetafactory.FLAG_MARKERS diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index bc3bdfc6ba..d501d4239b 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -192,20 +192,18 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } /* - * Populates the InnerClasses JVM attribute with `refedInnerClasses`. - * In addition to inner classes mentioned somewhere in `jclass` (where `jclass` is a class file being emitted) - * `refedInnerClasses` should contain those inner classes defined as direct member classes of `jclass` - * but otherwise not mentioned in `jclass`. + * Populates the InnerClasses JVM attribute with `refedInnerClasses`. See also the doc on inner + * classes in BTypes.scala. * - * `refedInnerClasses` may contain duplicates, - * need not contain the enclosing inner classes of each inner class it lists (those are looked up for consistency). + * `refedInnerClasses` may contain duplicates, need not contain the enclosing inner classes of + * each inner class it lists (those are looked up and included). * - * This method serializes in the InnerClasses JVM attribute in an appropriate order, - * not necessarily that given by `refedInnerClasses`. + * This method serializes in the InnerClasses JVM attribute in an appropriate order, not + * necessarily that given by `refedInnerClasses`. * * can-multi-thread */ - final def addInnerClassesASM(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]) { + final def addInnerClasses(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]) { val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain.get).distinct // sorting ensures nested classes are listed after their enclosing class thus satisfying the Eclipse Java compiler @@ -311,78 +309,28 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { final val emitLines = debugLevel >= 2 final val emitVars = debugLevel >= 3 - /* - * Contains class-symbols that: - * (a) are known to denote inner classes - * (b) are mentioned somewhere in the class being generated. - * - * In other words, the lifetime of `innerClassBufferASM` is associated to "the class being generated". - */ - final val innerClassBufferASM = mutable.Set.empty[ClassBType] - /** - * The class internal name for a given class symbol. If the symbol describes a nested class, the - * ClassBType is added to the innerClassBufferASM. + * The class internal name for a given class symbol. */ final def internalName(sym: Symbol): String = { // For each java class, the scala compiler creates a class and a module (thus a module class). - // If the `sym` is a java module class, we use the java class instead. This ensures that we - // register the class (instead of the module class) in innerClassBufferASM. + // If the `sym` is a java module class, we use the java class instead. This ensures that the + // ClassBType is created from the main class (instead of the module class). // The two symbols have the same name, so the resulting internalName is the same. // Phase travel (exitingPickler) required for SI-6613 - linkedCoC is only reliable in early phases (nesting) val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym - getClassBTypeAndRegisterInnerClass(classSym).internalName - } - - /** - * The ClassBType for a class symbol. If the class is nested, the ClassBType is added to the - * innerClassBufferASM. - * - * TODO: clean up the way we track referenced inner classes. - * doing it during code generation is not correct when the optimizer changes the code. - */ - final def getClassBTypeAndRegisterInnerClass(sym: Symbol): ClassBType = { - val r = classBTypeFromSymbol(sym) - if (r.isNestedClass.get) innerClassBufferASM += r - r + classBTypeFromSymbol(classSym).internalName } /** - * The BType for a type reference. If the result is a ClassBType for a nested class, it is added - * to the innerClassBufferASM. - * TODO: clean up the way we track referenced inner classes. + * The jvm descriptor of a type. */ - final def toTypeKind(t: Type): BType = typeToBType(t) match { - case c: ClassBType if c.isNestedClass.get => - innerClassBufferASM += c - c - case r => r - } + final def descriptor(t: Type): String = typeToBType(t).descriptor /** - * Class components that are nested classes are added to the innerClassBufferASM. - * TODO: clean up the way we track referenced inner classes. + * The jvm descriptor for a symbol. */ - final def asmMethodType(msym: Symbol): MethodBType = { - val r = methodBTypeFromSymbol(msym) - (r.returnType :: r.argumentTypes) foreach { - case c: ClassBType if c.isNestedClass.get => innerClassBufferASM += c - case _ => - } - r - } - - /** - * The jvm descriptor of a type. If `t` references a nested class, its ClassBType is added to - * the innerClassBufferASM. - */ - final def descriptor(t: Type): String = { toTypeKind(t).descriptor } - - /** - * The jvm descriptor for a symbol. If `sym` represents a nested class, its ClassBType is added - * to the innerClassBufferASM. - */ - final def descriptor(sym: Symbol): String = { getClassBTypeAndRegisterInnerClass(sym).descriptor } + final def descriptor(sym: Symbol): String = classBTypeFromSymbol(sym).descriptor } // end of trait BCInnerClassGen @@ -421,7 +369,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { 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, toTypeKind(const.typeValue).toASMType) + case ClazzTag => av.visit(name, typeToBType(const.typeValue).toASMType) case EnumTag => val edesc = descriptor(const.tpe) // the class descriptor of the enumeration class. val evalue = const.symbolValue.name.toString // value the actual enumeration value. @@ -542,11 +490,15 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * must-single-thread */ def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) { - val needsAnnotation = ( - ( isRemoteClass || - isRemote(meth) && isJMethodPublic - ) && !(meth.throwsAnnotations contains definitions.RemoteExceptionClass) - ) + def hasThrowsRemoteException = meth.annotations.exists { + case ThrownException(exc) => exc.typeSymbol == definitions.RemoteExceptionClass + case _ => false + } + val needsAnnotation = { + (isRemoteClass || + isRemote(meth) && isJMethodPublic + ) && !hasThrowsRemoteException + } if (needsAnnotation) { val c = Constant(definitions.RemoteExceptionClass.tpe) val arg = Literal(c) setType c.tpe @@ -561,7 +513,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol) { val moduleName = internalName(module) val methodInfo = module.thisType.memberInfo(m) - val paramJavaTypes: List[BType] = methodInfo.paramTypes map toTypeKind + val paramJavaTypes: List[BType] = methodInfo.paramTypes map typeToBType // val paramNames = 0 until paramJavaTypes.length map ("x_" + _) /* Forwarders must not be marked final, @@ -580,7 +532,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { val (throws, others) = m.annotations partition (_.symbol == definitions.ThrowsClass) val thrownExceptions: List[String] = getExceptions(throws) - val jReturnType = toTypeKind(methodInfo.resultType) + val jReturnType = typeToBType(methodInfo.resultType) val mdesc = MethodBType(paramJavaTypes, jReturnType).descriptor val mirrorMethodName = m.javaSimpleName.toString val mirrorMethod: asm.MethodVisitor = jclass.visitMethod( @@ -605,7 +557,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { index += jparamType.size } - mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, asmMethodType(m).descriptor, false) + mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, methodBTypeFromSymbol(m).descriptor, false) mirrorMethod.visitInsn(jReturnType.typedOpcode(asm.Opcodes.IRETURN)) mirrorMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments @@ -655,8 +607,11 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * must-single-thread */ def getExceptions(excs: List[AnnotationInfo]): List[String] = { - for (ThrownException(exc) <- excs.distinct) - yield internalName(exc) + for (ThrownException(tp) <- excs.distinct) + yield { + val erased = enteringErasure(erasure.erasure(tp.typeSymbol)(tp)) + internalName(erased.typeSymbol) + } } } // end of trait BCForwardersGen @@ -709,7 +664,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { def genMirrorClass(moduleClass: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = { assert(moduleClass.isModuleClass) assert(moduleClass.companionClass == NoSymbol, moduleClass) - innerClassBufferASM.clear() this.cunit = cunit val bType = mirrorClassClassBType(moduleClass) @@ -719,7 +673,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { bType.info.get.flags, bType.internalName, null /* no java-generic-signature */, - ObjectReference.internalName, + ObjectRef.internalName, EMPTY_STRING_ARRAY ) @@ -732,9 +686,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { addForwarders(isRemote(moduleClass), mirrorClass, bType.internalName, moduleClass) - innerClassBufferASM ++= bType.info.get.nestedClasses - addInnerClassesASM(mirrorClass, innerClassBufferASM.toList) - mirrorClass.visitEnd() ("" + moduleClass.name) // this side-effect is necessary, really. @@ -758,18 +709,15 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { def javaSimpleName(s: Symbol): String = { s.javaSimpleName.toString } - innerClassBufferASM.clear() + val beanInfoType = beanInfoClassClassBType(cls) - val flags = javaFlags(cls) - - val beanInfoName = (internalName(cls) + "BeanInfo") val beanInfoClass = new asm.tree.ClassNode beanInfoClass.visit( classfileVersion, - flags, - beanInfoName, + beanInfoType.info.get.flags, + beanInfoType.internalName, null, // no java-generic-signature - "scala/beans/ScalaBeanInfo", + sbScalaBeanInfoRef.internalName, EMPTY_STRING_ARRAY ) @@ -806,7 +754,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { EMPTY_STRING_ARRAY // no throwable exceptions ) - val stringArrayJType: BType = ArrayBType(StringReference) + val stringArrayJType: BType = ArrayBType(StringRef) val conJType: BType = MethodBType( classBTypeFromSymbol(definitions.ClassClass) :: stringArrayJType :: stringArrayJType :: Nil, UNIT @@ -819,7 +767,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { constructor.visitLdcInsn(new java.lang.Integer(fi)) if (f == null) { constructor.visitInsn(asm.Opcodes.ACONST_NULL) } else { constructor.visitLdcInsn(f) } - constructor.visitInsn(StringReference.typedOpcode(asm.Opcodes.IASTORE)) + constructor.visitInsn(StringRef.typedOpcode(asm.Opcodes.IASTORE)) fi += 1 } } @@ -832,12 +780,12 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { // push the string array of field information constructor.visitLdcInsn(new java.lang.Integer(fieldList.length)) - constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringReference.internalName) + constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringRef.internalName) push(fieldList) // push the string array of method information constructor.visitLdcInsn(new java.lang.Integer(methodList.length)) - constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringReference.internalName) + constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringRef.internalName) push(methodList) // invoke the superclass constructor, which will do the @@ -848,9 +796,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments constructor.visitEnd() - innerClassBufferASM ++= classBTypeFromSymbol(cls).info.get.nestedClasses - addInnerClassesASM(beanInfoClass, innerClassBufferASM.toList) - beanInfoClass.visitEnd() beanInfoClass @@ -879,8 +824,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * must-single-thread */ def legacyAddCreatorCode(clinit: asm.MethodVisitor, cnode: asm.tree.ClassNode, thisName: String) { - // this tracks the inner class in innerClassBufferASM, if needed. - val androidCreatorType = getClassBTypeAndRegisterInnerClass(AndroidCreatorClass) + val androidCreatorType = classBTypeFromSymbol(AndroidCreatorClass) val tdesc_creator = androidCreatorType.descriptor cnode.visitField( diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala index 535e1a8620..c78f422bf0 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -220,10 +220,10 @@ abstract class BCodeIdiomatic extends SubComponent { final def genStringConcat(el: BType, pos: Position): Unit = { val jtype = - if (el.isArray || el.isClass) ObjectReference + if (el.isArray || el.isClass) ObjectRef else el - val bt = MethodBType(List(jtype), StringBuilderReference) + val bt = MethodBType(List(jtype), jlStringBuilderRef) invokevirtual(StringBuilderClassName, "append", bt.descriptor, pos) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index b22fdf7c2c..3fe56dd962 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -72,14 +72,12 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { def paramTKs(app: Apply): List[BType] = { val Apply(fun, _) = app val funSym = fun.symbol - (funSym.info.paramTypes map toTypeKind) // this tracks mentioned inner classes (in innerClassBufferASM) + funSym.info.paramTypes map typeToBType } - def symInfoTK(sym: Symbol): BType = { - toTypeKind(sym.info) // this tracks mentioned inner classes (in innerClassBufferASM) - } + def symInfoTK(sym: Symbol): BType = typeToBType(sym.info) - def tpeTK(tree: Tree): BType = { toTypeKind(tree.tpe) } + def tpeTK(tree: Tree): BType = typeToBType(tree.tpe) def log(msg: => AnyRef) { global synchronized { global.log(msg) } @@ -91,7 +89,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { def genPlainClass(cd: ClassDef) { assert(cnode == null, "GenBCode detected nested methods.") - innerClassBufferASM.clear() claszSymbol = cd.symbol isCZParcelable = isAndroidParcelableClass(claszSymbol) @@ -118,7 +115,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { addClassFields() - innerClassBufferASM ++= classBType.info.get.nestedClasses gen(cd.impl) @@ -130,8 +126,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { if (shouldAddLambdaDeserialize) backendUtils.addLambdaDeserialize(cnode) - addInnerClassesASM(cnode, innerClassBufferASM.toList) - cnode.visitAttribute(classBType.inlineInfoAttribute.get) if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern)) @@ -146,12 +140,8 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { private def initJClass(jclass: asm.ClassVisitor) { val bType = classBTypeFromSymbol(claszSymbol) - val superClass = bType.info.get.superClass.getOrElse(ObjectReference).internalName - val interfaceNames = bType.info.get.interfaces map { - case classBType => - if (classBType.isNestedClass.get) { innerClassBufferASM += classBType } - classBType.internalName - } + val superClass = bType.info.get.superClass.getOrElse(ObjectRef).internalName + val interfaceNames = bType.info.get.interfaces.map(_.internalName) val flags = javaFlags(claszSymbol) @@ -164,7 +154,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { cnode.visitSource(cunit.source.toString, null /* SourceDebugExtension */) } - enclosingMethodAttribute(claszSymbol, internalName, asmMethodType(_).descriptor) match { + enclosingMethodAttribute(claszSymbol, internalName, methodBTypeFromSymbol(_).descriptor) match { case Some(EnclosingMethodEntry(className, methodName, methodDescriptor)) => cnode.visitOuterClass(className, methodName, methodDescriptor) case _ => () @@ -536,7 +526,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { if (isMethSymStaticCtor) CLASS_CONSTRUCTOR_NAME else jMethodName - val mdesc = asmMethodType(methSymbol).descriptor + val mdesc = methodBTypeFromSymbol(methSymbol).descriptor mnode = cnode.visitMethod( flags, bytecodeName, @@ -560,7 +550,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { methSymbol = dd.symbol jMethodName = methSymbol.javaSimpleName.toString - returnType = asmMethodType(dd.symbol).returnType + returnType = methodBTypeFromSymbol(dd.symbol).returnType isMethSymStaticCtor = methSymbol.isStaticConstructor resetMethodBookkeeping(dd) @@ -689,7 +679,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { val callee = methSymbol.enclClass.primaryConstructor val jname = callee.javaSimpleName.toString val jowner = internalName(callee.owner) - val jtype = asmMethodType(callee).descriptor + val jtype = methodBTypeFromSymbol(callee).descriptor insnModB = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESPECIAL, jowner, jname, jtype, false) } @@ -698,7 +688,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { // android creator code if (isCZParcelable) { // add a static field ("CREATOR") to this class to cache android.os.Parcelable$Creator - val andrFieldDescr = getClassBTypeAndRegisterInnerClass(AndroidCreatorClass).descriptor + val andrFieldDescr = classBTypeFromSymbol(AndroidCreatorClass).descriptor cnode.visitField( asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL, "CREATOR", @@ -710,7 +700,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { val callee = definitions.getMember(claszSymbol.companionModule, androidFieldName) val jowner = internalName(callee.owner) val jname = callee.javaSimpleName.toString - val jtype = asmMethodType(callee).descriptor + val jtype = methodBTypeFromSymbol(callee).descriptor insnParcA = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESTATIC, jowner, jname, jtype, false) // PUTSTATIC `thisName`.CREATOR; insnParcB = new asm.tree.FieldInsnNode(asm.Opcodes.PUTSTATIC, thisName, "CREATOR", andrFieldDescr) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala index b94208c1a5..3e53419573 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala @@ -30,7 +30,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder { def genSynchronized(tree: Apply, expectedType: BType): BType = { val Apply(fun, args) = tree - val monitor = locals.makeLocal(ObjectReference, "monitor") + val monitor = locals.makeLocal(ObjectRef, "monitor") val monCleanup = new asm.Label // if the synchronized block returns a result, store it in a local variable. @@ -40,7 +40,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder { /* ------ (1) pushing and entering the monitor, also keeping a reference to it in a local var. ------ */ genLoadQualifier(fun) - bc dup ObjectReference + bc dup ObjectRef locals.store(monitor) emit(asm.Opcodes.MONITORENTER) @@ -75,7 +75,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder { * Protected by whatever protects the whole synchronized expression. * ------ */ - protect(startProtected, endProtected, currProgramPoint(), ThrowableReference) + protect(startProtected, endProtected, currProgramPoint(), jlThrowableRef) locals.load(monitor) emit(asm.Opcodes.MONITOREXIT) emit(asm.Opcodes.ATHROW) @@ -184,7 +184,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder { for (CaseDef(pat, _, caseBody) <- catches) yield { pat match { case Typed(Ident(nme.WILDCARD), tpt) => NamelessEH(tpeTK(tpt).asClassBType, caseBody) - case Ident(nme.WILDCARD) => NamelessEH(ThrowableReference, caseBody) + case Ident(nme.WILDCARD) => NamelessEH(jlThrowableRef, caseBody) case Bind(_, _) => BoundEH (pat.symbol, caseBody) } } @@ -290,7 +290,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder { nopIfNeeded(startTryBody) val finalHandler = currProgramPoint() // version of the finally-clause reached via unhandled exception. protect(startTryBody, finalHandler, finalHandler, null) - val Local(eTK, _, eIdx, _) = locals(locals.makeLocal(ThrowableReference, "exc")) + val Local(eTK, _, eIdx, _) = locals(locals.makeLocal(jlThrowableRef, "exc")) bc.store(eIdx, eTK) emitFinalizer(finalizer, null, isDuplicate = true) bc.load(eIdx, eTK) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index f1b515910f..ef3fab7617 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -82,6 +82,14 @@ abstract class BTypes { val callsitePositions: concurrent.Map[MethodInsnNode, Position] = recordPerRunCache(TrieMap.empty) /** + * Stores callsite instructions of invocatinos annotated `f(): @inline/noinline`. + * Instructions are added during code generation (BCodeBodyBuilder). The maps are then queried + * when building the CallGraph, every Callsite object has an annotated(No)Inline field. + */ + val inlineAnnotatedCallsites: mutable.Set[MethodInsnNode] = recordPerRunCache(mutable.Set.empty) + val noInlineAnnotatedCallsites: mutable.Set[MethodInsnNode] = recordPerRunCache(mutable.Set.empty) + + /** * Contains the internal names of all classes that are defined in Java source files of the current * compilation run (mixed compilation). Used for more detailed error reporting. */ @@ -149,7 +157,7 @@ abstract class BTypes { val res = ClassBType(internalName) byteCodeRepository.classNode(internalName) match { case Left(msg) => res.info = Left(NoClassBTypeInfoMissingBytecode(msg)); res - case Right(c) => setClassInfoFromParsedClassfile(c, res) + case Right(c) => setClassInfoFromClassNode(c, res) } }) } @@ -159,14 +167,14 @@ abstract class BTypes { */ def classBTypeFromClassNode(classNode: ClassNode): ClassBType = { classBTypeFromInternalName.getOrElse(classNode.name, { - setClassInfoFromParsedClassfile(classNode, ClassBType(classNode.name)) + setClassInfoFromClassNode(classNode, ClassBType(classNode.name)) }) } - private def setClassInfoFromParsedClassfile(classNode: ClassNode, classBType: ClassBType): ClassBType = { + private def setClassInfoFromClassNode(classNode: ClassNode, classBType: ClassBType): ClassBType = { val superClass = classNode.superName match { case null => - assert(classNode.name == ObjectReference.internalName, s"class with missing super type: ${classNode.name}") + assert(classNode.name == ObjectRef.internalName, s"class with missing super type: ${classNode.name}") None case superName => Some(classBTypeFromParsedClassfile(superName)) @@ -313,8 +321,8 @@ abstract class BTypes { final def isNonVoidPrimitiveType = isPrimitive && this != UNIT - final def isNullType = this == RT_NULL - final def isNothingType = this == RT_NOTHING + final def isNullType = this == srNullRef + final def isNothingType = this == srNothingRef final def isBoxed = this.isClass && boxedClasses(this.asClassBType) @@ -337,7 +345,7 @@ abstract class BTypes { this match { case ArrayBType(component) => - if (other == ObjectReference || other == jlCloneableReference || other == jioSerializableReference) true + if (other == ObjectRef || other == jlCloneableRef || other == jiSerializableRef) true else other match { case ArrayBType(otherComponent) => component.conformsTo(otherComponent).orThrow case _ => false @@ -346,7 +354,7 @@ abstract class BTypes { case classType: ClassBType => if (isBoxed) { if (other.isBoxed) this == other - else if (other == ObjectReference) true + else if (other == ObjectRef) true else other match { case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType).orThrow // e.g., java/lang/Double conforms to java/lang/Number case _ => false @@ -389,7 +397,7 @@ abstract class BTypes { assert(other.isRef, s"Cannot compute maxType: $this, $other") // Approximate `lub`. The common type of two references is always ObjectReference. - ObjectReference + ObjectRef case _: MethodBType => assertionError(s"unexpected method type when computing maxType: $this") @@ -861,7 +869,7 @@ abstract class BTypes { // best-effort verification. also we don't report an error if the info is a Left. def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || c.info.isLeft || p(c) - def isJLO(t: ClassBType) = t.internalName == ObjectReference.internalName + def isJLO(t: ClassBType) = t.internalName == ObjectRef.internalName assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this") @@ -941,7 +949,7 @@ abstract class BTypes { def isSubtypeOf(other: ClassBType): Either[NoClassBTypeInfo, Boolean] = try { if (this == other) return Right(true) if (isInterface.orThrow) { - if (other == ObjectReference) return Right(true) // interfaces conform to Object + if (other == ObjectRef) return Right(true) // interfaces conform to Object if (!other.isInterface.orThrow) return Right(false) // this is an interface, the other is some class other than object. interfaces cannot extend classes, so the result is false. // else: this and other are both interfaces. continue to (*) } else { @@ -974,13 +982,13 @@ abstract class BTypes { // exercised by test/files/run/t4761.scala if (other.isSubtypeOf(this).orThrow) this else if (this.isSubtypeOf(other).orThrow) other - else ObjectReference + else ObjectRef case (true, false) => - if (other.isSubtypeOf(this).orThrow) this else ObjectReference + if (other.isSubtypeOf(this).orThrow) this else ObjectRef case (false, true) => - if (this.isSubtypeOf(other).orThrow) other else ObjectReference + if (this.isSubtypeOf(other).orThrow) other else ObjectRef case _ => // TODO @lry I don't really understand the reasoning here. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 79b5e6a2fb..a2bf8e5725 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -110,8 +110,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { assert(classSym.isClass, s"Cannot create ClassBType from non-class symbol $classSym") assertClassNotArrayNotPrimitive(classSym) assert(!primitiveTypeMap.contains(classSym) || isCompilingPrimitive, s"Cannot create ClassBType for primitive class symbol $classSym") - if (classSym == NothingClass) RT_NOTHING - else if (classSym == NullClass) RT_NULL + if (classSym == NothingClass) srNothingRef + else if (classSym == NullClass) srNullRef else { val internalName = classSym.javaBinaryName.toString classBTypeFromInternalName.getOrElse(internalName, { @@ -164,7 +164,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { */ def nonClassTypeRefToBType(sym: Symbol): ClassBType = { assert(sym.isType && isCompilingArray, sym) - ObjectReference + ObjectRef } t.dealiasWiden match { @@ -201,7 +201,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { "If possible, please file a bug on issues.scala-lang.org.") tp match { - case ThisType(ArrayClass) => ObjectReference // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test + case ThisType(ArrayClass) => ObjectRef // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test case ThisType(sym) => classBTypeFromSymbol(sym) case SingleType(_, sym) => primitiveOrClassToBType(sym) case ConstantType(_) => typeToBType(t.underlying) @@ -256,13 +256,6 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { /* The InnerClass table of a class C must contain all nested classes of C, even if they are only * declared but not otherwise referenced in C (from the bytecode or a method / field signature). * We collect them here. - * - * Nested classes that are also referenced in C will be added to the innerClassBufferASM during - * code generation, but those duplicates will be eliminated when emitting the InnerClass - * attribute. - * - * Why do we need to collect classes into innerClassBufferASM at all? To collect references to - * nested classes, but NOT nested in C, that are used within C. */ val nestedClassSymbols = { val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases @@ -455,7 +448,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } /** - * For top-level objects without a companion class, the compilere generates a mirror class with + * For top-level objects without a companion class, the compiler generates a mirror class with * static forwarders (Java compat). There's no symbol for the mirror class, but we still need a * ClassBType (its info.nestedClasses will hold the InnerClass entries, see comment in BTypes). */ @@ -467,7 +460,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { // class info consistent with BCodeHelpers.genMirrorClass val nested = exitingPickler(memberClassesForInnerClassTable(moduleClassSym)) map classBTypeFromSymbol c.info = Right(ClassInfo( - superClass = Some(ObjectReference), + superClass = Some(ObjectRef), interfaces = Nil, flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL, nestedClasses = nested, @@ -477,6 +470,21 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { }) } + def beanInfoClassClassBType(mainClass: Symbol): ClassBType = { + val internalName = mainClass.javaBinaryName.toString + "BeanInfo" + classBTypeFromInternalName.getOrElse(internalName, { + val c = ClassBType(internalName) + c.info = Right(ClassInfo( + superClass = Some(sbScalaBeanInfoRef), + interfaces = Nil, + flags = javaFlags(mainClass), + nestedClasses = Nil, + nestedInfo = None, + inlineInfo = EmptyInlineInfo)) + c + }) + } + /** * True for module classes of package level objects. The backend will generate a mirror class for * such objects. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala index 005d01f187..05cc484135 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala @@ -44,7 +44,7 @@ object BackendReporting { implicit class RightBiasedEither[A, B](val v: Either[A, B]) extends AnyVal { def map[U](f: B => U) = v.right.map(f) def flatMap[BB](f: B => Either[A, BB]) = v.right.flatMap(f) - def filter(f: B => Boolean)(implicit empty: A): Either[A, B] = v match { + def withFilter(f: B => Boolean)(implicit empty: A): Either[A, B] = v match { case Left(_) => v case Right(e) => if (f(e)) v else Left(empty) // scalaz.\/ requires an implicit Monoid m to get m.empty } @@ -86,8 +86,8 @@ object BackendReporting { def emitWarning(settings: ScalaSettings): Boolean } - // Method filter in RightBiasedEither requires an implicit empty value. Taking the value here - // in scope allows for-comprehensions that desugar into filter calls (for example when using a + // Method withFilter in RightBiasedEither requires an implicit empty value. Taking the value here + // in scope allows for-comprehensions that desugar into withFilter calls (for example when using a // tuple de-constructor). implicit object emptyOptimizerWarning extends OptimizerWarning { def emitWarning(settings: ScalaSettings): Boolean = false diff --git a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala index 48e43c74f4..79aa4308c5 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala @@ -29,7 +29,7 @@ import scala.annotation.switch class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { import bTypes._ import global._ - import rootMirror.{requiredClass, getClassIfDefined} + import rootMirror.{requiredClass, requiredModule, getClassIfDefined} import definitions._ /** @@ -48,15 +48,15 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { DoubleClass -> DOUBLE ) - lazy val BOXED_UNIT : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Void]) - lazy val BOXED_BOOLEAN : ClassBType = classBTypeFromSymbol(BoxedBooleanClass) - lazy val BOXED_BYTE : ClassBType = classBTypeFromSymbol(BoxedByteClass) - lazy val BOXED_SHORT : ClassBType = classBTypeFromSymbol(BoxedShortClass) - lazy val BOXED_CHAR : ClassBType = classBTypeFromSymbol(BoxedCharacterClass) - lazy val BOXED_INT : ClassBType = classBTypeFromSymbol(BoxedIntClass) - lazy val BOXED_LONG : ClassBType = classBTypeFromSymbol(BoxedLongClass) - lazy val BOXED_FLOAT : ClassBType = classBTypeFromSymbol(BoxedFloatClass) - lazy val BOXED_DOUBLE : ClassBType = classBTypeFromSymbol(BoxedDoubleClass) + private lazy val BOXED_UNIT : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Void]) + private lazy val BOXED_BOOLEAN : ClassBType = classBTypeFromSymbol(BoxedBooleanClass) + private lazy val BOXED_BYTE : ClassBType = classBTypeFromSymbol(BoxedByteClass) + private lazy val BOXED_SHORT : ClassBType = classBTypeFromSymbol(BoxedShortClass) + private lazy val BOXED_CHAR : ClassBType = classBTypeFromSymbol(BoxedCharacterClass) + private lazy val BOXED_INT : ClassBType = classBTypeFromSymbol(BoxedIntClass) + private lazy val BOXED_LONG : ClassBType = classBTypeFromSymbol(BoxedLongClass) + private lazy val BOXED_FLOAT : ClassBType = classBTypeFromSymbol(BoxedFloatClass) + private lazy val BOXED_DOUBLE : ClassBType = classBTypeFromSymbol(BoxedDoubleClass) /** * Map from primitive types to their boxed class type. Useful when pushing class literals onto the @@ -95,35 +95,30 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { /* * RT_NOTHING and RT_NULL exist at run-time only. They are the bytecode-level manifestation (in - * method signatures only) of what shows up as NothingClass resp. NullClass in Scala ASTs. + * method signatures only) of what shows up as NothingClass (scala.Nothing) resp. NullClass + * (scala.Null) in Scala ASTs. * * Therefore, when RT_NOTHING or RT_NULL are to be emitted, a mapping is needed: the internal * names of NothingClass and NullClass can't be emitted as-is. */ - lazy val RT_NOTHING : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Nothing$]) - lazy val RT_NULL : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Null$]) - - lazy val ObjectReference : ClassBType = classBTypeFromSymbol(ObjectClass) - lazy val objArrayReference : ArrayBType = ArrayBType(ObjectReference) - - lazy val StringReference : ClassBType = classBTypeFromSymbol(StringClass) - lazy val StringBuilderReference : ClassBType = classBTypeFromSymbol(StringBuilderClass) - lazy val ThrowableReference : ClassBType = classBTypeFromSymbol(ThrowableClass) - lazy val jlCloneableReference : ClassBType = classBTypeFromSymbol(JavaCloneableClass) // java/lang/Cloneable - lazy val jlNPEReference : ClassBType = classBTypeFromSymbol(NullPointerExceptionClass) // java/lang/NullPointerException - lazy val jioSerializableReference : ClassBType = classBTypeFromSymbol(JavaSerializableClass) // java/io/Serializable - lazy val scalaSerializableReference : ClassBType = classBTypeFromSymbol(SerializableClass) // scala/Serializable - lazy val classCastExceptionReference : ClassBType = classBTypeFromSymbol(ClassCastExceptionClass) // java/lang/ClassCastException - lazy val javaUtilMapReference : ClassBType = classBTypeFromSymbol(JavaUtilMap) // java/util/Map - lazy val javaUtilHashMapReference : ClassBType = classBTypeFromSymbol(JavaUtilHashMap) // java/util/HashMap - - lazy val srBooleanRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BooleanRef]) - lazy val srByteRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.ByteRef]) - lazy val srCharRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.CharRef]) - lazy val srIntRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.IntRef]) - lazy val srLongRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.LongRef]) - lazy val srFloatRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.FloatRef]) - lazy val srDoubleRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.DoubleRef]) + lazy val srNothingRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Nothing$]) + lazy val srNullRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Null$]) + + lazy val ObjectRef : ClassBType = classBTypeFromSymbol(ObjectClass) + lazy val StringRef : ClassBType = classBTypeFromSymbol(StringClass) + lazy val jlStringBuilderRef : ClassBType = classBTypeFromSymbol(StringBuilderClass) + lazy val jlThrowableRef : ClassBType = classBTypeFromSymbol(ThrowableClass) + lazy val jlCloneableRef : ClassBType = classBTypeFromSymbol(JavaCloneableClass) // java/lang/Cloneable + lazy val jiSerializableRef : ClassBType = classBTypeFromSymbol(JavaSerializableClass) // java/io/Serializable + lazy val jlClassCastExceptionRef : ClassBType = classBTypeFromSymbol(ClassCastExceptionClass) // java/lang/ClassCastException + lazy val juMapRef : ClassBType = classBTypeFromSymbol(JavaUtilMap) // java/util/Map + lazy val juHashMapRef : ClassBType = classBTypeFromSymbol(JavaUtilHashMap) // java/util/HashMap + lazy val sbScalaBeanInfoRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.beans.ScalaBeanInfo]) + lazy val jliSerializedLambdaRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.SerializedLambda]) + lazy val jliMethodHandlesRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodHandles]) + lazy val jliMethodHandlesLookupRef : ClassBType = classBTypeFromSymbol(exitingPickler(rootMirror.getRequiredClass("java.lang.invoke.MethodHandles.Lookup"))) // didn't find a reliable non-stringly-typed way that works for inner classes in the backend + lazy val srLambdaDeserializerRef : ClassBType = classBTypeFromSymbol(requiredModule[scala.runtime.LambdaDeserializer.type].moduleClass) + lazy val srBoxesRunTimeRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxesRunTime]) lazy val hashMethodSym: Symbol = getMember(ScalaRunTimeModule, nme.hash_) @@ -141,16 +136,6 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { }) } - // scala.FunctionX and scala.runtim.AbstractFunctionX - lazy val FunctionReference : Vector[ClassBType] = (0 to MaxFunctionArity).map(i => classBTypeFromSymbol(FunctionClass(i)))(collection.breakOut) - lazy val AbstractFunctionReference : Vector[ClassBType] = (0 to MaxFunctionArity).map(i => classBTypeFromSymbol(AbstractFunctionClass(i)))(collection.breakOut) - lazy val AbstractFunctionArityMap : Map[ClassBType, Int] = AbstractFunctionReference.zipWithIndex.toMap - - lazy val PartialFunctionReference : ClassBType = classBTypeFromSymbol(PartialFunctionClass) - lazy val AbstractPartialFunctionReference : ClassBType = classBTypeFromSymbol(AbstractPartialFunctionClass) - - lazy val BoxesRunTime: ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxesRunTime]) - /** * Methods in scala.runtime.BoxesRuntime */ @@ -166,14 +151,14 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { ) lazy val asmUnboxTo: Map[BType, MethodNameAndType] = Map( - BOOL -> MethodNameAndType("unboxToBoolean", MethodBType(List(ObjectReference), BOOL)), - BYTE -> MethodNameAndType("unboxToByte", MethodBType(List(ObjectReference), BYTE)), - CHAR -> MethodNameAndType("unboxToChar", MethodBType(List(ObjectReference), CHAR)), - SHORT -> MethodNameAndType("unboxToShort", MethodBType(List(ObjectReference), SHORT)), - INT -> MethodNameAndType("unboxToInt", MethodBType(List(ObjectReference), INT)), - LONG -> MethodNameAndType("unboxToLong", MethodBType(List(ObjectReference), LONG)), - FLOAT -> MethodNameAndType("unboxToFloat", MethodBType(List(ObjectReference), FLOAT)), - DOUBLE -> MethodNameAndType("unboxToDouble", MethodBType(List(ObjectReference), DOUBLE)) + BOOL -> MethodNameAndType("unboxToBoolean", MethodBType(List(ObjectRef), BOOL)), + BYTE -> MethodNameAndType("unboxToByte", MethodBType(List(ObjectRef), BYTE)), + CHAR -> MethodNameAndType("unboxToChar", MethodBType(List(ObjectRef), CHAR)), + SHORT -> MethodNameAndType("unboxToShort", MethodBType(List(ObjectRef), SHORT)), + INT -> MethodNameAndType("unboxToInt", MethodBType(List(ObjectRef), INT)), + LONG -> MethodNameAndType("unboxToLong", MethodBType(List(ObjectRef), LONG)), + FLOAT -> MethodNameAndType("unboxToFloat", MethodBType(List(ObjectRef), FLOAT)), + DOUBLE -> MethodNameAndType("unboxToDouble", MethodBType(List(ObjectRef), DOUBLE)) ) lazy val typeOfArrayOp: Map[Int, BType] = { @@ -187,7 +172,7 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { (List(LARRAY_LENGTH, LARRAY_GET, LARRAY_SET) map (_ -> LONG)) ++ (List(FARRAY_LENGTH, FARRAY_GET, FARRAY_SET) map (_ -> FLOAT)) ++ (List(DARRAY_LENGTH, DARRAY_GET, DARRAY_SET) map (_ -> DOUBLE)) ++ - (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectReference)) : _* + (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectRef)) : _* ) } } @@ -206,14 +191,17 @@ trait CoreBTypesProxyGlobalIndependent[BTS <: BTypes] { def boxedClasses: Set[ClassBType] - def RT_NOTHING: ClassBType - def RT_NULL : ClassBType - - def ObjectReference : ClassBType - def jlCloneableReference : ClassBType - def jioSerializableReference: ClassBType - def javaUtilHashMapReference: ClassBType - def javaUtilMapReference : ClassBType + def ObjectRef : ClassBType + def srNothingRef : ClassBType + def srNullRef : ClassBType + def jlCloneableRef : ClassBType + def jiSerializableRef : ClassBType + def juHashMapRef : ClassBType + def juMapRef : ClassBType + def jliSerializedLambdaRef : ClassBType + def jliMethodHandlesRef : ClassBType + def jliMethodHandlesLookupRef : ClassBType + def srLambdaDeserializerRef : ClassBType } /** @@ -230,48 +218,30 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: def primitiveTypeMap: Map[Symbol, PrimitiveBType] = _coreBTypes.primitiveTypeMap - def BOXED_UNIT : ClassBType = _coreBTypes.BOXED_UNIT - def BOXED_BOOLEAN : ClassBType = _coreBTypes.BOXED_BOOLEAN - def BOXED_BYTE : ClassBType = _coreBTypes.BOXED_BYTE - def BOXED_SHORT : ClassBType = _coreBTypes.BOXED_SHORT - def BOXED_CHAR : ClassBType = _coreBTypes.BOXED_CHAR - def BOXED_INT : ClassBType = _coreBTypes.BOXED_INT - def BOXED_LONG : ClassBType = _coreBTypes.BOXED_LONG - def BOXED_FLOAT : ClassBType = _coreBTypes.BOXED_FLOAT - def BOXED_DOUBLE : ClassBType = _coreBTypes.BOXED_DOUBLE - def boxedClasses: Set[ClassBType] = _coreBTypes.boxedClasses def boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] = _coreBTypes.boxedClassOfPrimitive def boxResultType: Map[Symbol, ClassBType] = _coreBTypes.boxResultType - def unboxResultType: Map[Symbol, PrimitiveBType] = _coreBTypes.unboxResultType - def RT_NOTHING : ClassBType = _coreBTypes.RT_NOTHING - def RT_NULL : ClassBType = _coreBTypes.RT_NULL - - def ObjectReference : ClassBType = _coreBTypes.ObjectReference - def objArrayReference : ArrayBType = _coreBTypes.objArrayReference - - def StringReference : ClassBType = _coreBTypes.StringReference - def StringBuilderReference : ClassBType = _coreBTypes.StringBuilderReference - def ThrowableReference : ClassBType = _coreBTypes.ThrowableReference - def jlCloneableReference : ClassBType = _coreBTypes.jlCloneableReference - def jlNPEReference : ClassBType = _coreBTypes.jlNPEReference - def jioSerializableReference : ClassBType = _coreBTypes.jioSerializableReference - def scalaSerializableReference : ClassBType = _coreBTypes.scalaSerializableReference - def classCastExceptionReference : ClassBType = _coreBTypes.classCastExceptionReference - def javaUtilMapReference : ClassBType = _coreBTypes.javaUtilMapReference - def javaUtilHashMapReference : ClassBType = _coreBTypes.javaUtilHashMapReference - - def srBooleanRef : ClassBType = _coreBTypes.srBooleanRef - def srByteRef : ClassBType = _coreBTypes.srByteRef - def srCharRef : ClassBType = _coreBTypes.srCharRef - def srIntRef : ClassBType = _coreBTypes.srIntRef - def srLongRef : ClassBType = _coreBTypes.srLongRef - def srFloatRef : ClassBType = _coreBTypes.srFloatRef - def srDoubleRef : ClassBType = _coreBTypes.srDoubleRef + def ObjectRef : ClassBType = _coreBTypes.ObjectRef + def StringRef : ClassBType = _coreBTypes.StringRef + def jlStringBuilderRef : ClassBType = _coreBTypes.jlStringBuilderRef + def srNothingRef : ClassBType = _coreBTypes.srNothingRef + def srNullRef : ClassBType = _coreBTypes.srNullRef + def jlThrowableRef : ClassBType = _coreBTypes.jlThrowableRef + def jlCloneableRef : ClassBType = _coreBTypes.jlCloneableRef + def jiSerializableRef : ClassBType = _coreBTypes.jiSerializableRef + def jlClassCastExceptionRef : ClassBType = _coreBTypes.jlClassCastExceptionRef + def juMapRef : ClassBType = _coreBTypes.juMapRef + def juHashMapRef : ClassBType = _coreBTypes.juHashMapRef + def sbScalaBeanInfoRef : ClassBType = _coreBTypes.sbScalaBeanInfoRef + def jliSerializedLambdaRef : ClassBType = _coreBTypes.jliSerializedLambdaRef + def jliMethodHandlesRef : ClassBType = _coreBTypes.jliMethodHandlesRef + def jliMethodHandlesLookupRef : ClassBType = _coreBTypes.jliMethodHandlesLookupRef + def srLambdaDeserializerRef : ClassBType = _coreBTypes.srLambdaDeserializerRef + def srBoxesRunTimeRef : ClassBType = _coreBTypes.srBoxesRunTimeRef def hashMethodSym: Symbol = _coreBTypes.hashMethodSym @@ -282,15 +252,6 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: def String_valueOf: Symbol = _coreBTypes.String_valueOf - def FunctionReference : Vector[ClassBType] = _coreBTypes.FunctionReference - def AbstractFunctionReference : Vector[ClassBType] = _coreBTypes.AbstractFunctionReference - def AbstractFunctionArityMap : Map[ClassBType, Int] = _coreBTypes.AbstractFunctionArityMap - - def PartialFunctionReference : ClassBType = _coreBTypes.PartialFunctionReference - def AbstractPartialFunctionReference : ClassBType = _coreBTypes.AbstractPartialFunctionReference - - def BoxesRunTime: ClassBType = _coreBTypes.BoxesRunTime - def asmBoxTo : Map[BType, MethodNameAndType] = _coreBTypes.asmBoxTo def asmUnboxTo: Map[BType, MethodNameAndType] = _coreBTypes.asmUnboxTo diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 61abc55501..4424a3796b 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -891,8 +891,11 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => * This method returns such list of internal names. */ def getExceptions(excs: List[AnnotationInfo]): List[String] = - for (ThrownException(exc) <- excs.distinct) - yield javaName(exc) + for (ThrownException(tp) <- excs.distinct) + yield { + val erased = enteringErasure(erasure.erasure(tp.typeSymbol)(tp)) + javaName(erased.typeSymbol) + } def getCurrentCUnit(): CompilationUnit @@ -995,10 +998,14 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => * Invoked from genMethod() and addForwarder(). */ def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) { + def hasThrowsRemoteException = meth.annotations.exists { + case ThrownException(exc) => exc.typeSymbol == definitions.RemoteExceptionClass + case _ => false + } val needsAnnotation = ( ( isRemoteClass || isRemote(meth) && isJMethodPublic - ) && !(meth.throwsAnnotations contains RemoteExceptionClass) + ) && !hasThrowsRemoteException ) if (needsAnnotation) { val c = Constant(RemoteExceptionClass.tpe) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala index e1a724f1cb..35e4db33bc 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -245,6 +245,11 @@ abstract class GenBCode extends BCodeSyncAndTry { BackendStats.timed(BackendStats.methodOptTimer)(localOpt.methodOptimizations(classNode)) } + def setInnerClasses(classNode: ClassNode): Unit = if (classNode != null) { + classNode.innerClasses.clear() + addInnerClasses(classNode, bTypes.backendUtils.collectNestedClasses(classNode)) + } + def run() { runGlobalOptimizations() @@ -257,6 +262,9 @@ abstract class GenBCode extends BCodeSyncAndTry { else { try { localOptimizations(item.plain) + setInnerClasses(item.plain) + setInnerClasses(item.mirror) + setInnerClasses(item.bean) addToQ3(item) } catch { case e: java.lang.RuntimeException if e.getMessage != null && (e.getMessage contains "too large!") => diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala index 9e5fbfcc0e..596ee55290 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala @@ -92,10 +92,9 @@ class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLoc def stackTop: Int = this.stackTop def peekStack(n: Int): V = this.peekStack(n) - // the val pattern `val (p, c) = f` still allocates a tuple (https://github.com/scala-opt/scala/issues/28) - val prodCons = InstructionStackEffect(insn, this) // needs to be called before super.execute, see its doc - val consumed = prodCons._1 - val produced = prodCons._2 + val prodCons = InstructionStackEffect.forAsmAnalysis(insn, this) // needs to be called before super.execute, see its doc + val consumed = InstructionStackEffect.cons(prodCons) + val produced = InstructionStackEffect.prod(prodCons) super.execute(insn, interpreter) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala index 793235c131..b02bc7c96e 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala @@ -2,12 +2,14 @@ package scala.tools.nsc package backend.jvm package analysis -import scala.tools.asm.Label +import scala.annotation.switch +import scala.tools.asm.{Opcodes, Handle, Type, Label} import scala.tools.asm.tree._ import scala.tools.asm.tree.analysis.{Frame, BasicInterpreter, Analyzer, Value} import scala.tools.nsc.backend.jvm.BTypes._ import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._ import java.lang.invoke.LambdaMetafactory +import scala.collection.mutable import scala.collection.convert.decorateAsJava._ import scala.collection.convert.decorateAsScala._ @@ -26,7 +28,7 @@ class BackendUtils[BT <: BTypes](val btypes: BT) { * A wrapper to make ASM's Analyzer a bit easier to use. */ class AsmAnalyzer[V <: Value](methodNode: MethodNode, classInternalName: InternalName, val analyzer: Analyzer[V] = new Analyzer(new BasicInterpreter)) { - localOpt.computeMaxLocalsMaxStack(methodNode) + computeMaxLocalsMaxStack(methodNode) analyzer.analyze(classInternalName, methodNode) def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.frameAt(instruction, methodNode) } @@ -67,41 +69,45 @@ class BackendUtils[BT <: BTypes](val btypes: BT) { def addLambdaDeserialize(classNode: ClassNode): Unit = { val cw = classNode import scala.tools.asm.Opcodes._ + import btypes.coreBTypes._ - // Need to force creation of BTypes for these as `getCommonSuperClass` is called on - // automatically computing the max stack size (`visitMaxs`) during method writing. - btypes.coreBTypes.javaUtilHashMapReference - btypes.coreBTypes.javaUtilMapReference + // Make sure to reference the ClassBTypes of all types that are used in the code generated + // here (e.g. java/util/Map) are initialized. Initializing a ClassBType adds it to the + // `classBTypeFromInternalName` map. When writing the classfile, the asm ClassWriter computes + // stack map frames and invokes the `getCommonSuperClass` method. This method expects all + // ClassBTypes mentioned in the source code to exist in the map. - // This is fine, even if `visitInnerClass` was called before for MethodHandles.Lookup: duplicates are not emitted. - cw.visitInnerClass("java/lang/invoke/MethodHandles$Lookup", "java/lang/invoke/MethodHandles", "Lookup", ACC_PUBLIC + ACC_FINAL + ACC_STATIC) + val mapDesc = juMapRef.descriptor + val nilLookupDesc = MethodBType(Nil, jliMethodHandlesLookupRef).descriptor + val serlamObjDesc = MethodBType(jliSerializedLambdaRef :: Nil, ObjectRef).descriptor + val lookupMapSerlamObjDesc = MethodBType(jliMethodHandlesLookupRef :: juMapRef :: jliSerializedLambdaRef :: Nil, ObjectRef).descriptor { - val fv = cw.visitField(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambdaCache$", "Ljava/util/Map;", null, null) + val fv = cw.visitField(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambdaCache$", mapDesc, null, null) fv.visitEnd() } { - val mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambda$", "(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", null, null) + val mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambda$", serlamObjDesc, null, null) mv.visitCode() // javaBinaryName returns the internal name of a class. Also used in BTypesFromsymbols.classBTypeFromSymbol. - mv.visitFieldInsn(GETSTATIC, classNode.name, "$deserializeLambdaCache$", "Ljava/util/Map;") + mv.visitFieldInsn(GETSTATIC, classNode.name, "$deserializeLambdaCache$", mapDesc) mv.visitVarInsn(ASTORE, 1) mv.visitVarInsn(ALOAD, 1) val l0 = new Label() mv.visitJumpInsn(IFNONNULL, l0) - mv.visitTypeInsn(NEW, "java/util/HashMap") + mv.visitTypeInsn(NEW, juHashMapRef.internalName) mv.visitInsn(DUP) - mv.visitMethodInsn(INVOKESPECIAL, "java/util/HashMap", "<init>", "()V", false) + mv.visitMethodInsn(INVOKESPECIAL, juHashMapRef.internalName, "<init>", "()V", false) mv.visitVarInsn(ASTORE, 1) mv.visitVarInsn(ALOAD, 1) - mv.visitFieldInsn(PUTSTATIC, classNode.name, "$deserializeLambdaCache$", "Ljava/util/Map;") + mv.visitFieldInsn(PUTSTATIC, classNode.name, "$deserializeLambdaCache$", mapDesc) mv.visitLabel(l0) - mv.visitFieldInsn(GETSTATIC, "scala/runtime/LambdaDeserializer$", "MODULE$", "Lscala/runtime/LambdaDeserializer$;") - mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "lookup", "()Ljava/lang/invoke/MethodHandles$Lookup;", false) + mv.visitFieldInsn(GETSTATIC, srLambdaDeserializerRef.internalName, "MODULE$", srLambdaDeserializerRef.descriptor) + mv.visitMethodInsn(INVOKESTATIC, jliMethodHandlesRef.internalName, "lookup", nilLookupDesc, false) mv.visitVarInsn(ALOAD, 1) mv.visitVarInsn(ALOAD, 0) - mv.visitMethodInsn(INVOKEVIRTUAL, "scala/runtime/LambdaDeserializer$", "deserializeLambda", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/util/Map;Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", false) + mv.visitMethodInsn(INVOKEVIRTUAL, srLambdaDeserializerRef.internalName, "deserializeLambda", lookupMapSerlamObjDesc, false) mv.visitInsn(ARETURN) mv.visitEnd() } @@ -133,4 +139,262 @@ class BackendUtils[BT <: BTypes](val btypes: BT) { } (result, map, hasSerializableClosureInstantiation) } + + /** + * Visit the class node and collect all referenced nested classes. + */ + def collectNestedClasses(classNode: ClassNode): List[ClassBType] = { + val innerClasses = mutable.Set.empty[ClassBType] + + def visitInternalName(internalName: InternalName): Unit = if (internalName != null) { + val t = classBTypeFromParsedClassfile(internalName) + if (t.isNestedClass.get) innerClasses += t + } + + // either an internal/Name or [[Linternal/Name; -- there are certain references in classfiles + // that are either an internal name (without the surrounding `L;`) or an array descriptor + // `[Linternal/Name;`. + def visitInternalNameOrArrayReference(ref: String): Unit = if (ref != null) { + val bracket = ref.lastIndexOf('[') + if (bracket == -1) visitInternalName(ref) + else if (ref.charAt(bracket + 1) == 'L') visitInternalName(ref.substring(bracket + 2, ref.length - 1)) + } + + // we are only interested in the class references in the descriptor, so we can skip over + // primitves and the brackets of array descriptors + def visitDescriptor(desc: String): Unit = (desc.charAt(0): @switch) match { + case '(' => + val internalNames = mutable.ListBuffer.empty[String] + var i = 1 + while (i < desc.length) { + if (desc.charAt(i) == 'L') { + val start = i + 1 // skip the L + while (desc.charAt(i) != ';') i += 1 + internalNames append desc.substring(start, i) + } + // skips over '[', ')', primitives + i += 1 + } + internalNames foreach visitInternalName + + case 'L' => + visitInternalName(desc.substring(1, desc.length - 1)) + + case '[' => + visitInternalNameOrArrayReference(desc) + + case _ => // skip over primitive types + } + + def visitConstant(const: AnyRef): Unit = const match { + case t: Type => visitDescriptor(t.getDescriptor) + case _ => + } + + // in principle we could references to annotation types, as they only end up as strings in the + // constant pool, not as class references. however, the java compiler still includes nested + // annotation classes in the innerClass table, so we do the same. explained in detail in the + // large comment in class BTypes. + def visitAnnotation(annot: AnnotationNode): Unit = { + visitDescriptor(annot.desc) + if (annot.values != null) annot.values.asScala foreach visitConstant + } + + def visitAnnotations(annots: java.util.List[_ <: AnnotationNode]) = if (annots != null) annots.asScala foreach visitAnnotation + def visitAnnotationss(annotss: Array[java.util.List[AnnotationNode]]) = if (annotss != null) annotss foreach visitAnnotations + + def visitHandle(handle: Handle): Unit = { + visitInternalNameOrArrayReference(handle.getOwner) + visitDescriptor(handle.getDesc) + } + + visitInternalName(classNode.name) + innerClasses ++= classBTypeFromParsedClassfile(classNode.name).info.get.nestedClasses + + visitInternalName(classNode.superName) + classNode.interfaces.asScala foreach visitInternalName + visitInternalName(classNode.outerClass) + + visitAnnotations(classNode.visibleAnnotations) + visitAnnotations(classNode.visibleTypeAnnotations) + visitAnnotations(classNode.invisibleAnnotations) + visitAnnotations(classNode.invisibleTypeAnnotations) + + for (f <- classNode.fields.asScala) { + visitDescriptor(f.desc) + visitAnnotations(f.visibleAnnotations) + visitAnnotations(f.visibleTypeAnnotations) + visitAnnotations(f.invisibleAnnotations) + visitAnnotations(f.invisibleTypeAnnotations) + } + + for (m <- classNode.methods.asScala) { + visitDescriptor(m.desc) + + visitAnnotations(m.visibleAnnotations) + visitAnnotations(m.visibleTypeAnnotations) + visitAnnotations(m.invisibleAnnotations) + visitAnnotations(m.invisibleTypeAnnotations) + visitAnnotationss(m.visibleParameterAnnotations) + visitAnnotationss(m.invisibleParameterAnnotations) + visitAnnotations(m.visibleLocalVariableAnnotations) + visitAnnotations(m.invisibleLocalVariableAnnotations) + + m.exceptions.asScala foreach visitInternalName + for (tcb <- m.tryCatchBlocks.asScala) visitInternalName(tcb.`type`) + + val iter = m.instructions.iterator() + while (iter.hasNext) iter.next() match { + case ti: TypeInsnNode => visitInternalNameOrArrayReference(ti.desc) + case fi: FieldInsnNode => visitInternalNameOrArrayReference(fi.owner); visitDescriptor(fi.desc) + case mi: MethodInsnNode => visitInternalNameOrArrayReference(mi.owner); visitDescriptor(mi.desc) + case id: InvokeDynamicInsnNode => visitDescriptor(id.desc); visitHandle(id.bsm); id.bsmArgs foreach visitConstant + case ci: LdcInsnNode => visitConstant(ci.cst) + case ma: MultiANewArrayInsnNode => visitDescriptor(ma.desc) + case _ => + } + } + innerClasses.toList + } + + /** + * In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM + * framework only computes these values during bytecode generation. + * + * NOTE 1: as explained in the `analysis` package object, the maxStack value used by the Analyzer + * may be smaller than the correct maxStack value in the classfile (Analyzers only use a single + * slot for long / double values). The maxStack computed here are correct for running an analyzer, + * but not for writing in the classfile. We let the ClassWriter recompute max's. + * + * NOTE 2: the maxStack value computed here may be larger than the smallest correct value + * that would allow running an analyzer, see `InstructionStackEffect.forAsmAnalysisConservative`. + * + * NOTE 3: the implementation doesn't look at instructions that cannot be reached, it computes + * the max local / stack size in the reachable code. These max's work just fine for running an + * Analyzer: its implementation also skips over unreachable code in the same way. + */ + def computeMaxLocalsMaxStack(method: MethodNode): Unit = { + import Opcodes._ + + if (isAbstractMethod(method) || isNativeMethod(method)) { + method.maxLocals = 0 + method.maxStack = 0 + } else if (!maxLocalsMaxStackComputed(method)) { + val size = method.instructions.size + + var maxLocals = (Type.getArgumentsAndReturnSizes(method.desc) >> 2) - (if (isStaticMethod(method)) 1 else 0) + var maxStack = 0 + + // queue of instruction indices where analysis should start + var queue = new Array[Int](8) + var top = -1 + def enq(i: Int): Unit = { + if (top == queue.length - 1) { + val nq = new Array[Int](queue.length * 2) + Array.copy(queue, 0, nq, 0, queue.length) + queue = nq + } + top += 1 + queue(top) = i + } + def deq(): Int = { + val r = queue(top) + top -= 1 + r + } + + val subroutineRetTargets = new mutable.Stack[AbstractInsnNode] + + // for each instruction in the queue, contains the stack height at this instruction. + // once an instruction has been treated, contains -1 to prevent re-enqueuing + val stackHeights = new Array[Int](size) + + def enqInsn(insn: AbstractInsnNode, height: Int): Unit = { + enqInsnIndex(method.instructions.indexOf(insn), height) + } + + def enqInsnIndex(insnIndex: Int, height: Int): Unit = { + if (insnIndex < size && stackHeights(insnIndex) != -1) { + stackHeights(insnIndex) = height + enq(insnIndex) + } + } + + val tcbIt = method.tryCatchBlocks.iterator() + while (tcbIt.hasNext) { + val tcb = tcbIt.next() + enqInsn(tcb.handler, 1) + if (maxStack == 0) maxStack = 1 + } + + enq(0) + while (top != -1) { + val insnIndex = deq() + val insn = method.instructions.get(insnIndex) + val initHeight = stackHeights(insnIndex) + stackHeights(insnIndex) = -1 // prevent i from being enqueued again + + if (insn.getOpcode == -1) { // frames, labels, line numbers + enqInsnIndex(insnIndex + 1, initHeight) + } else { + val stackGrowth = InstructionStackEffect.maxStackGrowth(insn) + val heightAfter = initHeight + stackGrowth + if (heightAfter > maxStack) maxStack = heightAfter + + // update maxLocals + insn match { + case v: VarInsnNode => + val longSize = if (isSize2LoadOrStore(v.getOpcode)) 1 else 0 + maxLocals = math.max(maxLocals, v.`var` + longSize + 1) // + 1 becauase local numbers are 0-based + + case i: IincInsnNode => + maxLocals = math.max(maxLocals, i.`var` + 1) + + case _ => + } + + insn match { + case j: JumpInsnNode => + if (j.getOpcode == JSR) { + val jsrTargetHeight = heightAfter + 1 + if (jsrTargetHeight > maxStack) maxStack = jsrTargetHeight + subroutineRetTargets.push(j.getNext) + enqInsn(j.label, jsrTargetHeight) + } else { + enqInsn(j.label, heightAfter) + val opc = j.getOpcode + if (opc != GOTO) enqInsnIndex(insnIndex + 1, heightAfter) // jump is conditional, so the successor is also a possible control flow target + } + + case l: LookupSwitchInsnNode => + var j = 0 + while (j < l.labels.size) { + enqInsn(l.labels.get(j), heightAfter); j += 1 + } + enqInsn(l.dflt, heightAfter) + + case t: TableSwitchInsnNode => + var j = 0 + while (j < t.labels.size) { + enqInsn(t.labels.get(j), heightAfter); j += 1 + } + enqInsn(t.dflt, heightAfter) + + case r: VarInsnNode if r.getOpcode == RET => + enqInsn(subroutineRetTargets.pop(), heightAfter) + + case _ => + val opc = insn.getOpcode + if (opc != ATHROW && !isReturn(insn)) + enqInsnIndex(insnIndex + 1, heightAfter) + } + } + } + + method.maxLocals = maxLocals + method.maxStack = maxStack + + maxLocalsMaxStackComputed += method + } + } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala index 8d8ea839e6..4e81018451 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala @@ -5,35 +5,74 @@ package analysis import scala.annotation.switch import scala.tools.asm.Opcodes._ import scala.tools.asm.Type -import scala.tools.asm.tree.{MultiANewArrayInsnNode, InvokeDynamicInsnNode, MethodInsnNode, AbstractInsnNode} +import scala.tools.asm.tree._ import scala.tools.asm.tree.analysis.{Frame, Value} import opt.BytecodeUtils._ -import collection.immutable object InstructionStackEffect { - private var cache: immutable.IntMap[(Int, Int)] = immutable.IntMap.empty - private def t(x: Int, y: Int): (Int, Int) = { - // x can go up to 255 (number of parameters of a method, dimensions in multianewarray) we cache - // x up to 10, which covers most cases and limits the cache. y doesn't go above 6 (see cases). - if (x > 10 || y > 6) (x, y) - else { - val key = (x << 8) + y // this would work for any x < 256 - if (cache contains key) { - cache(key) - } else { - val r = (x, y) - cache += key -> r - r - } - } + val consShift = 3 + val prodMask = (1 << consShift) - 1 + + def cons(i: Int) = i >>> consShift + def prod(i: Int) = i & prodMask + + private def t(x: Int, y: Int): Int = (x << consShift) | y + + /** + * Returns the number of stack values consumed and produced by `insn`, encoded in a single `Int` + * (the `cons` / `prod` extract individual values). The returned values are correct for use in + * asm's Analyzer framework. For example, a LLOAD instruction produces one stack value. See also + * doc in `analysis` package object. + * + * This method requires the `frame` to be in the state **before** executing / interpreting the + * `insn`. + */ + def forAsmAnalysis[V <: Value](insn: AbstractInsnNode, frame: Frame[V]): Int = computeConsProd(insn, frame = frame) + + /** + * Returns the maximal possible growth of the stack when executing `insn`. The returned value + * is usually the same as expected by asm's Analyzer framework, but it may be larger. For + * example, consider a POP2 instruction: + * - if two size-1 values are popped, then the asm Analyzer consumes two values + * - if a size-2 value is popped, the asm Analyzer consumes only one stack slot (see doc in the + * `analysis` package object) + * + * If a precise result is needed, invoke the `forAsmAnalysis` and provide a `frame` value that + * allows looking up the sizes of values on the stack. + */ + def maxStackGrowth(insn: AbstractInsnNode): Int = { + val prodCons = computeConsProd(insn, conservative = true) + prod(prodCons) - cons(prodCons) } /** - * Returns a pair with the number of stack values consumed and produced by `insn`. - * This method requires the `frame` to be in the state **before** executing / interpreting - * the `insn`. + * Returns the number of stack values consumed and produced by `insn`, encoded in a single `Int` + * (the `cons` / `prod` extract individual values). The returned values are correct for writing + * into a classfile (see doc on the `analysis` package object). */ - def apply[V <: Value](insn: AbstractInsnNode, frame: Frame[V]): (Int, Int) = { + def forClassfile(insn: AbstractInsnNode): Int = computeConsProd(insn, forClassfile = true) + + private def invokeConsProd(methodDesc: String, insn: AbstractInsnNode, forClassfile: Boolean): Int = { + val consumesReceiver = insn.getOpcode != INVOKESTATIC && insn.getOpcode != INVOKEDYNAMIC + if (forClassfile) { + val sizes = Type.getArgumentsAndReturnSizes(methodDesc) + val cons = (sizes >> 2) - (if (consumesReceiver) 0 else 1) + val prod = sizes & 0x03 + t(cons, prod) + } else { + val cons = Type.getArgumentTypes(methodDesc).length + (if (consumesReceiver) 1 else 0) + val prod = if (Type.getReturnType(methodDesc) == Type.VOID_TYPE) 0 else 1 + t(cons, prod) + } + } + + private def fieldInsnIsLongOrDouble(insn: AbstractInsnNode) = { + val d = insn.asInstanceOf[FieldInsnNode].desc + d == "J" || d == "D" + } + + private def computeConsProd[V <: Value](insn: AbstractInsnNode, forClassfile: Boolean = false, conservative: Boolean = false, frame: Frame[V] = null): Int = { + // not used if `forClassfile || conservative`: in these cases, `frame` is allowed to be `null` def peekStack(n: Int): V = frame.peekStack(n) (insn.getOpcode: @switch) match { @@ -48,142 +87,176 @@ object InstructionStackEffect { ICONST_3 | ICONST_4 | ICONST_5 | - LCONST_0 | - LCONST_1 | FCONST_0 | FCONST_1 | FCONST_2 | - DCONST_0 | - DCONST_1 | BIPUSH | SIPUSH | - LDC | ILOAD | - LLOAD | FLOAD | - DLOAD | ALOAD => t(0, 1) + case LDC => + if (forClassfile) insn.asInstanceOf[LdcInsnNode].cst match { + case _: java.lang.Long | _: java.lang.Double => t(0, 2) + case _ => t(0, 1) + } else + t(0, 1) + + case LCONST_0 | + LCONST_1 | + DCONST_0 | + DCONST_1 | + LLOAD | + DLOAD => if (forClassfile) t(0, 2) else t(0, 1) + case IALOAD | - LALOAD | FALOAD | - DALOAD | AALOAD | BALOAD | CALOAD | SALOAD => t(2, 1) + case LALOAD | + DALOAD => if (forClassfile) t(2, 2) else t(2, 1) + case ISTORE | - LSTORE | FSTORE | - DSTORE | ASTORE => t(1, 0) + case LSTORE | + DSTORE => if (forClassfile) t(2, 0) else t(1, 0) + case IASTORE | - LASTORE | FASTORE | - DASTORE | AASTORE | BASTORE | CASTORE | SASTORE => t(3, 0) + case LASTORE | + DASTORE => if (forClassfile) t(4, 0) else t(3, 0) + case POP => t(1, 0) case POP2 => - val isSize2 = peekStack(0).getSize == 2 - if (isSize2) t(1, 0) else t(2, 0) + if (forClassfile) t(2, 0) + else if (conservative) t(1, 0) + else { + val isSize2 = peekStack(0).getSize == 2 + if (isSize2) t(1, 0) else t(2, 0) + } case DUP => t(1, 2) case DUP_X1 => t(2, 3) case DUP_X2 => - val isSize2 = peekStack(1).getSize == 2 - if (isSize2) t(2, 3) else t(3, 4) + if (forClassfile || conservative) t(3, 4) + else { + val isSize2 = peekStack(1).getSize == 2 + if (isSize2) t(2, 3) else t(3, 4) + } case DUP2 => - val isSize2 = peekStack(0).getSize == 2 - if (isSize2) t(1, 2) else t(2, 4) + if (forClassfile || conservative) t(2, 4) + else { + val isSize2 = peekStack(0).getSize == 2 + if (isSize2) t(1, 2) else t(2, 4) + } case DUP2_X1 => - val isSize2 = peekStack(0).getSize == 2 - if (isSize2) t(2, 3) else t(3, 4) + if (forClassfile || conservative) t(3, 5) + else { + val isSize2 = peekStack(0).getSize == 2 + if (isSize2) t(2, 3) else t(3, 5) + } case DUP2_X2 => - val v1isSize2 = peekStack(0).getSize == 2 - if (v1isSize2) { - val v2isSize2 = peekStack(1).getSize == 2 - if (v2isSize2) t(2, 3) else t(3, 4) - } else { - val v3isSize2 = peekStack(2).getSize == 2 - if (v3isSize2) t(3, 5) else t(4, 6) + if (forClassfile || conservative) t(4, 6) + else { + val v1isSize2 = peekStack(0).getSize == 2 + if (v1isSize2) { + val v2isSize2 = peekStack(1).getSize == 2 + if (v2isSize2) t(2, 3) else t(3, 4) + } else { + val v3isSize2 = peekStack(2).getSize == 2 + if (v3isSize2) t(3, 5) else t(4, 6) + } } case SWAP => t(2, 2) case IADD | - LADD | FADD | - DADD | ISUB | - LSUB | FSUB | - DSUB | IMUL | - LMUL | FMUL | - DMUL | IDIV | - LDIV | FDIV | - DDIV | IREM | + FREM => t(2, 1) + + case LADD | + DADD | + LSUB | + DSUB | + LMUL | + DMUL | + LDIV | + DDIV | LREM | - FREM | - DREM => t(2, 1) + DREM => if (forClassfile) t(4, 2) else t(2, 1) case INEG | - LNEG | - FNEG | - DNEG => t(1, 1) + FNEG => t(1, 1) + + case LNEG | + DNEG => if (forClassfile) t(2, 2) else t(1, 1) case ISHL | - LSHL | ISHR | - LSHR | IUSHR | - LUSHR | IAND | - LAND | IOR | + IXOR => t(2, 1) + + case LSHL | + LSHR | + LUSHR => if (forClassfile) t(3, 2) else t(2, 1) + + case LAND | LOR | - IXOR | - LXOR => t(2, 1) + LXOR => if (forClassfile) t(4, 2) else t(2, 1) case IINC => t(0, 0) - case I2L | - I2F | - I2D | - L2I | - L2F | - L2D | + case I2F | F2I | - F2L | - F2D | - D2I | - D2L | - D2F | I2B | I2C | I2S => t(1, 1) + case I2L | + I2D | + F2L | + F2D => if (forClassfile) t(1, 2) else t(1, 1) + + case L2I | + L2F | + D2I | + D2F => if (forClassfile) t(2, 1) else t(1, 1) + + case L2D | + D2L => if (forClassfile) t(2, 2) else t(1, 1) + + case FCMPL | + FCMPG => t(2, 1) + case LCMP | - FCMPL | - FCMPG | DCMPL | - DCMPG => t(2, 1) + DCMPG => if (forClassfile) t(4, 1) else t(2, 1) case IFEQ | IFNE | @@ -211,35 +284,36 @@ object InstructionStackEffect { LOOKUPSWITCH => t(1, 0) case IRETURN | - LRETURN | FRETURN | - DRETURN | ARETURN => t(1, 0) // Frame.execute consumes one stack value + case LRETURN | + DRETURN => if (forClassfile) t(2, 0) else t(1, 0) + case RETURN => t(0, 0) // Frame.execute does not change the stack - case GETSTATIC => t(0, 1) + case GETSTATIC => + val prod = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 2 else 1 + t(0, prod) - case PUTSTATIC => t(1, 0) + case PUTSTATIC => + val cons = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 2 else 1 + t(cons, 0) - case GETFIELD => t(1, 1) + case GETFIELD => + val prod = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 2 else 1 + t(1, prod) - case PUTFIELD => t(2, 0) + case PUTFIELD => + val cons = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 3 else 2 + t(cons, 0) case INVOKEVIRTUAL | INVOKESPECIAL | INVOKESTATIC | - INVOKEINTERFACE => - val desc = insn.asInstanceOf[MethodInsnNode].desc - val cons = Type.getArgumentTypes(desc).length + (if (insn.getOpcode == INVOKESTATIC) 0 else 1) - val prod = if (Type.getReturnType(desc) == Type.VOID_TYPE) 0 else 1 - t(cons, prod) - - case INVOKEDYNAMIC => - val desc = insn.asInstanceOf[InvokeDynamicInsnNode].desc - val cons = Type.getArgumentTypes(desc).length - val prod = if (Type.getReturnType(desc) == Type.VOID_TYPE) 0 else 1 - t(cons, prod) + INVOKEINTERFACE => invokeConsProd(insn.asInstanceOf[MethodInsnNode].desc, insn, forClassfile) + + case INVOKEDYNAMIC => invokeConsProd(insn.asInstanceOf[InvokeDynamicInsnNode].desc, insn, forClassfile) case NEW => t(0, 1) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerImpl.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerImpl.scala index 242171476a..c933341492 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerImpl.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerImpl.scala @@ -368,9 +368,9 @@ trait ProdConsAnalyzerImpl { Seq(insn.asInstanceOf[IincInsnNode].`var`) } else { val frame = frameAt(insn) - val stackEffect = InstructionStackEffect(insn, frame) + val prodCons = InstructionStackEffect.forAsmAnalysis(insn, frame) val stackSize = frame.getLocals + frame.getStackSize - (stackSize - stackEffect._1) until stackSize + (stackSize - InstructionStackEffect.cons(prodCons)) until stackSize } } @@ -387,10 +387,10 @@ trait ProdConsAnalyzerImpl { Seq(insn.asInstanceOf[IincInsnNode].`var`) } else { val frame = frameAt(insn) - val stackEffect = InstructionStackEffect(insn, frame) + val prodCons = InstructionStackEffect.forAsmAnalysis(insn, frame) val nextFrame = frameAt(insn.getNext) val stackSize = nextFrame.getLocals + nextFrame.getStackSize - (stackSize - stackEffect._2) until stackSize + (stackSize - InstructionStackEffect.prod(prodCons)) until stackSize } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala index 65a8c45813..e543a8c3e0 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala @@ -20,7 +20,7 @@ import scala.collection.convert.decorateAsScala._ object BytecodeUtils { // http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.9.1 - final val maxJVMMethodSize = 65535 + final val maxJVMMethodSize = 65535 // 5% margin, more than enough for the instructions added by the inliner (store / load args, null check for instance methods) final val maxMethodSizeAfterInline = maxJVMMethodSize - (maxJVMMethodSize / 20) @@ -310,9 +310,9 @@ object BytecodeUtils { * method which explains the issue with such phantom values. */ def fixLoadedNothingOrNullValue(loadedType: Type, loadInstr: AbstractInsnNode, methodNode: MethodNode, bTypes: BTypes): Unit = { - if (loadedType == bTypes.coreBTypes.RT_NOTHING.toASMType) { + if (loadedType == bTypes.coreBTypes.srNothingRef.toASMType) { methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ATHROW)) - } else if (loadedType == bTypes.coreBTypes.RT_NULL.toASMType) { + } else if (loadedType == bTypes.coreBTypes.srNullRef.toASMType) { methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ACONST_NULL)) methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.POP)) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala index a8f1e43071..b192e1b46a 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -67,6 +67,8 @@ class CallGraph[BT <: BTypes](val btypes: BT) { callsites(callsite.callsiteMethod) = methodCallsites + (callsite.callsiteInstruction -> callsite) } + def containsCallsite(callsite: Callsite): Boolean = callsites(callsite.callsiteMethod) contains callsite.callsiteInstruction + def removeClosureInstantiation(indy: InvokeDynamicInsnNode, methodNode: MethodNode): Option[ClosureInstantiation] = { val methodClosureInits = closureInstantiations(methodNode) val newClosureInits = methodClosureInits - indy @@ -89,93 +91,94 @@ class CallGraph[BT <: BTypes](val btypes: BT) { if (!callsites.contains(methodNode)) addMethod(methodNode, definingClass) } - /** - * Returns a list of callsites in the method, plus a list of closure instantiation indy instructions. - */ def addMethod(methodNode: MethodNode, definingClass: ClassBType): Unit = { - // TODO: run dataflow analyses to make the call graph more precise - // - producers to get forwarded parameters (ForwardedParam) - // - typeAnalysis for more precise argument types, more precise callee - - // For now we run a NullnessAnalyzer. It is used to determine if the receiver of an instance - // call is known to be not-null, in which case we don't have to emit a null check when inlining. - // It is also used to get the stack height at the call site. - - val analyzer = { - if (compilerSettings.YoptNullnessTracking && AsmAnalyzer.sizeOKForNullness(methodNode)) { - Some(new AsmAnalyzer(methodNode, definingClass.internalName, new NullnessAnalyzer)) - } else if (AsmAnalyzer.sizeOKForBasicValue(methodNode)) { - Some(new AsmAnalyzer(methodNode, definingClass.internalName)) - } else None - } - - // if the method is too large to run an analyzer, it is not added to the call graph - if (analyzer.nonEmpty) { - val Some(a) = analyzer - def receiverNotNullByAnalysis(call: MethodInsnNode, numArgs: Int) = a.analyzer match { - case nullnessAnalyzer: NullnessAnalyzer => - val frame = nullnessAnalyzer.frameAt(call, methodNode) - frame.getStack(frame.getStackSize - 1 - numArgs) eq NotNullValue - case _ => false + if (!BytecodeUtils.isAbstractMethod(methodNode) && !BytecodeUtils.isNativeMethod(methodNode)) { + // TODO: run dataflow analyses to make the call graph more precise + // - producers to get forwarded parameters (ForwardedParam) + // - typeAnalysis for more precise argument types, more precise callee + + // For now we run a NullnessAnalyzer. It is used to determine if the receiver of an instance + // call is known to be not-null, in which case we don't have to emit a null check when inlining. + // It is also used to get the stack height at the call site. + + val analyzer = { + if (compilerSettings.YoptNullnessTracking && AsmAnalyzer.sizeOKForNullness(methodNode)) { + Some(new AsmAnalyzer(methodNode, definingClass.internalName, new NullnessAnalyzer)) + } else if (AsmAnalyzer.sizeOKForBasicValue(methodNode)) { + Some(new AsmAnalyzer(methodNode, definingClass.internalName)) + } else None } - var methodCallsites = Map.empty[MethodInsnNode, Callsite] - var methodClosureInstantiations = Map.empty[InvokeDynamicInsnNode, ClosureInstantiation] - - // lazy so it is only computed if actually used by computeArgInfos - lazy val prodCons = new ProdConsAnalyzer(methodNode, definingClass.internalName) - - methodNode.instructions.iterator.asScala foreach { - case call: MethodInsnNode if a.frameAt(call) != null => // skips over unreachable code - val callee: Either[OptimizerWarning, Callee] = for { - (method, declarationClass) <- byteCodeRepository.methodNode(call.owner, call.name, call.desc): Either[OptimizerWarning, (MethodNode, InternalName)] - (declarationClassNode, source) <- byteCodeRepository.classNodeAndSource(declarationClass): Either[OptimizerWarning, (ClassNode, Source)] - declarationClassBType = classBTypeFromClassNode(declarationClassNode) - } yield { - val CallsiteInfo(safeToInline, safeToRewrite, annotatedInline, annotatedNoInline, samParamTypes, warning) = analyzeCallsite(method, declarationClassBType, call.owner, source) - Callee( - callee = method, - calleeDeclarationClass = declarationClassBType, - safeToInline = safeToInline, - safeToRewrite = safeToRewrite, - annotatedInline = annotatedInline, - annotatedNoInline = annotatedNoInline, - samParamTypes = samParamTypes, - calleeInfoWarning = warning) + // if the method is too large to run an analyzer, it is not added to the call graph + if (analyzer.nonEmpty) { + val Some(a) = analyzer + def receiverNotNullByAnalysis(call: MethodInsnNode, numArgs: Int) = a.analyzer match { + case nullnessAnalyzer: NullnessAnalyzer => + val frame = nullnessAnalyzer.frameAt(call, methodNode) + frame.getStack(frame.getStackSize - 1 - numArgs) eq NotNullValue + case _ => false + } + + var methodCallsites = Map.empty[MethodInsnNode, Callsite] + var methodClosureInstantiations = Map.empty[InvokeDynamicInsnNode, ClosureInstantiation] + + // lazy so it is only computed if actually used by computeArgInfos + lazy val prodCons = new ProdConsAnalyzer(methodNode, definingClass.internalName) + + methodNode.instructions.iterator.asScala foreach { + case call: MethodInsnNode if a.frameAt(call) != null => // skips over unreachable code + val callee: Either[OptimizerWarning, Callee] = for { + (method, declarationClass) <- byteCodeRepository.methodNode(call.owner, call.name, call.desc): Either[OptimizerWarning, (MethodNode, InternalName)] + (declarationClassNode, source) <- byteCodeRepository.classNodeAndSource(declarationClass): Either[OptimizerWarning, (ClassNode, Source)] + } yield { + val declarationClassBType = classBTypeFromClassNode(declarationClassNode) + val CallsiteInfo(safeToInline, safeToRewrite, annotatedInline, annotatedNoInline, samParamTypes, warning) = analyzeCallsite(method, declarationClassBType, call.owner, source) + Callee( + callee = method, + calleeDeclarationClass = declarationClassBType, + safeToInline = safeToInline, + safeToRewrite = safeToRewrite, + annotatedInline = annotatedInline, + annotatedNoInline = annotatedNoInline, + samParamTypes = samParamTypes, + calleeInfoWarning = warning) + } + + val argInfos = computeArgInfos(callee, call, prodCons) + + val receiverNotNull = call.getOpcode == Opcodes.INVOKESTATIC || { + val numArgs = Type.getArgumentTypes(call.desc).length + receiverNotNullByAnalysis(call, numArgs) } - val argInfos = computeArgInfos(callee, call, prodCons) + methodCallsites += call -> Callsite( + callsiteInstruction = call, + callsiteMethod = methodNode, + callsiteClass = definingClass, + callee = callee, + argInfos = argInfos, + callsiteStackHeight = a.frameAt(call).getStackSize, + receiverKnownNotNull = receiverNotNull, + callsitePosition = callsitePositions.getOrElse(call, NoPosition), + annotatedInline = inlineAnnotatedCallsites(call), + annotatedNoInline = noInlineAnnotatedCallsites(call) + ) - val receiverNotNull = call.getOpcode == Opcodes.INVOKESTATIC || { - val numArgs = Type.getArgumentTypes(call.desc).length - receiverNotNullByAnalysis(call, numArgs) - } + case LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) if a.frameAt(indy) != null => + val lmf = LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) + val capturedArgInfos = computeCapturedArgInfos(lmf, prodCons) + methodClosureInstantiations += indy -> ClosureInstantiation( + lmf, + methodNode, + definingClass, + capturedArgInfos) - methodCallsites += call -> Callsite( - callsiteInstruction = call, - callsiteMethod = methodNode, - callsiteClass = definingClass, - callee = callee, - argInfos = argInfos, - callsiteStackHeight = a.frameAt(call).getStackSize, - receiverKnownNotNull = receiverNotNull, - callsitePosition = callsitePositions.getOrElse(call, NoPosition) - ) - - case LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) if a.frameAt(indy) != null => - val lmf = LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) - val capturedArgInfos = computeCapturedArgInfos(lmf, prodCons) - methodClosureInstantiations += indy -> ClosureInstantiation( - lmf, - methodNode, - definingClass, - capturedArgInfos) + case _ => + } - case _ => + callsites(methodNode) = methodCallsites + closureInstantiations(methodNode) = methodClosureInstantiations } - - callsites(methodNode) = methodCallsites - closureInstantiations(methodNode) = methodClosureInstantiations } } @@ -346,7 +349,14 @@ class CallGraph[BT <: BTypes](val btypes: BT) { */ final case class Callsite(callsiteInstruction: MethodInsnNode, callsiteMethod: MethodNode, callsiteClass: ClassBType, callee: Either[OptimizerWarning, Callee], argInfos: IntMap[ArgInfo], - callsiteStackHeight: Int, receiverKnownNotNull: Boolean, callsitePosition: Position) { + callsiteStackHeight: Int, receiverKnownNotNull: Boolean, callsitePosition: Position, + annotatedInline: Boolean, annotatedNoInline: Boolean) { + /** + * Contains callsites that were created during inlining by cloning this callsite. Used to find + * corresponding callsites when inlining post-inline requests. + */ + val inlinedClones = mutable.Set.empty[ClonedCallsite] + override def toString = "Invocation of" + s" ${callee.map(_.calleeDeclarationClass.internalName).getOrElse("?")}.${callsiteInstruction.name + callsiteInstruction.desc}" + @@ -354,6 +364,8 @@ class CallGraph[BT <: BTypes](val btypes: BT) { s" in ${callsiteClass.internalName}.${callsiteMethod.name}" } + final case class ClonedCallsite(callsite: Callsite, clonedWhenInlining: Callsite) + /** * Information about invocation arguments, obtained through data flow analysis of the callsite method. */ @@ -399,6 +411,10 @@ class CallGraph[BT <: BTypes](val btypes: BT) { * graph when re-writing a closure invocation to the body method. */ final case class ClosureInstantiation(lambdaMetaFactoryCall: LambdaMetaFactoryCall, ownerMethod: MethodNode, ownerClass: ClassBType, capturedArgInfos: IntMap[ArgInfo]) { + /** + * Contains closure instantiations that were created during inlining by cloning this instantiation. + */ + val inlinedClones = mutable.Set.empty[ClosureInstantiation] override def toString = s"ClosureInstantiation($lambdaMetaFactoryCall, ${ownerMethod.name + ownerMethod.desc}, $ownerClass)" } final case class LambdaMetaFactoryCall(indy: InvokeDynamicInsnNode, samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala index a7c4c27a97..4203a93f2e 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala @@ -263,7 +263,9 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { argInfos = argInfos, callsiteStackHeight = invocationStackHeight, receiverKnownNotNull = true, // see below (*) - callsitePosition = originalCallsite.map(_.callsitePosition).getOrElse(NoPosition) + callsitePosition = originalCallsite.map(_.callsitePosition).getOrElse(NoPosition), + annotatedInline = false, + annotatedNoInline = false ) // (*) The documentation in class LambdaMetafactory says: // "if implMethod corresponds to an instance method, the first capture argument diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala index f88c131e8d..449a56fdd1 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -38,27 +38,18 @@ class Inliner[BT <: BTypes](val btypes: BT) { rewriteFinalTraitMethodInvocations() for (request <- collectAndOrderInlineRequests) { - val callsite = request.callsite - val Right(callee) = callsite.callee // collectAndOrderInlineRequests returns callsites with a known callee - - // Inlining a method can create unreachable code. Example: - // def f = throw e - // def g = f; println() // println is unreachable after inlining f - // If we have an inline request for a call to g, and f has been already inlined into g, we - // need to run DCE before inlining g. - eliminateUnreachableCodeAndUpdateCallGraph(callee.callee, callee.calleeDeclarationClass.internalName) - - // DCE above removes unreachable callsites from the call graph. If the inlining request denotes - // such an eliminated callsite, do nothing. - if (callGraph.callsites(callsite.callsiteMethod).contains(callsite.callsiteInstruction)) { - val warnings = inline(request) - - for (warning <- warnings) { - if ((callee.annotatedInline && btypes.compilerSettings.YoptWarningEmitAtInlineFailed) || warning.emitWarning(compilerSettings)) { - val annotWarn = if (callee.annotatedInline) " is annotated @inline but" else "" - val msg = s"${BackendReporting.methodSignature(callee.calleeDeclarationClass.internalName, callee.callee)}$annotWarn could not be inlined:\n$warning" - backendReporting.inlinerWarning(callsite.callsitePosition, msg) - } + val Right(callee) = request.callsite.callee // collectAndOrderInlineRequests returns callsites with a known callee + + // TODO: if the request has downstream requests, create a snapshot to which we could roll back in case some downstream callsite cannot be inlined + // (Needs to revert modifications to the callee method, but also the call graph) + // (This assumes that inlining a request only makes sense if its downstream requests are satisfied - sync with heuristics!) + + val warnings = inline(request) + for (warning <- warnings) { + if ((callee.annotatedInline && btypes.compilerSettings.YoptWarningEmitAtInlineFailed) || warning.emitWarning(compilerSettings)) { + val annotWarn = if (callee.annotatedInline) " is annotated @inline but" else "" + val msg = s"${BackendReporting.methodSignature(callee.calleeDeclarationClass.internalName, callee.callee)}$annotWarn could not be inlined:\n$warning" + backendReporting.inlinerWarning(request.callsite.callsitePosition, msg) } } } @@ -163,10 +154,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { if (selfParamType.info.get.inlineInfo.sam.isEmpty) samParamTypes - 0 else samParamTypes.updated(0, selfParamType) } - val staticCallsite = Callsite( + val staticCallsite = callsite.copy( callsiteInstruction = newCallsiteInstruction, - callsiteMethod = callsite.callsiteMethod, - callsiteClass = callsite.callsiteClass, callee = Right(Callee( callee = implClassMethod, calleeDeclarationClass = implClassBType, @@ -175,11 +164,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { annotatedInline = annotatedInline, annotatedNoInline = annotatedNoInline, samParamTypes = staticCallSamParamTypes, - calleeInfoWarning = infoWarning)), - argInfos = callsite.argInfos, - callsiteStackHeight = callsite.callsiteStackHeight, - receiverKnownNotNull = callsite.receiverKnownNotNull, - callsitePosition = callsite.callsitePosition + calleeInfoWarning = infoWarning)) ) callGraph.addCallsite(staticCallsite) } @@ -261,30 +246,93 @@ class Inliner[BT <: BTypes](val btypes: BT) { } /** - * Inline the callsites of an inlining request and its post-inlining requests. + * Given an InlineRequest(mainCallsite, post = List(postCallsite)), the postCallsite is a callsite + * in the method `mainCallsite.callee`. Once the mainCallsite is inlined into the target method + * (mainCallsite.callsiteMethod), we need to find the cloned callsite that corresponds to the + * postCallsite so we can inline that into the target method as well. + * + * However, it is possible that there is no cloned callsite at all that corresponds to the + * postCallsite, for example if the corresponding callsite already inlined. Example: + * + * def a() = 1 + * def b() = a() + 2 + * def c() = b() + 3 + * def d() = c() + 4 + * + * We have the following callsite objects in the call graph: + * + * c1 = a() in b + * c2 = b() in c + * c3 = c() in d + * + * Assume we have the following inline request + * r = InlineRequest(c3, + * post = List(InlineRequest(c2, + * post = List(InlineRequest(c1, post = Nil))))) + * + * But before inlining r, assume a separate InlineRequest(c2, post = Nil) is inlined first. We get + * + * c1' = a() in c // added to the call graph + * c1.inlinedClones += (c1' at c2) // remember that c1' was created when inlining c2 + * ~c2~ // c2 is removed from the call graph + * + * If we now inline r, we first inline c3. We get + * + * c1'' = a() in d // added to call graph + * c1'.inlinedClones += (c1'' at c3) // remember that c1'' was created when inlining c3 + * ~c3~ + * + * Now we continue with the post-requests for r, i.e. c2. + * - we try to find the clone of c2 that was created when inlining c3 - but there is none. c2 + * was already inlined before + * - we continue with the post-request of c2: c1 + * - we search for the callsite of c1 that was cloned when inlining c2, we find c1' + * - recursively we search for the callsite of c1' that was cloned when inlining c3, we find c1'' + * - so we create an inline request for c1'' + */ + def adaptPostRequestForMainCallsite(post: InlineRequest, mainCallsite: Callsite): List[InlineRequest] = { + def impl(post: InlineRequest, at: Callsite): List[InlineRequest] = { + post.callsite.inlinedClones.find(_.clonedWhenInlining == at) match { + case Some(clonedCallsite) => + List(InlineRequest(clonedCallsite.callsite, post.post)) + case None => + post.post.flatMap(impl(_, post.callsite)).flatMap(impl(_, at)) + } + } + impl(post, mainCallsite) + } + + + /** + * Inline the callsite of an inlining request and its post-inlining requests. * * @return An inliner warning for each callsite that could not be inlined. */ - def inline(request: InlineRequest): List[CannotInlineWarning] = canInline(request.callsite) match { - case Some(warning) => List(warning) + def inline(request: InlineRequest): List[CannotInlineWarning] = canInlineBody(request.callsite) match { + case Some(w) => List(w) case None => - val instructionsMap = inlineCallsite(request.callsite) - val postRequests = request.post.flatMap(post => { - // the post-request invocation instruction might not exist anymore: it might have been - // inlined itself, or eliminated by DCE. - for { - inlinedInvocationInstr <- instructionsMap.get(post.callsiteInstruction).map(_.asInstanceOf[MethodInsnNode]) - inlinedCallsite <- callGraph.callsites(request.callsite.callsiteMethod).get(inlinedInvocationInstr) - } yield InlineRequest(inlinedCallsite, post.post) - }) - postRequests flatMap inline + // Inlining a method can create unreachable code. Example: + // def f = throw e + // def g = f; println() // println is unreachable after inlining f + // If we have an inline request for a call to g, and f has been already inlined into g, we + // need to run DCE before inlining g. + val Right(callee) = request.callsite.callee + eliminateUnreachableCodeAndUpdateCallGraph(callee.callee, callee.calleeDeclarationClass.internalName) + // Skip over DCE'd callsites + if (callGraph.containsCallsite(request.callsite)) { + inlineCallsite(request.callsite) + val postRequests = request.post.flatMap(adaptPostRequestForMainCallsite(_, request.callsite)) + postRequests flatMap inline + } else { + Nil + } } /** * Copy and adapt the instructions of a method to a callsite. * * Preconditions: - * - The callsite can safely be inlined (canInline is true) + * - The callsite can safely be inlined (canInlineBody is true) * - The maxLocals and maxStack values of the callsite method are correctly computed * - The callsite method contains no unreachable basic blocks, i.e., running an Analyzer does * not produce any `null` frames @@ -292,7 +340,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { * @return A map associating instruction nodes of the callee with the corresponding cloned * instruction in the callsite method. */ - def inlineCallsite(callsite: Callsite): Map[AbstractInsnNode, AbstractInsnNode] = { + def inlineCallsite(callsite: Callsite): Unit = { import callsite.{callsiteClass, callsiteMethod, callsiteInstruction, receiverKnownNotNull, callsiteStackHeight} val Right(callsiteCallee) = callsite.callee import callsiteCallee.{callee, calleeDeclarationClass} @@ -447,28 +495,27 @@ class Inliner[BT <: BTypes](val btypes: BT) { callGraph.callsites(callee).valuesIterator foreach { originalCallsite => val newCallsiteIns = instructionMap(originalCallsite.callsiteInstruction).asInstanceOf[MethodInsnNode] val argInfos = originalCallsite.argInfos flatMap mapArgInfo - callGraph.addCallsite(Callsite( + val newCallsite = originalCallsite.copy( callsiteInstruction = newCallsiteIns, callsiteMethod = callsiteMethod, callsiteClass = callsiteClass, - callee = originalCallsite.callee, argInfos = argInfos, - callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight, - receiverKnownNotNull = originalCallsite.receiverKnownNotNull, - callsitePosition = originalCallsite.callsitePosition - )) + callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight + ) + originalCallsite.inlinedClones += ClonedCallsite(newCallsite, callsite) + callGraph.addCallsite(newCallsite) } callGraph.closureInstantiations(callee).valuesIterator foreach { originalClosureInit => val newIndy = instructionMap(originalClosureInit.lambdaMetaFactoryCall.indy).asInstanceOf[InvokeDynamicInsnNode] val capturedArgInfos = originalClosureInit.capturedArgInfos flatMap mapArgInfo - callGraph.addClosureInstantiation( - ClosureInstantiation( - originalClosureInit.lambdaMetaFactoryCall.copy(indy = newIndy), - callsiteMethod, - callsiteClass, - capturedArgInfos) - ) + val newClosureInit = ClosureInstantiation( + originalClosureInit.lambdaMetaFactoryCall.copy(indy = newIndy), + callsiteMethod, + callsiteClass, + capturedArgInfos) + originalClosureInit.inlinedClones += newClosureInit + callGraph.addClosureInstantiation(newClosureInit) } // Remove the elided invocation from the call graph @@ -476,15 +523,48 @@ class Inliner[BT <: BTypes](val btypes: BT) { // Inlining a method body can render some code unreachable, see example above (in runInliner). unreachableCodeEliminated -= callsiteMethod + } + + /** + * Check whether an inlining can be performed. This method performs tests that don't change even + * if the body of the callee is changed by the inliner / optimizer, so it can be used early + * (when looking at the call graph and collecting inline requests for the program). + * + * The tests that inspect the callee's instructions are implemented in method `canInlineBody`, + * which is queried when performing an inline. + * + * @return `Some(message)` if inlining cannot be performed, `None` otherwise + */ + def earlyCanInlineCheck(callsite: Callsite): Option[CannotInlineWarning] = { + import callsite.{callsiteMethod, callsiteClass} + val Right(callsiteCallee) = callsite.callee + import callsiteCallee.{callee, calleeDeclarationClass} - instructionMap + if (isSynchronizedMethod(callee)) { + // Could be done by locking on the receiver, wrapping the inlined code in a try and unlocking + // in finally. But it's probably not worth the effort, scala never emits synchronized methods. + Some(SynchronizedMethod(calleeDeclarationClass.internalName, callee.name, callee.desc)) + } else if (isStrictfpMethod(callsiteMethod) != isStrictfpMethod(callee)) { + Some(StrictfpMismatch( + calleeDeclarationClass.internalName, callee.name, callee.desc, + callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)) + } else + None } /** - * Check whether an inling can be performed. + * Check whether the body of the callee contains any instructions that prevent the callsite from + * being inlined. See also method `earlyCanInlineCheck`. + * + * The result of this check depends on changes to the callee method's body. For example, if the + * callee initially invokes a private method, it cannot be inlined into a different class. If the + * private method is inlined into the callee, inlining the callee becomes possible. Therefore + * we don't query it while traversing the call graph and selecting callsites to inline - it might + * rule out callsites that can be inlined just fine. + * * @return `Some(message)` if inlining cannot be performed, `None` otherwise */ - def canInline(callsite: Callsite): Option[CannotInlineWarning] = { + def canInlineBody(callsite: Callsite): Option[CannotInlineWarning] = { import callsite.{callsiteInstruction, callsiteMethod, callsiteClass, callsiteStackHeight} val Right(callsiteCallee) = callsite.callee import callsiteCallee.{callee, calleeDeclarationClass} @@ -518,14 +598,6 @@ class Inliner[BT <: BTypes](val btypes: BT) { Some(ResultingMethodTooLarge( calleeDeclarationClass.internalName, callee.name, callee.desc, callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)) - } else if (isSynchronizedMethod(callee)) { - // Could be done by locking on the receiver, wrapping the inlined code in a try and unlocking - // in finally. But it's probably not worth the effort, scala never emits synchronized methods. - Some(SynchronizedMethod(calleeDeclarationClass.internalName, callee.name, callee.desc)) - } else if (isStrictfpMethod(callsiteMethod) != isStrictfpMethod(callee)) { - Some(StrictfpMismatch( - calleeDeclarationClass.internalName, callee.name, callee.desc, - callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)) } else if (!callee.tryCatchBlocks.isEmpty && stackHasNonParameters) { Some(MethodWithHandlerCalledOnNonEmptyStack( calleeDeclarationClass.internalName, callee.name, callee.desc, diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala index e559b63c09..89a768fd9c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala @@ -12,14 +12,17 @@ import scala.tools.asm.Type import scala.tools.asm.tree.{MethodNode, MethodInsnNode} import scala.tools.nsc.backend.jvm.BTypes.InternalName import scala.collection.convert.decorateAsScala._ +import scala.tools.nsc.backend.jvm.BackendReporting.OptimizerWarning class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { import bTypes._ import inliner._ import callGraph._ - case class InlineRequest(callsite: Callsite, post: List[PostInlineRequest]) - case class PostInlineRequest(callsiteInstruction: MethodInsnNode, post: List[PostInlineRequest]) + case class InlineRequest(callsite: Callsite, post: List[InlineRequest]) { + // invariant: all post inline requests denote callsites in the callee of the main callsite + for (pr <- post) assert(pr.callsite.callsiteMethod == callsite.callee.get.callee, s"Callsite method mismatch: main $callsite - post ${pr.callsite}") + } /** * Select callsites from the call graph that should be inlined, grouped by the containing method. @@ -38,28 +41,35 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { compilingMethods.map(methodNode => { var requests = Set.empty[InlineRequest] callGraph.callsites(methodNode).valuesIterator foreach { - case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, _, annotatedInline, _, _, warning)), _, _, _, pos) => - val request = inlineRequest(callsite) - requests ++= request - if (request.isEmpty) { - if (annotatedInline && bTypes.compilerSettings.YoptWarningEmitAtInlineFailed) { - // if the callsite is annotated @inline, we report an inline warning even if the underlying - // reason is, for example, mixed compilation (which has a separate -Yopt-warning flag). - def initMsg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)} is annotated @inline but cannot be inlined" - def warnMsg = warning.map(" Possible reason:\n" + _).getOrElse("") - if (doRewriteTraitCallsite(callsite)) - backendReporting.inlinerWarning(pos, s"$initMsg: the trait method call could not be rewritten to the static implementation method." + warnMsg) - else if (!safeToInline) - backendReporting.inlinerWarning(pos, s"$initMsg: the method is not final and may be overridden." + warnMsg) - else - backendReporting.inlinerWarning(pos, s"$initMsg." + warnMsg) - } else if (warning.isDefined && warning.get.emitWarning(compilerSettings)) { - // when annotatedInline is false, and there is some warning, the callsite metadata is possibly incomplete. - backendReporting.inlinerWarning(pos, s"there was a problem determining if method ${callee.name} can be inlined: \n"+ warning.get) - } + case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, _, calleeAnnotatedInline, _, _, callsiteWarning)), _, _, _, pos, _, _) => + inlineRequest(callsite) match { + case Some(Right(req)) => requests += req + case Some(Left(w)) => + if ((calleeAnnotatedInline && bTypes.compilerSettings.YoptWarningEmitAtInlineFailed) || w.emitWarning(compilerSettings)) { + val annotWarn = if (calleeAnnotatedInline) " is annotated @inline but" else "" + val msg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)}$annotWarn could not be inlined:\n$w" + backendReporting.inlinerWarning(callsite.callsitePosition, msg) + } + + case None => + if (calleeAnnotatedInline && !callsite.annotatedNoInline && bTypes.compilerSettings.YoptWarningEmitAtInlineFailed) { + // if the callsite is annotated @inline, we report an inline warning even if the underlying + // reason is, for example, mixed compilation (which has a separate -Yopt-warning flag). + def initMsg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)} is annotated @inline but cannot be inlined" + def warnMsg = callsiteWarning.map(" Possible reason:\n" + _).getOrElse("") + if (doRewriteTraitCallsite(callsite)) + backendReporting.inlinerWarning(pos, s"$initMsg: the trait method call could not be rewritten to the static implementation method." + warnMsg) + else if (!safeToInline) + backendReporting.inlinerWarning(pos, s"$initMsg: the method is not final and may be overridden." + warnMsg) + else + backendReporting.inlinerWarning(pos, s"$initMsg." + warnMsg) + } else if (callsiteWarning.isDefined && callsiteWarning.get.emitWarning(compilerSettings)) { + // when annotatedInline is false, and there is some warning, the callsite metadata is possibly incomplete. + backendReporting.inlinerWarning(pos, s"there was a problem determining if method ${callee.name} can be inlined: \n"+ callsiteWarning.get) + } } - case Callsite(ins, _, _, Left(warning), _, _, _, pos) => + case Callsite(ins, _, _, Left(warning), _, _, _, pos, _, _) => if (warning.emitWarning(compilerSettings)) backendReporting.inlinerWarning(pos, s"failed to determine if ${ins.name} should be inlined:\n$warning") } @@ -73,27 +83,39 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { * * The resulting inline request may contain post-inlining requests of callsites that in turn are * also selected as individual inlining requests. + * + * @return `None` if this callsite should not be inlined according to the active heuristic + * `Some(Left)` if the callsite cannot be inlined (for example because that would cause + * an IllegalAccessError) but should be according to the heuristic + * TODO: what if a downstream inline request would cause an IAE and we don't create an + * InlineRequest for the original callsite? new subclass of OptimizerWarning. + * `Some(Right)` if the callsite should be and can be inlined */ - def inlineRequest(callsite: Callsite): Option[InlineRequest] = compilerSettings.YoptInlineHeuristics.value match { - case "everything" => - if (callsite.callee.get.safeToInline) Some(InlineRequest(callsite, Nil)) - else None - - case "at-inline-annotated" => - val callee = callsite.callee.get - if (callee.safeToInline && callee.annotatedInline) Some(InlineRequest(callsite, Nil)) - else None - - case "default" => - val callee = callsite.callee.get - if (callee.safeToInline && !callee.annotatedNoInline) { - val shouldInlineHO = callee.samParamTypes.nonEmpty && (callee.samParamTypes exists { - case (index, _) => callsite.argInfos.contains(index) - }) - - if (shouldInlineHO || callee.annotatedInline) Some(InlineRequest(callsite, Nil)) + def inlineRequest(callsite: Callsite): Option[Either[OptimizerWarning, InlineRequest]] = { + val callee = callsite.callee.get + def requestIfCanInline(callsite: Callsite): Either[OptimizerWarning, InlineRequest] = inliner.earlyCanInlineCheck(callsite) match { + case Some(w) => Left(w) + case None => Right(InlineRequest(callsite, Nil)) + } + + compilerSettings.YoptInlineHeuristics.value match { + case "everything" => + if (callee.safeToInline) Some(requestIfCanInline(callsite)) else None - } else None + + case "at-inline-annotated" => + if (callee.safeToInline && callee.annotatedInline) Some(requestIfCanInline(callsite)) + else None + + case "default" => + if (callee.safeToInline && !callee.annotatedNoInline && !callsite.annotatedNoInline) { + def shouldInlineHO = callee.samParamTypes.nonEmpty && (callee.samParamTypes exists { + case (index, _) => callsite.argInfos.contains(index) + }) + if (callee.annotatedInline || callsite.annotatedInline || shouldInlineHO) Some(requestIfCanInline(callsite)) + else None + } else None + } } /* diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InstructionResultSize.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InstructionResultSize.scala index 8d744f6d13..79e44a8503 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InstructionResultSize.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InstructionResultSize.scala @@ -33,14 +33,10 @@ object InstructionResultSize { case LDC => instruction.asInstanceOf[LdcInsnNode].cst match { - case _: java.lang.Integer | - _: java.lang.Float | - _: String | - _: Type | - _: Handle => 1 - case _: java.lang.Long | _: java.lang.Double => 2 + + case _ => 1 } case ILOAD | diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala index 38f3c51892..a80b3d0487 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala @@ -51,38 +51,6 @@ class LocalOpt[BT <: BTypes](val btypes: BT) { import backendUtils._ /** - * In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM - * framework only computes these values during bytecode generation. - * - * Since there's currently no better way, we run a bytecode generator on the method and extract - * the computed values. This required changes to the ASM codebase: - * - the [[MethodWriter]] class was made public - * - accessors for maxLocals / maxStack were added to the MethodWriter class - * - * We could probably make this faster (and allocate less memory) by hacking the ASM framework - * more: create a subclass of MethodWriter with a /dev/null byteVector. Another option would be - * to create a separate visitor for computing those values, duplicating the functionality from the - * MethodWriter. - * - * NOTE: the maxStack value computed by this method allocates two slots for long / double values, - * as required by the JVM spec. For running an Analyzer, one slot per long / double would be fine. - * See comment in `analysis` package object. - */ - def computeMaxLocalsMaxStack(method: MethodNode): Unit = { - if (!maxLocalsMaxStackComputed(method)) { - method.maxLocals = 0 - method.maxStack = 0 - val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS) - val excs = method.exceptions.asScala.toArray - val mw = cw.visitMethod(method.access, method.name, method.desc, method.signature, excs).asInstanceOf[MethodWriter] - method.accept(mw) - method.maxLocals = mw.getMaxLocals - method.maxStack = mw.getMaxStack - maxLocalsMaxStackComputed += method - } - } - - /** * Remove unreachable code from a method. * * This implementation only removes instructions that are unreachable for an ASM analyzer / @@ -225,7 +193,7 @@ class LocalOpt[BT <: BTypes](val btypes: BT) { var i = 0 var liveLabels = Set.empty[LabelNode] var removedInstructions = Set.empty[AbstractInsnNode] - var maxLocals = Type.getArgumentsAndReturnSizes(method.desc) >> 2 - (if (BytecodeUtils.isStaticMethod(method)) 1 else 0) + var maxLocals = (Type.getArgumentsAndReturnSizes(method.desc) >> 2) - (if (BytecodeUtils.isStaticMethod(method)) 1 else 0) var maxStack = 0 val itr = method.instructions.iterator() while (itr.hasNext) { diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index 833f25537c..747d20a441 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -71,7 +71,9 @@ abstract class Erasure extends AddInterfaces } override protected def verifyJavaErasure = settings.Xverify || settings.debug - def needsJavaSig(tp: Type) = !settings.Ynogenericsig && NeedsSigCollector.collect(tp) + def needsJavaSig(tp: Type, throwsArgs: List[Type]) = !settings.Ynogenericsig && { + NeedsSigCollector.collect(tp) || throwsArgs.exists(NeedsSigCollector.collect) + } // only refer to type params that will actually make it into the sig, this excludes: // * higher-order type parameters @@ -277,7 +279,7 @@ abstract class Erasure extends AddInterfaces val preRebound = pre.baseType(sym.owner) // #2585 dotCleanup( ( - if (needsJavaSig(preRebound)) { + if (needsJavaSig(preRebound, Nil)) { val s = jsig(preRebound, existentiallyBound) if (s.charAt(0) == 'L') s.substring(0, s.length - 1) + "." + sym.javaSimpleName else fullNameInSig(sym) @@ -356,8 +358,9 @@ abstract class Erasure extends AddInterfaces else jsig(etp) } } - if (needsJavaSig(info)) { - try Some(jsig(info, toplevel = true)) + val throwsArgs = sym0.annotations flatMap ThrownException.unapply + if (needsJavaSig(info, throwsArgs)) { + try Some(jsig(info, toplevel = true) + throwsArgs.map("^" + jsig(_, toplevel = true)).mkString("")) catch { case ex: UnknownSig => None } } else None diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 26e04edcca..510c83eb88 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -3653,7 +3653,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val annType = annTpt.tpe finish( - if (typedFun.isErroneous) + if (typedFun.isErroneous || annType == null) ErroneousAnnotation else if (annType.typeSymbol isNonBottomSubClass ClassfileAnnotationClass) { // annotation to be saved as java classfile annotation @@ -4130,6 +4130,14 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper ann setType arg1.tpe.withAnnotation(annotInfo) } val atype = ann.tpe + // For `f(): @inline/noinline` callsites, add the InlineAnnotatedAttachment. TypeApplys + // are eliminated by erasure, so add it to the underlying function in this case. + def setInlineAttachment(t: Tree, att: InlineAnnotatedAttachment): Unit = t match { + case TypeApply(fun, _) => setInlineAttachment(fun, att) + case _ => t.updateAttachment(att) + } + if (atype.hasAnnotation(definitions.ScalaNoInlineClass)) setInlineAttachment(arg1, NoInlineCallsiteAttachment) + else if (atype.hasAnnotation(definitions.ScalaInlineClass)) setInlineAttachment(arg1, InlineCallsiteAttachment) Typed(arg1, resultingTypeTree(atype)) setPos tree.pos setType atype } } diff --git a/src/eclipse/interactive/.gitignore b/src/eclipse/interactive/.gitignore new file mode 100644 index 0000000000..fe789dd686 --- /dev/null +++ b/src/eclipse/interactive/.gitignore @@ -0,0 +1,2 @@ +# what appears to be a Scala IDE-generated file +.cache-main diff --git a/src/eclipse/partest/.gitignore b/src/eclipse/partest/.gitignore new file mode 100644 index 0000000000..fe789dd686 --- /dev/null +++ b/src/eclipse/partest/.gitignore @@ -0,0 +1,2 @@ +# what appears to be a Scala IDE-generated file +.cache-main diff --git a/src/eclipse/reflect/.gitignore b/src/eclipse/reflect/.gitignore new file mode 100644 index 0000000000..fe789dd686 --- /dev/null +++ b/src/eclipse/reflect/.gitignore @@ -0,0 +1,2 @@ +# what appears to be a Scala IDE-generated file +.cache-main diff --git a/src/eclipse/repl/.gitignore b/src/eclipse/repl/.gitignore new file mode 100644 index 0000000000..fe789dd686 --- /dev/null +++ b/src/eclipse/repl/.gitignore @@ -0,0 +1,2 @@ +# what appears to be a Scala IDE-generated file +.cache-main diff --git a/src/eclipse/scala-compiler/.gitignore b/src/eclipse/scala-compiler/.gitignore new file mode 100644 index 0000000000..fe789dd686 --- /dev/null +++ b/src/eclipse/scala-compiler/.gitignore @@ -0,0 +1,2 @@ +# what appears to be a Scala IDE-generated file +.cache-main diff --git a/src/eclipse/scala-library/.gitignore b/src/eclipse/scala-library/.gitignore new file mode 100644 index 0000000000..fe789dd686 --- /dev/null +++ b/src/eclipse/scala-library/.gitignore @@ -0,0 +1,2 @@ +# what appears to be a Scala IDE-generated file +.cache-main diff --git a/src/eclipse/scaladoc/.gitignore b/src/eclipse/scaladoc/.gitignore new file mode 100644 index 0000000000..fe789dd686 --- /dev/null +++ b/src/eclipse/scaladoc/.gitignore @@ -0,0 +1,2 @@ +# what appears to be a Scala IDE-generated file +.cache-main diff --git a/src/eclipse/scalap/.gitignore b/src/eclipse/scalap/.gitignore new file mode 100644 index 0000000000..fe789dd686 --- /dev/null +++ b/src/eclipse/scalap/.gitignore @@ -0,0 +1,2 @@ +# what appears to be a Scala IDE-generated file +.cache-main diff --git a/src/eclipse/test-junit/.gitignore b/src/eclipse/test-junit/.gitignore new file mode 100644 index 0000000000..fe789dd686 --- /dev/null +++ b/src/eclipse/test-junit/.gitignore @@ -0,0 +1,2 @@ +# what appears to be a Scala IDE-generated file +.cache-main diff --git a/src/library/scala/collection/GenTraversableOnce.scala b/src/library/scala/collection/GenTraversableOnce.scala index f77462ce88..a45ec965f5 100644 --- a/src/library/scala/collection/GenTraversableOnce.scala +++ b/src/library/scala/collection/GenTraversableOnce.scala @@ -393,20 +393,35 @@ trait GenTraversableOnce[+A] extends Any { */ def minBy[B](f: A => B)(implicit cmp: Ordering[B]): A - def forall(pred: A => Boolean): Boolean + /** Tests whether a predicate holds for all elements of this $coll. + * + * $mayNotTerminateInf + * + * @param p the predicate used to test elements. + * @return `true` if this $coll is empty or the given predicate `p` + * holds for all elements of this $coll, otherwise `false`. + */ + def forall(@deprecatedName('pred) p: A => Boolean): Boolean - def exists(pred: A => Boolean): Boolean + /** Tests whether a predicate holds for at least one element of this $coll. + * + * $mayNotTerminateInf + * + * @param p the predicate used to test elements. + * @return `true` if the given predicate `p` is satisfied by at least one element of this $coll, otherwise `false` + */ + def exists(@deprecatedName('pred) p: A => Boolean): Boolean /** Finds the first element of the $coll satisfying a predicate, if any. * * $mayNotTerminateInf * $orderDependent * - * @param pred the predicate used to test elements. + * @param p the predicate used to test elements. * @return an option value containing the first element in the $coll * that satisfies `p`, or `None` if none exists. */ - def find(pred: A => Boolean): Option[A] + def find(@deprecatedName('pred) p: A => Boolean): Option[A] /** Copies values of this $coll to an array. * Fills the given array `xs` with values of this $coll. diff --git a/src/library/scala/collection/TraversableLike.scala b/src/library/scala/collection/TraversableLike.scala index f187a7a655..a0b1430d17 100644 --- a/src/library/scala/collection/TraversableLike.scala +++ b/src/library/scala/collection/TraversableLike.scala @@ -357,7 +357,7 @@ trait TraversableLike[+A, +Repr] extends Any result } - /** Tests whether a predicate holds for some of the elements of this $coll. + /** Tests whether a predicate holds for at least one element of this $coll. * * $mayNotTerminateInf * diff --git a/src/library/scala/collection/immutable/ListMap.scala b/src/library/scala/collection/immutable/ListMap.scala index c5773338f5..c59f4f7436 100644 --- a/src/library/scala/collection/immutable/ListMap.scala +++ b/src/library/scala/collection/immutable/ListMap.scala @@ -36,7 +36,7 @@ object ListMap extends ImmutableMapFactory[ListMap] { } } -/** This class implements immutable maps using a list-based data structure. +/** This class implements immutable maps using a list-based data structure, which preserves insertion order. * Instances of `ListMap` represent empty maps; they can be either created by * calling the constructor directly, or by applying the function `ListMap.empty`. * diff --git a/src/library/scala/collection/immutable/RedBlackTree.scala b/src/library/scala/collection/immutable/RedBlackTree.scala index 7e8cfcc902..afb4c9c552 100644 --- a/src/library/scala/collection/immutable/RedBlackTree.scala +++ b/src/library/scala/collection/immutable/RedBlackTree.scala @@ -409,11 +409,11 @@ object RedBlackTree { def cons[B](x: B, xs: NList[B]): NList[B] = new NList(x, xs) - def foldLeft[A, B](xs: NList[A], z: B)(f: (B, A) => B): B = { + def foldLeft[A, B](xs: NList[A], z: B)(op: (B, A) => B): B = { var acc = z var these = xs while (these ne null) { - acc = f(acc, these.head) + acc = op(acc, these.head) these = these.tail } acc diff --git a/src/library/scala/collection/immutable/Set.scala b/src/library/scala/collection/immutable/Set.scala index a115469b83..031e5248c1 100644 --- a/src/library/scala/collection/immutable/Set.scala +++ b/src/library/scala/collection/immutable/Set.scala @@ -33,7 +33,7 @@ trait Set[A] extends Iterable[A] with Parallelizable[A, ParSet[A]] { override def companion: GenericCompanion[Set] = Set - + /** Returns this $coll as an immutable set, perhaps accepting a * wider range of elements. Since it already is an @@ -63,7 +63,7 @@ trait Set[A] extends Iterable[A] object Set extends ImmutableSetFactory[Set] { /** $setCanBuildFromInfo */ implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Set[A]] = setCanBuildFrom[A] - + /** An optimized representation for immutable empty sets */ private object EmptySet extends AbstractSet[Any] with Set[Any] with Serializable { override def size: Int = 0 @@ -71,7 +71,7 @@ object Set extends ImmutableSetFactory[Set] { def + (elem: Any): Set[Any] = new Set1(elem) def - (elem: Any): Set[Any] = this def iterator: Iterator[Any] = Iterator.empty - override def foreach[U](f: Any => U): Unit = {} + override def foreach[U](f: Any => U): Unit = () override def toSet[B >: Any]: Set[B] = this.asInstanceOf[Set[B]] } private[collection] def emptyInstance: Set[Any] = EmptySet @@ -90,17 +90,17 @@ object Set extends ImmutableSetFactory[Set] { else this def iterator: Iterator[A] = Iterator(elem1) - override def foreach[U](f: A => U): Unit = { + override def foreach[U](f: A => U): Unit = { f(elem1) } - override def exists(f: A => Boolean): Boolean = { - f(elem1) + override def exists(@deprecatedName('f) p: A => Boolean): Boolean = { + p(elem1) } - override def forall(f: A => Boolean): Boolean = { - f(elem1) + override def forall(@deprecatedName('f) p: A => Boolean): Boolean = { + p(elem1) } - override def find(f: A => Boolean): Option[A] = { - if (f(elem1)) Some(elem1) + override def find(@deprecatedName('f) p: A => Boolean): Option[A] = { + if (p(elem1)) Some(elem1) else None } // Why is Set1 non-final? Need to fix that! @@ -124,18 +124,18 @@ object Set extends ImmutableSetFactory[Set] { else this def iterator: Iterator[A] = Iterator(elem1, elem2) - override def foreach[U](f: A => U): Unit = { + override def foreach[U](f: A => U): Unit = { f(elem1); f(elem2) } - override def exists(f: A => Boolean): Boolean = { - f(elem1) || f(elem2) + override def exists(@deprecatedName('f) p: A => Boolean): Boolean = { + p(elem1) || p(elem2) } - override def forall(f: A => Boolean): Boolean = { - f(elem1) && f(elem2) + override def forall(@deprecatedName('f) p: A => Boolean): Boolean = { + p(elem1) && p(elem2) } - override def find(f: A => Boolean): Option[A] = { - if (f(elem1)) Some(elem1) - else if (f(elem2)) Some(elem2) + override def find(@deprecatedName('f) p: A => Boolean): Option[A] = { + if (p(elem1)) Some(elem1) + else if (p(elem2)) Some(elem2) else None } // Why is Set2 non-final? Need to fix that! @@ -159,19 +159,19 @@ object Set extends ImmutableSetFactory[Set] { else this def iterator: Iterator[A] = Iterator(elem1, elem2, elem3) - override def foreach[U](f: A => U): Unit = { + override def foreach[U](f: A => U): Unit = { f(elem1); f(elem2); f(elem3) } - override def exists(f: A => Boolean): Boolean = { - f(elem1) || f(elem2) || f(elem3) + override def exists(@deprecatedName('f) p: A => Boolean): Boolean = { + p(elem1) || p(elem2) || p(elem3) } - override def forall(f: A => Boolean): Boolean = { - f(elem1) && f(elem2) && f(elem3) + override def forall(@deprecatedName('f) p: A => Boolean): Boolean = { + p(elem1) && p(elem2) && p(elem3) } - override def find(f: A => Boolean): Option[A] = { - if (f(elem1)) Some(elem1) - else if (f(elem2)) Some(elem2) - else if (f(elem3)) Some(elem3) + override def find(@deprecatedName('f) p: A => Boolean): Option[A] = { + if (p(elem1)) Some(elem1) + else if (p(elem2)) Some(elem2) + else if (p(elem3)) Some(elem3) else None } // Why is Set3 non-final? Need to fix that! @@ -196,20 +196,20 @@ object Set extends ImmutableSetFactory[Set] { else this def iterator: Iterator[A] = Iterator(elem1, elem2, elem3, elem4) - override def foreach[U](f: A => U): Unit = { + override def foreach[U](f: A => U): Unit = { f(elem1); f(elem2); f(elem3); f(elem4) } - override def exists(f: A => Boolean): Boolean = { - f(elem1) || f(elem2) || f(elem3) || f(elem4) + override def exists(@deprecatedName('f) p: A => Boolean): Boolean = { + p(elem1) || p(elem2) || p(elem3) || p(elem4) } - override def forall(f: A => Boolean): Boolean = { - f(elem1) && f(elem2) && f(elem3) && f(elem4) + override def forall(@deprecatedName('f) p: A => Boolean): Boolean = { + p(elem1) && p(elem2) && p(elem3) && p(elem4) } - override def find(f: A => Boolean): Option[A] = { - if (f(elem1)) Some(elem1) - else if (f(elem2)) Some(elem2) - else if (f(elem3)) Some(elem3) - else if (f(elem4)) Some(elem4) + override def find(@deprecatedName('f) p: A => Boolean): Option[A] = { + if (p(elem1)) Some(elem1) + else if (p(elem2)) Some(elem2) + else if (p(elem3)) Some(elem3) + else if (p(elem4)) Some(elem4) else None } // Why is Set4 non-final? Need to fix that! diff --git a/src/library/scala/collection/mutable/OpenHashMap.scala b/src/library/scala/collection/mutable/OpenHashMap.scala index 24f5761cf5..5f8f5b9a0a 100644 --- a/src/library/scala/collection/mutable/OpenHashMap.scala +++ b/src/library/scala/collection/mutable/OpenHashMap.scala @@ -81,6 +81,9 @@ extends AbstractMap[Key, Value] h ^ (h >>> 7) ^ (h >>> 4) } + /** Increase the size of the table. + * Copy only the occupied slots, effectively eliminating the deleted slots. + */ private[this] def growTable() = { val oldSize = mask + 1 val newSize = 4 * oldSize @@ -92,8 +95,18 @@ extends AbstractMap[Key, Value] deleted = 0 } + /** Return the index of the first slot in the hash table (in probe order) + * that either is empty, or is or was last occupied by the given key. + */ private[this] def findIndex(key: Key) : Int = findIndex(key, hashOf(key)) + /** Return the index of the first slot in the hash table (in probe order) + * that either is empty, or is or was last occupied by the given key. + * + * This method is an optimization for when the hash value is in hand. + * + * @param hash hash value for `key` + */ private[this] def findIndex(key: Key, hash: Int): Int = { var j = hash @@ -136,7 +149,11 @@ extends AbstractMap[Key, Value] None } else { val res = entry.value - if (entry.value == None) { size += 1; modCount += 1 } + if (entry.value == None) { + size += 1 + deleted -= 1 + modCount += 1 + } entry.value = Some(value) res } diff --git a/src/library/scala/collection/parallel/ParIterableLike.scala b/src/library/scala/collection/parallel/ParIterableLike.scala index 53f9a7b87a..a5a82764d6 100644 --- a/src/library/scala/collection/parallel/ParIterableLike.scala +++ b/src/library/scala/collection/parallel/ParIterableLike.scala @@ -520,22 +520,22 @@ self: ParIterableLike[T, Repr, Sequential] => * * $abortsignalling * - * @param pred a predicate used to test elements + * @param p a predicate used to test elements * @return true if `p` holds for all elements, false otherwise */ - def forall(pred: T => Boolean): Boolean = { - tasksupport.executeAndWaitResult(new Forall(pred, splitter assign new DefaultSignalling with VolatileAbort)) + def forall(@deprecatedName('pred) p: T => Boolean): Boolean = { + tasksupport.executeAndWaitResult(new Forall(p, splitter assign new DefaultSignalling with VolatileAbort)) } /** Tests whether a predicate holds for some element of this $coll. * * $abortsignalling * - * @param pred a predicate used to test elements + * @param p a predicate used to test elements * @return true if `p` holds for some element, false otherwise */ - def exists(pred: T => Boolean): Boolean = { - tasksupport.executeAndWaitResult(new Exists(pred, splitter assign new DefaultSignalling with VolatileAbort)) + def exists(@deprecatedName('pred) p: T => Boolean): Boolean = { + tasksupport.executeAndWaitResult(new Exists(p, splitter assign new DefaultSignalling with VolatileAbort)) } /** Finds some element in the collection for which the predicate holds, if such @@ -546,11 +546,11 @@ self: ParIterableLike[T, Repr, Sequential] => * * $abortsignalling * - * @param pred predicate used to test the elements + * @param p predicate used to test the elements * @return an option value with the element if such an element exists, or `None` otherwise */ - def find(pred: T => Boolean): Option[T] = { - tasksupport.executeAndWaitResult(new Find(pred, splitter assign new DefaultSignalling with VolatileAbort)) + def find(@deprecatedName('pred) p: T => Boolean): Option[T] = { + tasksupport.executeAndWaitResult(new Find(p, splitter assign new DefaultSignalling with VolatileAbort)) } /** Creates a combiner factory. Each combiner factory instance is used diff --git a/src/library/scala/inline.scala b/src/library/scala/inline.scala index a21cced928..dc55af301c 100644 --- a/src/library/scala/inline.scala +++ b/src/library/scala/inline.scala @@ -11,8 +11,30 @@ package scala /** - * An annotation on methods that requests that the compiler should - * try especially hard to inline the annotated method. + * An annotation on methods that requests that the compiler should try especially hard to inline the + * annotated method. The annotation can be used at definition site or at callsite. + * + * {{{ + * @inline final def f1(x: Int) = x + * @noinline final def f2(x: Int) = x + * final def f3(x: Int) = x + * + * def t1 = f1(1) // inlined if possible + * def t2 = f2(1) // not inlined + * def t3 = f3(1) // may be inlined (heuristics) + * def t4 = f1(1): @noinline // not inlined (override at callsite) + * def t5 = f2(1): @inline // not inlined (cannot override the @noinline at f2's definition) + * def t6 = f3(1): @inline // inlined if possible + * def t7 = f3(1): @noinline // not inlined + * } + * }}} + * + * Note: parentheses are required when annotating a callsite withing a larger expression. + * + * {{{ + * def t1 = f1(1) + f1(1): @noinline // equivalent to (f1(1) + f1(1)): @noinline + * def t2 = f1(1) + (f1(1): @noinline) // the second call to f1 is not inlined + * }}} * * @author Lex Spoon * @version 1.0, 2007-5-21 diff --git a/src/library/scala/noinline.scala b/src/library/scala/noinline.scala index 38fd4c39d6..a427e170f4 100644 --- a/src/library/scala/noinline.scala +++ b/src/library/scala/noinline.scala @@ -11,8 +11,30 @@ package scala /** - * An annotation on methods that forbids the compiler to inline the - * method, no matter how safe the inlining appears to be. + * An annotation on methods that forbids the compiler to inline the method, no matter how safe the + * inlining appears to be. The annotation can be used at definition site or at callsite. + * + * {{{ + * @inline final def f1(x: Int) = x + * @noinline final def f2(x: Int) = x + * final def f3(x: Int) = x + * + * def t1 = f1(1) // inlined if possible + * def t2 = f2(1) // not inlined + * def t3 = f3(1) // may be inlined (heuristics) + * def t4 = f1(1): @noinline // not inlined (override at callsite) + * def t5 = f2(1): @inline // not inlined (cannot override the @noinline at f2's definition) + * def t6 = f3(1): @inline // inlined if possible + * def t7 = f3(1): @noinline // not inlined + * } + * }}} + * + * Note: parentheses are required when annotating a callsite withing a larger expression. + * + * {{{ + * def t1 = f1(1) + f1(1): @noinline // equivalent to (f1(1) + f1(1)): @noinline + * def t2 = f1(1) + (f1(1): @noinline) // the second call to f1 is not inlined + * }}} * * @author Lex Spoon * @version 1.0, 2007-5-21 diff --git a/src/library/scala/runtime/Tuple2Zipped.scala b/src/library/scala/runtime/Tuple2Zipped.scala index 1c432b0403..15331d4160 100644 --- a/src/library/scala/runtime/Tuple2Zipped.scala +++ b/src/library/scala/runtime/Tuple2Zipped.scala @@ -84,12 +84,12 @@ final class Tuple2Zipped[El1, Repr1, El2, Repr2](val colls: (TraversableLike[El1 (b1.result(), b2.result()) } - def exists(f: (El1, El2) => Boolean): Boolean = { + def exists(@deprecatedName('f) p: (El1, El2) => Boolean): Boolean = { val elems2 = colls._2.iterator for (el1 <- colls._1) { if (elems2.hasNext) { - if (f(el1, elems2.next())) + if (p(el1, elems2.next())) return true } else return false @@ -97,8 +97,8 @@ final class Tuple2Zipped[El1, Repr1, El2, Repr2](val colls: (TraversableLike[El1 false } - def forall(f: (El1, El2) => Boolean): Boolean = - !exists((x, y) => !f(x, y)) + def forall(@deprecatedName('f) p: (El1, El2) => Boolean): Boolean = + !exists((x, y) => !p(x, y)) def foreach[U](f: (El1, El2) => U): Unit = { val elems2 = colls._2.iterator diff --git a/src/library/scala/runtime/Tuple3Zipped.scala b/src/library/scala/runtime/Tuple3Zipped.scala index 3bab86921b..62bee5ff0e 100644 --- a/src/library/scala/runtime/Tuple3Zipped.scala +++ b/src/library/scala/runtime/Tuple3Zipped.scala @@ -90,13 +90,13 @@ final class Tuple3Zipped[El1, Repr1, El2, Repr2, El3, Repr3](val colls: (Travers result } - def exists(f: (El1, El2, El3) => Boolean): Boolean = { + def exists(@deprecatedName('f) p: (El1, El2, El3) => Boolean): Boolean = { val elems2 = colls._2.iterator val elems3 = colls._3.iterator for (el1 <- colls._1) { if (elems2.hasNext && elems3.hasNext) { - if (f(el1, elems2.next(), elems3.next())) + if (p(el1, elems2.next(), elems3.next())) return true } else return false @@ -104,8 +104,8 @@ final class Tuple3Zipped[El1, Repr1, El2, Repr2, El3, Repr3](val colls: (Travers false } - def forall(f: (El1, El2, El3) => Boolean): Boolean = - !exists((x, y, z) => !f(x, y, z)) + def forall(@deprecatedName('f) p: (El1, El2, El3) => Boolean): Boolean = + !exists((x, y, z) => !p(x, y, z)) def foreach[U](f: (El1, El2, El3) => U): Unit = { val elems2 = colls._2.iterator diff --git a/src/library/scala/util/Either.scala b/src/library/scala/util/Either.scala index 6ea9da64f2..675b3c8c61 100644 --- a/src/library/scala/util/Either.scala +++ b/src/library/scala/util/Either.scala @@ -329,8 +329,8 @@ object Either { * }}} * */ - def forall(f: A => Boolean) = e match { - case Left(a) => f(a) + def forall(@deprecatedName('f) p: A => Boolean) = e match { + case Left(a) => p(a) case Right(_) => true } @@ -345,8 +345,8 @@ object Either { * }}} * */ - def exists(f: A => Boolean) = e match { - case Left(a) => f(a) + def exists(@deprecatedName('f) p: A => Boolean) = e match { + case Left(a) => p(a) case Right(_) => false } @@ -507,9 +507,9 @@ object Either { * Left(12).right.exists(_ > 10) // false * }}} */ - def exists(f: B => Boolean) = e match { + def exists(@deprecatedName('f) p: B => Boolean) = e match { case Left(_) => false - case Right(b) => f(b) + case Right(b) => p(b) } /** diff --git a/src/reflect/scala/reflect/internal/AnnotationInfos.scala b/src/reflect/scala/reflect/internal/AnnotationInfos.scala index b923541b56..207a66e360 100644 --- a/src/reflect/scala/reflect/internal/AnnotationInfos.scala +++ b/src/reflect/scala/reflect/internal/AnnotationInfos.scala @@ -30,12 +30,6 @@ trait AnnotationInfos extends api.Annotations { self: SymbolTable => def staticAnnotations = annotations filter (_.isStatic) - /** Symbols of any @throws annotations on this symbol. - */ - def throwsAnnotations(): List[Symbol] = annotations collect { - case ThrownException(exc) => exc - } - def addThrowsAnnotation(throwableSym: Symbol): Self = { val throwableTpe = if (throwableSym.isMonomorphicType) throwableSym.tpe else { debuglog(s"Encountered polymorphic exception `${throwableSym.fullName}` while parsing class file.") @@ -406,24 +400,24 @@ trait AnnotationInfos extends api.Annotations { self: SymbolTable => class ErroneousAnnotation() extends CompleteAnnotationInfo(ErrorType, Nil, Nil) - /** Extracts symbol of thrown exception from AnnotationInfo. + /** Extracts the type of the thrown exception from an AnnotationInfo. * * Supports both “old-style” `@throws(classOf[Exception])` * as well as “new-stye” `@throws[Exception]("cause")` annotations. */ object ThrownException { - def unapply(ann: AnnotationInfo): Option[Symbol] = { + def unapply(ann: AnnotationInfo): Option[Type] = { ann match { case AnnotationInfo(tpe, _, _) if tpe.typeSymbol != ThrowsClass => None // old-style: @throws(classOf[Exception]) (which is throws[T](classOf[Exception])) case AnnotationInfo(_, List(Literal(Constant(tpe: Type))), _) => - Some(tpe.typeSymbol) + Some(tpe) // new-style: @throws[Exception], @throws[Exception]("cause") case AnnotationInfo(TypeRef(_, _, arg :: _), _, _) => - Some(arg.typeSymbol) + Some(arg) case AnnotationInfo(TypeRef(_, _, Nil), _, _) => - Some(ThrowableClass) + Some(ThrowableTpe) } } } diff --git a/src/reflect/scala/reflect/internal/StdAttachments.scala b/src/reflect/scala/reflect/internal/StdAttachments.scala index cca33253be..8358c1295c 100644 --- a/src/reflect/scala/reflect/internal/StdAttachments.scala +++ b/src/reflect/scala/reflect/internal/StdAttachments.scala @@ -52,4 +52,8 @@ trait StdAttachments { /** Untyped list of subpatterns attached to selector dummy. */ case class SubpatternsAttachment(patterns: List[Tree]) + + abstract class InlineAnnotatedAttachment + case object NoInlineCallsiteAttachment extends InlineAnnotatedAttachment + case object InlineCallsiteAttachment extends InlineAnnotatedAttachment } diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index c3b2a8da08..ce93841567 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -2964,7 +2964,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => loop(info) } - override def exceptions = annotations flatMap ThrownException.unapply + override def exceptions = for (ThrownException(tp) <- annotations) yield tp.typeSymbol } implicit val MethodSymbolTag = ClassTag[MethodSymbol](classOf[MethodSymbol]) diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index 1e9a4fe8a5..0132fff17c 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -41,6 +41,8 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => this.ForAttachment this.SyntheticUnitAttachment this.SubpatternsAttachment + this.NoInlineCallsiteAttachment + this.InlineCallsiteAttachment this.noPrint this.typeDebug this.Range diff --git a/src/repl/scala/tools/nsc/interpreter/ILoop.scala b/src/repl/scala/tools/nsc/interpreter/ILoop.scala index 5851aa88df..060bccb451 100644 --- a/src/repl/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/repl/scala/tools/nsc/interpreter/ILoop.scala @@ -693,25 +693,37 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) Iterator continually in.readLine("") takeWhile (x => x != null && cond(x)) } + /* :paste -raw file + * or + * :paste < EOF + * your code + * EOF + * :paste <~ EOF + * ~your code + * EOF + */ def pasteCommand(arg: String): Result = { var shouldReplay: Option[String] = None def result = Result(keepRunning = true, shouldReplay) - val (raw, file) = - if (arg.isEmpty) (false, None) + val (raw, file, margin) = + if (arg.isEmpty) (false, None, None) else { - val r = """(-raw)?(\s+)?([^\-]\S*)?""".r - arg match { - case r(flag, sep, name) => - if (flag != null && name != null && sep == null) - echo(s"""I assume you mean "$flag $name"?""") - (flag != null, Option(name)) - case _ => - echo("usage: :paste -raw file") - return result + def maybeRaw(ss: List[String]) = if (ss.nonEmpty && ss.head == "-raw") (true, ss.tail) else (false, ss) + def maybeHere(ss: List[String]) = + if (ss.nonEmpty && ss.head.startsWith("<")) (ss.head.dropWhile(_ == '<'), ss.tail) + else (null, ss) + + val (raw0, ss0) = maybeRaw(words(arg)) + val (margin0, ss1) = maybeHere(ss0) + val file0 = ss1 match { + case Nil => null + case x :: Nil => x + case _ => echo("usage: :paste [-raw] file | < EOF") ; return result } + (raw0, Option(file0), Option(margin0)) } - val code = file match { - case Some(name) => + val code = (file, margin) match { + case (Some(name), None) => withFile(name) { f => shouldReplay = Some(s":paste $arg") val s = f.slurp.trim @@ -719,9 +731,16 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) else echo(s"Pasting file $f...") s } getOrElse "" - case None => - echo("// Entering paste mode (ctrl-D to finish)\n") - val text = (readWhile(_ => true) mkString "\n").trim + case (eof, _) => + echo(s"// Entering paste mode (${ eof getOrElse "ctrl-D" } to finish)\n") + val delimiter = eof orElse replProps.pasteDelimiter.option + val input = readWhile(s => delimiter.isEmpty || delimiter.get != s) mkString "\n" + val text = ( + margin filter (_.nonEmpty) map { + case "-" => input.lines map (_.trim) mkString "\n" + case m => input stripMargin m.head // ignore excess chars in "<<||" + } getOrElse input + ).trim if (text.isEmpty) echo("\n// Nothing pasted, nothing gained.\n") else echo("\n// Exiting paste mode, now interpreting.\n") text diff --git a/src/repl/scala/tools/nsc/interpreter/ReplGlobal.scala b/src/repl/scala/tools/nsc/interpreter/ReplGlobal.scala index 07d619bca5..cf055e0758 100644 --- a/src/repl/scala/tools/nsc/interpreter/ReplGlobal.scala +++ b/src/repl/scala/tools/nsc/interpreter/ReplGlobal.scala @@ -30,37 +30,5 @@ trait ReplGlobal extends Global { val virtualDirectory = globalSettings.outputDirs.getSingleOutput.get new util.AbstractFileClassLoader(virtualDirectory, loader) {} } - - override def newTyper(context: Context): Typer = new Typer(context) { - override def typed(tree: Tree, mode: Mode, pt: Type): Tree = { - val res = super.typed(tree, mode, pt) - tree match { - case Ident(name) if !tree.symbol.hasPackageFlag && !name.toString.startsWith("$") => - repldbg("typed %s: %s".format(name, res.tpe)) - case _ => - } - res - } - } - } - - object replPhase extends SubComponent { - val global: ReplGlobal.this.type = ReplGlobal.this - val phaseName = "repl" - val runsAfter = List[String]("typer") - val runsRightAfter = None - def newPhase(_prev: Phase): StdPhase = new StdPhase(_prev) { - def apply(unit: CompilationUnit) { - repldbg("Running replPhase on " + unit.body) - // newNamer(rootContext(unit)).enterSym(unit.body) - } - } - // add to initial or terminal phase to sanity check Run at construction - override val requires = List("typer") // ensure they didn't -Ystop-after:parser - } - - override protected def computePhaseDescriptors: List[SubComponent] = { - addToPhasesSet(replPhase, "repl") - super.computePhaseDescriptors } } diff --git a/src/repl/scala/tools/nsc/interpreter/ReplProps.scala b/src/repl/scala/tools/nsc/interpreter/ReplProps.scala index 588d92f81b..f3115d9800 100644 --- a/src/repl/scala/tools/nsc/interpreter/ReplProps.scala +++ b/src/repl/scala/tools/nsc/interpreter/ReplProps.scala @@ -64,6 +64,8 @@ class ReplProps { if (p.isSet) p.get else shellWelcomeString } + val pasteDelimiter = Prop[String]("scala.repl.here") + /** CSV of paged,across to enable pagination or `-x` style * columns, "across" instead of down the column. Since * pagination turns off columnar output, these flags are |