diff options
Diffstat (limited to 'src/compiler')
12 files changed, 850 insertions, 575 deletions
diff --git a/src/compiler/scala/reflect/reify/Taggers.scala b/src/compiler/scala/reflect/reify/Taggers.scala index e4c3d02f22..e09f13a052 100644 --- a/src/compiler/scala/reflect/reify/Taggers.scala +++ b/src/compiler/scala/reflect/reify/Taggers.scala @@ -21,6 +21,8 @@ abstract class Taggers { BooleanTpe -> nme.Boolean, UnitTpe -> nme.Unit, AnyTpe -> nme.Any, + AnyValTpe -> nme.AnyVal, + AnyRefTpe -> nme.AnyRef, ObjectTpe -> nme.Object, NothingTpe -> nme.Nothing, NullTpe -> nme.Null) diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala index f7541a4739..b638745327 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala @@ -860,7 +860,7 @@ abstract class GenICode extends SubComponent { if (sym.isLabel) { // jump to a label val label = ctx.labels.getOrElse(sym, { // it is a forward jump, scan for labels - scanForLabels(ctx.defdef, ctx) + resolveForwardLabel(ctx.defdef, ctx, sym) ctx.labels.get(sym) match { case Some(l) => log("Forward jump for " + sym.fullLocationString + ": scan found label " + l) @@ -1406,21 +1406,17 @@ abstract class GenICode extends SubComponent { def ifOneIsNull(l: Tree, r: Tree) = if (isNull(l)) r else if (isNull(r)) l else null /** - * Traverse the tree and store label stubs in the context. This is - * necessary to handle forward jumps, because at a label application - * with arguments, the symbols of the corresponding LabelDef parameters - * are not yet known. + * Find the label denoted by `lsym` and enter it in context `ctx`. * - * Since it is expensive to traverse each method twice, this method is called - * only when forward jumps really happen, and then it re-traverses the whole - * method, scanning for LabelDefs. + * We only enter one symbol at a time, even though we might traverse the same + * tree more than once per method. That's because we cannot enter labels that + * might be duplicated (for instance, inside finally blocks). * * TODO: restrict the scanning to smaller subtrees than the whole method. * It is sufficient to scan the trees of the innermost enclosing block. */ - // - private def scanForLabels(tree: Tree, ctx: Context): Unit = tree foreachPartial { - case t @ LabelDef(_, params, rhs) => + private def resolveForwardLabel(tree: Tree, ctx: Context, lsym: Symbol): Unit = tree foreachPartial { + case t @ LabelDef(_, params, rhs) if t.symbol == lsym => ctx.labels.getOrElseUpdate(t.symbol, { val locals = params map (p => new Local(p.symbol, toTypeKind(p.symbol.info), false)) ctx.method addLocals locals diff --git a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala b/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala index 5f495c8456..13457bfe58 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala @@ -15,37 +15,48 @@ import scala.reflect.internal.util.{Position,NoPosition} /* A pattern match - case THIS(clasz) => - case STORE_THIS(kind) => - case CONSTANT(const) => - case LOAD_ARRAY_ITEM(kind) => - case LOAD_LOCAL(local) => - case LOAD_FIELD(field, isStatic) => - case LOAD_MODULE(module) => - case STORE_ARRAY_ITEM(kind) => - case STORE_LOCAL(local) => - case STORE_FIELD(field, isStatic) => - case CALL_PRIMITIVE(primitive) => - case CALL_METHOD(method, style) => - case NEW(kind) => - case CREATE_ARRAY(elem, dims) => - case IS_INSTANCE(tpe) => - case CHECK_CAST(tpe) => - case SWITCH(tags, labels) => - case JUMP(whereto) => - case CJUMP(success, failure, cond, kind) => - case CZJUMP(success, failure, cond, kind) => - case RETURN(kind) => - case THROW(clasz) => - case DROP(kind) => - case DUP(kind) => - case MONITOR_ENTER() => - case MONITOR_EXIT() => - case BOX(boxType) => - case UNBOX(tpe) => - case SCOPE_ENTER(lv) => - case SCOPE_EXIT(lv) => - case LOAD_EXCEPTION(clasz) => + // locals + case THIS(clasz) => + case STORE_THIS(kind) => + case LOAD_LOCAL(local) => + case STORE_LOCAL(local) => + case SCOPE_ENTER(lv) => + case SCOPE_EXIT(lv) => + // stack + case LOAD_MODULE(module) => + case LOAD_EXCEPTION(clasz) => + case DROP(kind) => + case DUP(kind) => + // constants + case CONSTANT(const) => + // arithlogic + case CALL_PRIMITIVE(primitive) => + // casts + case IS_INSTANCE(tpe) => + case CHECK_CAST(tpe) => + // objs + case NEW(kind) => + case MONITOR_ENTER() => + case MONITOR_EXIT() => + case BOX(boxType) => + case UNBOX(tpe) => + // flds + case LOAD_FIELD(field, isStatic) => + case STORE_FIELD(field, isStatic) => + // mthds + case CALL_METHOD(method, style) => + // arrays + case LOAD_ARRAY_ITEM(kind) => + case STORE_ARRAY_ITEM(kind) => + case CREATE_ARRAY(elem, dims) => + // jumps + case SWITCH(tags, labels) => + case JUMP(whereto) => + case CJUMP(success, failure, cond, kind) => + case CZJUMP(success, failure, cond, kind) => + // ret + case RETURN(kind) => + case THROW(clasz) => */ @@ -58,11 +69,26 @@ import scala.reflect.internal.util.{Position,NoPosition} trait Opcodes { self: ICodes => import global.{Symbol, NoSymbol, Type, Name, Constant}; + // categories of ICode instructions + final val localsCat = 1 + final val stackCat = 2 + final val constCat = 3 + final val arilogCat = 4 + final val castsCat = 5 + final val objsCat = 6 + final val fldsCat = 7 + final val mthdsCat = 8 + final val arraysCat = 9 + final val jumpsCat = 10 + final val retCat = 11 + /** This class represents an instruction of the intermediate code. * Each case subclass will represent a specific operation. */ abstract class Instruction extends Cloneable { + def category: Int = 0 // undefined + /** This abstract method returns the number of used elements on the stack */ def consumed : Int = 0 @@ -118,6 +144,8 @@ trait Opcodes { self: ICodes => override def produced = 1 override def producedTypes = List(REFERENCE(clasz)) + + override def category = localsCat } /** Loads a constant on the stack. @@ -130,6 +158,8 @@ trait Opcodes { self: ICodes => override def produced = 1 override def producedTypes = List(toTypeKind(constant.tpe)) + + override def category = constCat } /** Loads an element of an array. The array and the index should @@ -143,6 +173,8 @@ trait Opcodes { self: ICodes => override def consumedTypes = List(ARRAY(kind), INT) override def producedTypes = List(kind) + + override def category = arraysCat } /** Load a local variable on the stack. It can be a method argument. @@ -154,6 +186,8 @@ trait Opcodes { self: ICodes => override def produced = 1 override def producedTypes = List(local.kind) + + override def category = localsCat } /** Load a field on the stack. The object to which it refers should be @@ -176,6 +210,8 @@ trait Opcodes { self: ICodes => // see #4283 var hostClass: Symbol = field.owner def setHostClass(cls: Symbol): this.type = { hostClass = cls; this } + + override def category = fldsCat } case class LOAD_MODULE(module: Symbol) extends Instruction { @@ -187,6 +223,8 @@ trait Opcodes { self: ICodes => override def produced = 1 override def producedTypes = List(REFERENCE(module)) + + override def category = stackCat } /** Store a value into an array at a specified index. @@ -198,6 +236,8 @@ trait Opcodes { self: ICodes => override def produced = 0 override def consumedTypes = List(ARRAY(kind), INT, kind) + + override def category = arraysCat } /** Store a value into a local variable. It can be an argument. @@ -209,6 +249,8 @@ trait Opcodes { self: ICodes => override def produced = 0 override def consumedTypes = List(local.kind) + + override def category = localsCat } /** Store a value into a field. @@ -228,6 +270,8 @@ trait Opcodes { self: ICodes => List(toTypeKind(field.tpe)) else List(REFERENCE(field.owner), toTypeKind(field.tpe)); + + override def category = fldsCat } /** Store a value into the 'this' pointer. @@ -238,6 +282,7 @@ trait Opcodes { self: ICodes => override def consumed = 1 override def produced = 0 override def consumedTypes = List(kind) + override def category = localsCat } /** Call a primitive function. @@ -292,6 +337,8 @@ trait Opcodes { self: ICodes => case StartConcat => List(ConcatClass) case EndConcat => List(REFERENCE(global.definitions.StringClass)) } + + override def category = arilogCat } /** This class represents a CALL_METHOD instruction @@ -347,6 +394,8 @@ trait Opcodes { self: ICodes => * being able to store such instructions into maps, when more * than one CALL_METHOD to the same method might exist. */ + + override def category = mthdsCat } case class BOX(boxType: TypeKind) extends Instruction { @@ -355,6 +404,7 @@ trait Opcodes { self: ICodes => override def consumed = 1 override def consumedTypes = boxType :: Nil override def produced = 1 + override def category = objsCat } case class UNBOX(boxType: TypeKind) extends Instruction { @@ -363,6 +413,7 @@ trait Opcodes { self: ICodes => override def consumed = 1 override def consumedTypes = ObjectReference :: Nil override def produced = 1 + override def category = objsCat } /** Create a new instance of a class through the specified constructor @@ -378,6 +429,8 @@ trait Opcodes { self: ICodes => /** The corresponding constructor call. */ var init: CALL_METHOD = _ + + override def category = objsCat } @@ -392,6 +445,8 @@ trait Opcodes { self: ICodes => override def consumed = dims; override def consumedTypes = List.fill(dims)(INT) override def produced = 1; + + override def category = arraysCat } /** This class represents a IS_INSTANCE instruction @@ -405,6 +460,8 @@ trait Opcodes { self: ICodes => override def consumed = 1 override def consumedTypes = ObjectReference :: Nil override def produced = 1 + + override def category = castsCat } /** This class represents a CHECK_CAST instruction @@ -419,6 +476,8 @@ trait Opcodes { self: ICodes => override def produced = 1 override val consumedTypes = List(ObjectReference) override def producedTypes = List(typ) + + override def category = castsCat } /** This class represents a SWITCH instruction @@ -439,6 +498,8 @@ trait Opcodes { self: ICodes => override val consumedTypes = List(INT) def flatTagsCount: Int = { var acc = 0; var rest = tags; while(rest.nonEmpty) { acc += rest.head.length; rest = rest.tail }; acc } // a one-liner + + override def category = jumpsCat } /** This class represents a JUMP instruction @@ -451,6 +512,8 @@ trait Opcodes { self: ICodes => override def consumed = 0 override def produced = 0 + + override def category = jumpsCat } /** This class represents a CJUMP instruction @@ -474,6 +537,8 @@ trait Opcodes { self: ICodes => override def produced = 0 override val consumedTypes = List(kind, kind) + + override def category = jumpsCat } /** This class represents a CZJUMP instruction @@ -495,6 +560,8 @@ trait Opcodes { self: ICodes => override def produced = 0 override val consumedTypes = List(kind) + + override def category = jumpsCat } @@ -507,6 +574,8 @@ trait Opcodes { self: ICodes => override def produced = 0 // TODO override val consumedTypes = List(kind) + + override def category = retCat } /** This class represents a THROW instruction @@ -522,6 +591,8 @@ trait Opcodes { self: ICodes => override def consumed = 1 override def produced = 0 + + override def category = retCat } /** This class represents a DROP instruction @@ -534,6 +605,8 @@ trait Opcodes { self: ICodes => override def consumed = 1 override def produced = 0 + + override def category = stackCat } /** This class represents a DUP instruction @@ -543,6 +616,7 @@ trait Opcodes { self: ICodes => case class DUP (typ: TypeKind) extends Instruction { override def consumed = 1 override def produced = 2 + override def category = stackCat } /** This class represents a MONITOR_ENTER instruction @@ -555,6 +629,8 @@ trait Opcodes { self: ICodes => override def consumed = 1 override def produced = 0 + + override def category = objsCat } /** This class represents a MONITOR_EXIT instruction @@ -567,6 +643,8 @@ trait Opcodes { self: ICodes => override def consumed = 1; override def produced = 0; + + override def category = objsCat } /** A local variable becomes visible at this point in code. @@ -577,6 +655,7 @@ trait Opcodes { self: ICodes => override def toString(): String = "SCOPE_ENTER " + lv override def consumed = 0 override def produced = 0 + override def category = localsCat } /** A local variable leaves its scope at this point in code. @@ -587,6 +666,7 @@ trait Opcodes { self: ICodes => override def toString(): String = "SCOPE_EXIT " + lv override def consumed = 0 override def produced = 0 + override def category = localsCat } /** Fake instruction. It designates the VM who pushes an exception @@ -598,6 +678,7 @@ trait Opcodes { self: ICodes => override def consumed = sys.error("LOAD_EXCEPTION does clean the whole stack, no idea how many things it consumes!") override def produced = 1 override def producedTypes = REFERENCE(clasz) :: Nil + override def category = stackCat } /** This class represents a method invocation style. */ @@ -658,6 +739,8 @@ trait Opcodes { self: ICodes => override def produced = 1 override def producedTypes = List(msil_mgdptr(local.kind)) + + override def category = localsCat } case class CIL_LOAD_FIELD_ADDRESS(field: Symbol, isStatic: Boolean) extends Instruction { @@ -670,6 +753,8 @@ trait Opcodes { self: ICodes => override def consumedTypes = if (isStatic) Nil else List(REFERENCE(field.owner)); override def producedTypes = List(msil_mgdptr(REFERENCE(field.owner))); + + override def category = fldsCat } case class CIL_LOAD_ARRAY_ITEM_ADDRESS(kind: TypeKind) extends Instruction { @@ -681,6 +766,8 @@ trait Opcodes { self: ICodes => override def consumedTypes = List(ARRAY(kind), INT) override def producedTypes = List(msil_mgdptr(kind)) + + override def category = arraysCat } case class CIL_UNBOX(valueType: TypeKind) extends Instruction { @@ -689,6 +776,7 @@ trait Opcodes { self: ICodes => override def consumedTypes = ObjectReference :: Nil // actually consumes a 'boxed valueType' override def produced = 1 override def producedTypes = List(msil_mgdptr(valueType)) + override def category = objsCat } case class CIL_INITOBJ(valueType: TypeKind) extends Instruction { @@ -696,6 +784,7 @@ trait Opcodes { self: ICodes => override def consumed = 1 override def consumedTypes = ObjectReference :: Nil // actually consumes a managed pointer override def produced = 0 + override def category = objsCat } case class CIL_NEWOBJ(method: Symbol) extends Instruction { @@ -705,6 +794,7 @@ trait Opcodes { self: ICodes => override def consumedTypes = method.tpe.paramTypes map toTypeKind override def produced = 1 override def producedTypes = List(toTypeKind(method.tpe.resultType)) + override def category = objsCat } } diff --git a/src/compiler/scala/tools/nsc/backend/icode/Repository.scala b/src/compiler/scala/tools/nsc/backend/icode/Repository.scala index 290979d205..663b626bef 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Repository.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Repository.scala @@ -38,19 +38,21 @@ trait Repository { } /** Load bytecode for given symbol. */ - def load(sym: Symbol) { + def load(sym: Symbol): Boolean = { try { val (c1, c2) = icodeReader.readClass(sym) - assert(c1.symbol == sym || c2.symbol == sym, - "c1.symbol = %s, c2.symbol = %s, sym = %s".format(c1.symbol, c2.symbol, sym)) + assert(c1.symbol == sym || c2.symbol == sym, "c1.symbol = %s, c2.symbol = %s, sym = %s".format(c1.symbol, c2.symbol, sym)) loaded += (c1.symbol -> c1) loaded += (c2.symbol -> c2) + + true } catch { case e: Throwable => // possible exceptions are MissingRequirementError, IOException and TypeError -> no better common supertype log("Failed to load %s. [%s]".format(sym.fullName, e.getMessage)) - if (settings.debug.value) - e.printStackTrace + if (settings.debug.value) { e.printStackTrace } + + false } } } diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala index d31eafff48..c3fbf31cc6 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala @@ -174,11 +174,8 @@ abstract class TypeFlowAnalysis { } i match { - case THIS(clasz) => - stack push toTypeKind(clasz.tpe) - - case CONSTANT(const) => - stack push toTypeKind(const.tpe) + case THIS(clasz) => stack push toTypeKind(clasz.tpe) + case CONSTANT(const) => stack push toTypeKind(const.tpe) case LOAD_ARRAY_ITEM(kind) => stack.pop2 match { @@ -194,139 +191,73 @@ abstract class TypeFlowAnalysis { stack push (if (t == typeLattice.bottom) local.kind else t) case LOAD_FIELD(field, isStatic) => - if (!isStatic) - stack.pop + if (!isStatic) { stack.pop } stack push toTypeKind(field.tpe) - case LOAD_MODULE(module) => - stack push toTypeKind(module.tpe) - - case STORE_ARRAY_ITEM(kind) => - stack.pop3 - - case STORE_LOCAL(local) => - val t = stack.pop - bindings += (local -> t) - - case STORE_THIS(_) => - stack.pop + case LOAD_MODULE(module) => stack push toTypeKind(module.tpe) + case STORE_ARRAY_ITEM(kind) => stack.pop3 + case STORE_LOCAL(local) => val t = stack.pop; bindings += (local -> t) + case STORE_THIS(_) => stack.pop - case STORE_FIELD(field, isStatic) => - if (isStatic) - stack.pop - else - stack.pop2 + case STORE_FIELD(field, isStatic) => if (isStatic) stack.pop else stack.pop2 case CALL_PRIMITIVE(primitive) => primitive match { - case Negation(kind) => - stack.pop; stack.push(kind) + case Negation(kind) => stack.pop; stack.push(kind) + case Test(_, kind, zero) => stack.pop - if (!zero) stack.pop + if (!zero) { stack.pop } stack push BOOL; - case Comparison(_, _) => - stack.pop2 - stack push INT + + case Comparison(_, _) => stack.pop2; stack push INT case Arithmetic(op, kind) => stack.pop - if (op != NOT) - stack.pop + if (op != NOT) { stack.pop } val k = kind match { case BYTE | SHORT | CHAR => INT case _ => kind } stack push k - case Logical(op, kind) => - stack.pop2 - stack push kind - - case Shift(op, kind) => - stack.pop2 - stack push kind - - case Conversion(src, dst) => - stack.pop - stack push dst - - case ArrayLength(kind) => - stack.pop - stack push INT - - case StartConcat => - stack.push(ConcatClass) - - case EndConcat => - stack.pop - stack.push(STRING) - - case StringConcat(el) => - stack.pop2 - stack push ConcatClass + case Logical(op, kind) => stack.pop2; stack push kind + case Shift(op, kind) => stack.pop2; stack push kind + case Conversion(src, dst) => stack.pop; stack push dst + case ArrayLength(kind) => stack.pop; stack push INT + case StartConcat => stack.push(ConcatClass) + case EndConcat => stack.pop; stack.push(STRING) + case StringConcat(el) => stack.pop2; stack push ConcatClass } case cm @ CALL_METHOD(_, _) => stack pop cm.consumed cm.producedTypes foreach (stack push _) - case BOX(kind) => - stack.pop - stack.push(BOXED(kind)) - - case UNBOX(kind) => - stack.pop - stack.push(kind) - - case NEW(kind) => - stack.push(kind) - - case CREATE_ARRAY(elem, dims) => - stack.pop(dims) - stack.push(ARRAY(elem)) - - case IS_INSTANCE(tpe) => - stack.pop - stack.push(BOOL) - - case CHECK_CAST(tpe) => - stack.pop - stack.push(tpe) + case BOX(kind) => stack.pop; stack.push(BOXED(kind)) + case UNBOX(kind) => stack.pop; stack.push(kind) - case SWITCH(tags, labels) => - stack.pop + case NEW(kind) => stack.push(kind) - case JUMP(whereto) => - () + case CREATE_ARRAY(elem, dims) => stack.pop(dims); stack.push(ARRAY(elem)) - case CJUMP(success, failure, cond, kind) => - stack.pop2 + case IS_INSTANCE(tpe) => stack.pop; stack.push(BOOL) + case CHECK_CAST(tpe) => stack.pop; stack.push(tpe) - case CZJUMP(success, failure, cond, kind) => - stack.pop + case _: SWITCH => stack.pop + case _: JUMP => () + case _: CJUMP => stack.pop2 + case _: CZJUMP => stack.pop - case RETURN(kind) => - if (kind != UNIT) - stack.pop; + case RETURN(kind) => if (kind != UNIT) { stack.pop } + case THROW(_) => stack.pop - case THROW(_) => - stack.pop + case DROP(kind) => stack.pop + case DUP(kind) => stack.push(stack.head) - case DROP(kind) => - stack.pop + case MONITOR_ENTER() | MONITOR_EXIT() => stack.pop - case DUP(kind) => - stack.push(stack.head) - - case MONITOR_ENTER() => - stack.pop - - case MONITOR_EXIT() => - stack.pop - - case SCOPE_ENTER(_) | SCOPE_EXIT(_) => - () + case SCOPE_ENTER(_) | SCOPE_EXIT(_) => () case LOAD_EXCEPTION(clasz) => stack.pop(stack.length) @@ -551,14 +482,24 @@ abstract class TypeFlowAnalysis { val relevantBBs = mutable.Set.empty[BasicBlock] + /* + * Rationale to prevent some methods from ever being inlined: + * + * (1) inlining getters and setters results in exposing a private field, + * which may itself prevent inlining of the caller (at best) or + * lead to situations like SI-5442 ("IllegalAccessError when mixing optimized and unoptimized bytecode") + * + * (2) only invocations having a receiver object are considered (ie no static-methods are ever inlined). + * This is taken care of by checking `isDynamic` (ie virtual method dispatch) and `Static(true)` (ie calls to private members) + */ private def isPreCandidate(cm: opcodes.CALL_METHOD): Boolean = { val msym = cm.method val style = cm.style - // Dynamic == normal invocations - // Static(true) == calls to private members - !msym.isConstructor && !blackballed(msym) && - (style.isDynamic || (style.hasInstance && style.isStatic)) - // && !(msym hasAnnotation definitions.ScalaNoInlineClass) + + !blackballed(msym) && + !msym.isConstructor && + (!msym.isAccessor || inliner.isClosureClass(msym.owner)) && + (style.isDynamic || (style.hasInstance && style.isStatic)) } override def init(m: icodes.IMethod) { diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala index ff68aba845..42921e733d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala @@ -37,7 +37,7 @@ trait BytecodeWriters { getFile(outputDirectory(sym), clsName, suffix) trait BytecodeWriter { - def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], sym: Symbol): Unit + def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], outfile: AbstractFile): Unit def close(): Unit = () } @@ -48,7 +48,9 @@ trait BytecodeWriters { ) val writer = new Jar(jfile).jarWriter(jarMainAttrs: _*) - def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], sym: Symbol) { + def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], outfile: AbstractFile) { + assert(outfile == null, + "The outfile formal param is there just because ClassBytecodeWriter overrides this method and uses it.") val path = jclassName + ".class" val out = writer.newOutputStream(path) @@ -72,21 +74,21 @@ trait BytecodeWriters { try javap(Seq("-verbose", "dummy")) foreach (_.show()) finally pw.close() } - abstract override def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], sym: Symbol) { - super.writeClass(label, jclassName, jclassBytes, sym) + abstract override def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], outfile: AbstractFile) { + super.writeClass(label, jclassName, jclassBytes, outfile) - val bytes = getFile(sym, jclassName, ".class").toByteArray val segments = jclassName.split("[./]") val javapFile = segments.foldLeft(baseDir: Path)(_ / _) changeExtension "javap" toFile; javapFile.parent.createDirectory() - emitJavap(bytes, javapFile) + emitJavap(jclassBytes, javapFile) } } trait ClassBytecodeWriter extends BytecodeWriter { - def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], sym: Symbol) { - val outfile = getFile(sym, jclassName, ".class") + def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], outfile: AbstractFile) { + assert(outfile != null, + "Precisely this override requires its invoker to hand out a non-null AbstractFile.") val outstream = new DataOutputStream(outfile.bufferedOutput) try outstream.write(jclassBytes, 0, jclassBytes.length) @@ -98,8 +100,8 @@ trait BytecodeWriters { trait DumpBytecodeWriter extends BytecodeWriter { val baseDir = Directory(settings.Ydumpclasses.value).createDirectory() - abstract override def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], sym: Symbol) { - super.writeClass(label, jclassName, jclassBytes, sym) + abstract override def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], outfile: AbstractFile) { + super.writeClass(label, jclassName, jclassBytes, outfile) val pathName = jclassName var dumpFile = pathName.split("[./]").foldLeft(baseDir: Path) (_ / _) changeExtension "class" toFile; diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 59adcc637a..5ab8a3d751 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -29,6 +29,10 @@ abstract class GenASM extends SubComponent with BytecodeWriters { val phaseName = "jvm"
+ case class WorkUnit(label: String, jclassName: String, jclass: asm.ClassWriter, outF: AbstractFile)
+
+ type WorkUnitQueue = _root_.java.util.concurrent.LinkedBlockingQueue[WorkUnit]
+
/** Create a new phase */
override def newPhase(p: Phase): Phase = new AsmPhase(p)
@@ -148,6 +152,44 @@ abstract class GenASM extends SubComponent with BytecodeWriters { }
}
+ // -----------------------------------------------------------------------------------------
+ // Allow overlapping disk write of classfiles with building of the next classfiles.
+ // -----------------------------------------------------------------------------------------
+
+ val q = new WorkUnitQueue(500)
+
+ class WriteTask(bytecodeWriter: BytecodeWriter) extends _root_.java.lang.Runnable {
+
+ def run() {
+ var stop = false
+ try {
+ while (!stop) {
+ val WorkUnit(label, jclassName, jclass, outF) = q.take
+ if(jclass eq null) { stop = true }
+ else { writeIfNotTooBig(label, jclassName, jclass, outF) }
+ }
+ } catch {
+ case ex: InterruptedException => throw ex
+ }
+ }
+
+ private def writeIfNotTooBig(label: String, jclassName: String, jclass: asm.ClassWriter, outF: AbstractFile) {
+ try {
+ val arr = jclass.toByteArray()
+ bytecodeWriter.writeClass(label, jclassName, arr, outF)
+ } catch {
+ case e: java.lang.RuntimeException if(e.getMessage() == "Class file too large!") =>
+ // TODO check where ASM throws the equivalent of CodeSizeTooBigException
+ log("Skipped class "+jclassName+" because it exceeds JVM limits (it's too big or has methods that are too long).")
+ }
+ }
+
+ }
+
+ // -----------------------------------------------------------------------------------------
+ // what AsmPhase does.
+ // -----------------------------------------------------------------------------------------
+
override def run() {
if (settings.debug.value)
@@ -161,10 +203,14 @@ abstract class GenASM extends SubComponent with BytecodeWriters { var sortedClasses = classes.values.toList sortBy ("" + _.symbol.fullName)
debuglog("Created new bytecode generator for " + classes.size + " classes.")
- val bytecodeWriter = initBytecodeWriter(sortedClasses filter isJavaEntryPoint)
- val plainCodeGen = new JPlainBuilder(bytecodeWriter)
- val mirrorCodeGen = new JMirrorBuilder(bytecodeWriter)
- val beanInfoCodeGen = new JBeanInfoBuilder(bytecodeWriter)
+ val bytecodeWriter = initBytecodeWriter(sortedClasses filter isJavaEntryPoint)
+ val needsOutfileForSymbol = bytecodeWriter.isInstanceOf[ClassBytecodeWriter]
+
+ val plainCodeGen = new JPlainBuilder(q, needsOutfileForSymbol)
+ val mirrorCodeGen = new JMirrorBuilder(q, needsOutfileForSymbol)
+ val beanInfoCodeGen = new JBeanInfoBuilder(q, needsOutfileForSymbol)
+
+ new _root_.java.lang.Thread(new WriteTask(bytecodeWriter)).start()
while(!sortedClasses.isEmpty) {
val c = sortedClasses.head
@@ -187,6 +233,10 @@ abstract class GenASM extends SubComponent with BytecodeWriters { classes -= c.symbol // GC opportunity
}
+ q put WorkUnit(null, null, null, null)
+
+ while(!q.isEmpty) { _root_.java.lang.Thread.sleep(10) }
+
bytecodeWriter.close()
classes.clear()
reverseJavaName.clear()
@@ -448,7 +498,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { val JAVA_LANG_STRING = asm.Type.getObjectType("java/lang/String")
/** basic functionality for class file building */
- abstract class JBuilder(bytecodeWriter: BytecodeWriter) {
+ abstract class JBuilder(wuQ: WorkUnitQueue, needsOutfileForSymbol: Boolean) {
val EMPTY_JTYPE_ARRAY = Array.empty[asm.Type]
val EMPTY_STRING_ARRAY = Array.empty[String]
@@ -504,17 +554,6 @@ abstract class GenASM extends SubComponent with BytecodeWriters { // utitilies useful when emitting plain, mirror, and beaninfo classes.
// -----------------------------------------------------------------------------------------
- def writeIfNotTooBig(label: String, jclassName: String, jclass: asm.ClassWriter, sym: Symbol) {
- try {
- val arr = jclass.toByteArray()
- bytecodeWriter.writeClass(label, jclassName, arr, sym)
- } catch {
- case e: java.lang.RuntimeException if(e.getMessage() == "Class file too large!") =>
- // TODO check where ASM throws the equivalent of CodeSizeTooBigException
- log("Skipped class "+jclassName+" because it exceeds JVM limits (it's too big or has methods that are too long).")
- }
- }
-
/** Specialized array conversion to prevent calling
* java.lang.reflect.Array.newInstance via TraversableOnce.toArray
*/
@@ -728,11 +767,18 @@ abstract class GenASM extends SubComponent with BytecodeWriters { }
}
+ def enqueue(label: String, jclassName: String, jclass: asm.ClassWriter, sym: Symbol) {
+ val outF: scala.tools.nsc.io.AbstractFile = {
+ if(needsOutfileForSymbol) getFile(sym, jclassName, ".class") else null
+ }
+ wuQ put WorkUnit(label, jclassName, jclass, outF)
+ }
+
} // end of class JBuilder
/** functionality for building plain and mirror classes */
- abstract class JCommonBuilder(bytecodeWriter: BytecodeWriter) extends JBuilder(bytecodeWriter) {
+ abstract class JCommonBuilder(wuQ: WorkUnitQueue, needsOutfileForSymbol: Boolean) extends JBuilder(wuQ, needsOutfileForSymbol) {
// -----------------------------------------------------------------------------------------
// more constants
@@ -1290,8 +1336,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters { case class BlockInteval(start: BasicBlock, end: BasicBlock)
/** builder of plain classes */
- class JPlainBuilder(bytecodeWriter: BytecodeWriter)
- extends JCommonBuilder(bytecodeWriter)
+ class JPlainBuilder(wuQ: WorkUnitQueue, needsOutfileForSymbol: Boolean)
+ extends JCommonBuilder(wuQ, needsOutfileForSymbol)
with JAndroidBuilder {
val MIN_SWITCH_DENSITY = 0.7
@@ -1445,7 +1491,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { addInnerClasses(clasz.symbol, jclass)
jclass.visitEnd()
- writeIfNotTooBig("" + c.symbol.name, thisName, jclass, c.symbol)
+ enqueue("" + c.symbol.name, thisName, jclass, c.symbol)
}
@@ -2366,251 +2412,269 @@ abstract class GenASM extends SubComponent with BytecodeWriters { }
}
- instr match {
- case THIS(_) => jmethod.visitVarInsn(Opcodes.ALOAD, 0)
-
- case CONSTANT(const) => genConstant(jmethod, const)
-
- case LOAD_ARRAY_ITEM(kind) => jcode.aload(kind)
-
- case LOAD_LOCAL(local) => jcode.load(indexOf(local), local.kind)
-
- case lf @ LOAD_FIELD(field, isStatic) =>
- var owner = javaName(lf.hostClass)
- debuglog("LOAD_FIELD with owner: " + owner + " flags: " + Flags.flagsToString(field.owner.flags))
- val fieldJName = javaName(field)
- val fieldDescr = descriptor(field)
- val opc = if (isStatic) Opcodes.GETSTATIC else Opcodes.GETFIELD
- jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr)
-
- case LOAD_MODULE(module) =>
- // assert(module.isModule, "Expected module: " + module)
- debuglog("generating LOAD_MODULE for: " + module + " flags: " + Flags.flagsToString(module.flags));
- if (clasz.symbol == module.moduleClass && jMethodName != nme.readResolve.toString) {
- jmethod.visitVarInsn(Opcodes.ALOAD, 0)
- } else {
- jmethod.visitFieldInsn(
- Opcodes.GETSTATIC,
- javaName(module) /* + "$" */ ,
- strMODULE_INSTANCE_FIELD,
- descriptor(module)
- )
- }
+ (instr.category: @scala.annotation.switch) match {
+
+ case icodes.localsCat => (instr: @unchecked) match {
+ case THIS(_) => jmethod.visitVarInsn(Opcodes.ALOAD, 0)
+ case LOAD_LOCAL(local) => jcode.load(indexOf(local), local.kind)
+ case STORE_LOCAL(local) => jcode.store(indexOf(local), local.kind)
+ case STORE_THIS(_) =>
+ // this only works for impl classes because the self parameter comes first
+ // in the method signature. If that changes, this code has to be revisited.
+ jmethod.visitVarInsn(Opcodes.ASTORE, 0)
+
+ case SCOPE_ENTER(lv) =>
+ // locals removed by closelim (via CopyPropagation) may have left behind SCOPE_ENTER, SCOPE_EXIT that are to be ignored
+ val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv))
+ if(relevant) { // TODO check: does GenICode emit SCOPE_ENTER, SCOPE_EXIT for synthetic vars?
+ // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes)
+ // similarly, these labels aren't tracked in the `labels` map.
+ val start = new asm.Label
+ jmethod.visitLabel(start)
+ scoping.pushScope(lv, start)
+ }
+
+ case SCOPE_EXIT(lv) =>
+ val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv))
+ if(relevant) {
+ // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes)
+ // similarly, these labels aren't tracked in the `labels` map.
+ val end = new asm.Label
+ jmethod.visitLabel(end)
+ scoping.popScope(lv, end)
+ }
+ }
- case STORE_ARRAY_ITEM(kind) => jcode.astore(kind)
+ case icodes.stackCat => (instr: @unchecked) match {
- case STORE_LOCAL(local) => jcode.store(indexOf(local), local.kind)
+ case LOAD_MODULE(module) =>
+ // assert(module.isModule, "Expected module: " + module)
+ debuglog("generating LOAD_MODULE for: " + module + " flags: " + Flags.flagsToString(module.flags));
+ if (clasz.symbol == module.moduleClass && jMethodName != nme.readResolve.toString) {
+ jmethod.visitVarInsn(Opcodes.ALOAD, 0)
+ } else {
+ jmethod.visitFieldInsn(
+ Opcodes.GETSTATIC,
+ javaName(module) /* + "$" */ ,
+ strMODULE_INSTANCE_FIELD,
+ descriptor(module)
+ )
+ }
- case STORE_THIS(_) =>
- // this only works for impl classes because the self parameter comes first
- // in the method signature. If that changes, this code has to be revisited.
- jmethod.visitVarInsn(Opcodes.ASTORE, 0)
+ case DROP(kind) => emit(if(kind.isWideType) Opcodes.POP2 else Opcodes.POP)
- case STORE_FIELD(field, isStatic) =>
- val owner = javaName(field.owner)
- val fieldJName = javaName(field)
- val fieldDescr = descriptor(field)
- val opc = if (isStatic) Opcodes.PUTSTATIC else Opcodes.PUTFIELD
- jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr)
+ case DUP(kind) => emit(if(kind.isWideType) Opcodes.DUP2 else Opcodes.DUP)
- case CALL_PRIMITIVE(primitive) => genPrimitive(primitive, instr.pos)
+ case LOAD_EXCEPTION(_) => ()
+ }
- /** Special handling to access native Array.clone() */
- case call @ CALL_METHOD(definitions.Array_clone, Dynamic) =>
- val target: String = javaType(call.targetTypeKind).getInternalName
- jcode.invokevirtual(target, "clone", mdesc_arrayClone)
+ case icodes.constCat => genConstant(jmethod, instr.asInstanceOf[CONSTANT].constant)
- case call @ CALL_METHOD(method, style) => genCallMethod(call)
+ case icodes.arilogCat => genPrimitive(instr.asInstanceOf[CALL_PRIMITIVE].primitive, instr.pos)
- case BOX(kind) =>
- val MethodNameAndType(mname, mdesc) = jBoxTo(kind)
- jcode.invokestatic(BoxesRunTime, mname, mdesc)
+ case icodes.castsCat => (instr: @unchecked) match {
- case UNBOX(kind) =>
- val MethodNameAndType(mname, mdesc) = jUnboxTo(kind)
- jcode.invokestatic(BoxesRunTime, mname, mdesc)
+ case IS_INSTANCE(tpe) =>
+ val jtyp: asm.Type =
+ tpe match {
+ case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls))
+ case ARRAY(elem) => javaArrayType(javaType(elem))
+ case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe)
+ }
+ jmethod.visitTypeInsn(Opcodes.INSTANCEOF, jtyp.getInternalName)
- case NEW(REFERENCE(cls)) =>
- val className = javaName(cls)
- jmethod.visitTypeInsn(Opcodes.NEW, className)
+ case CHECK_CAST(tpe) =>
+ tpe match {
- case CREATE_ARRAY(elem, 1) => jcode newarray elem
+ case REFERENCE(cls) =>
+ if (cls != ObjectClass) { // No need to checkcast for Objects
+ jmethod.visitTypeInsn(Opcodes.CHECKCAST, javaName(cls))
+ }
- case CREATE_ARRAY(elem, dims) =>
- jmethod.visitMultiANewArrayInsn(descriptor(ArrayN(elem, dims)), dims)
+ case ARRAY(elem) =>
+ val iname = javaArrayType(javaType(elem)).getInternalName
+ jmethod.visitTypeInsn(Opcodes.CHECKCAST, iname)
- case IS_INSTANCE(tpe) =>
- val jtyp: asm.Type =
- tpe match {
- case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls))
- case ARRAY(elem) => javaArrayType(javaType(elem))
case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe)
}
- jmethod.visitTypeInsn(Opcodes.INSTANCEOF, jtyp.getInternalName)
- case CHECK_CAST(tpe) =>
- tpe match {
+ }
- case REFERENCE(cls) =>
- if (cls != ObjectClass) { // No need to checkcast for Objects
- jmethod.visitTypeInsn(Opcodes.CHECKCAST, javaName(cls))
- }
+ case icodes.objsCat => (instr: @unchecked) match {
- case ARRAY(elem) =>
- val iname = javaArrayType(javaType(elem)).getInternalName
- jmethod.visitTypeInsn(Opcodes.CHECKCAST, iname)
+ case BOX(kind) =>
+ val MethodNameAndType(mname, mdesc) = jBoxTo(kind)
+ jcode.invokestatic(BoxesRunTime, mname, mdesc)
- case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe)
- }
+ case UNBOX(kind) =>
+ val MethodNameAndType(mname, mdesc) = jUnboxTo(kind)
+ jcode.invokestatic(BoxesRunTime, mname, mdesc)
- case sw @ SWITCH(tagss, branches) =>
- assert(branches.length == tagss.length + 1, sw)
- val flatSize = sw.flatTagsCount
- val flatKeys = new Array[Int](flatSize)
- val flatBranches = new Array[asm.Label](flatSize)
-
- var restTagss = tagss
- var restBranches = branches
- var k = 0 // ranges over flatKeys and flatBranches
- while(restTagss.nonEmpty) {
- val currLabel = labels(restBranches.head)
- for(cTag <- restTagss.head) {
- flatKeys(k) = cTag;
- flatBranches(k) = currLabel
- k += 1
- }
- restTagss = restTagss.tail
- restBranches = restBranches.tail
- }
- val defaultLabel = labels(restBranches.head)
- assert(restBranches.tail.isEmpty)
- debuglog("Emitting SWITCH:\ntags: " + tagss + "\nbranches: " + branches)
- jcode.emitSWITCH(flatKeys, flatBranches, defaultLabel, MIN_SWITCH_DENSITY)
-
- case JUMP(whereto) =>
- if (nextBlock != whereto) {
- jcode goTo labels(whereto)
- }
+ case NEW(REFERENCE(cls)) =>
+ val className = javaName(cls)
+ jmethod.visitTypeInsn(Opcodes.NEW, className)
- case CJUMP(success, failure, cond, kind) =>
- if(kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
- if (nextBlock == success) {
- jcode.emitIF_ICMP(cond.negate, labels(failure))
- // .. and fall through to success label
- } else {
- jcode.emitIF_ICMP(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- } else if(kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_)
- if (nextBlock == success) {
- jcode.emitIF_ACMP(cond.negate, labels(failure))
- // .. and fall through to success label
- } else {
- jcode.emitIF_ACMP(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- } else {
- (kind: @unchecked) match {
- case LONG => emit(Opcodes.LCMP)
- case FLOAT =>
- if (cond == LT || cond == LE) emit(Opcodes.FCMPG)
- else emit(Opcodes.FCMPL)
- case DOUBLE =>
- if (cond == LT || cond == LE) emit(Opcodes.DCMPG)
- else emit(Opcodes.DCMPL)
- }
- if (nextBlock == success) {
- jcode.emitIF(cond.negate, labels(failure))
- // .. and fall through to success label
- } else {
- jcode.emitIF(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- }
+ case MONITOR_ENTER() => emit(Opcodes.MONITORENTER)
+ case MONITOR_EXIT() => emit(Opcodes.MONITOREXIT)
+ }
- case CZJUMP(success, failure, cond, kind) =>
- if(kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
- if (nextBlock == success) {
- jcode.emitIF(cond.negate, labels(failure))
- } else {
- jcode.emitIF(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- } else if(kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_)
- val Success = success
- val Failure = failure
- // @unchecked because references aren't compared with GT, GE, LT, LE.
- ((cond, nextBlock) : @unchecked) match {
- case (EQ, Success) => jcode emitIFNONNULL labels(failure)
- case (NE, Failure) => jcode emitIFNONNULL labels(success)
- case (EQ, Failure) => jcode emitIFNULL labels(success)
- case (NE, Success) => jcode emitIFNULL labels(failure)
- case (EQ, _) =>
- jcode emitIFNULL labels(success)
- jcode goTo labels(failure)
- case (NE, _) =>
- jcode emitIFNONNULL labels(success)
- jcode goTo labels(failure)
- }
- } else {
- (kind: @unchecked) match {
- case LONG =>
- emit(Opcodes.LCONST_0)
- emit(Opcodes.LCMP)
- case FLOAT =>
- emit(Opcodes.FCONST_0)
- if (cond == LT || cond == LE) emit(Opcodes.FCMPG)
- else emit(Opcodes.FCMPL)
- case DOUBLE =>
- emit(Opcodes.DCONST_0)
- if (cond == LT || cond == LE) emit(Opcodes.DCMPG)
- else emit(Opcodes.DCMPL)
- }
- if (nextBlock == success) {
- jcode.emitIF(cond.negate, labels(failure))
- } else {
- jcode.emitIF(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- }
+ case icodes.fldsCat => (instr: @unchecked) match {
- case RETURN(kind) => jcode emitRETURN kind
+ case lf @ LOAD_FIELD(field, isStatic) =>
+ var owner = javaName(lf.hostClass)
+ debuglog("LOAD_FIELD with owner: " + owner + " flags: " + Flags.flagsToString(field.owner.flags))
+ val fieldJName = javaName(field)
+ val fieldDescr = descriptor(field)
+ val opc = if (isStatic) Opcodes.GETSTATIC else Opcodes.GETFIELD
+ jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr)
- case THROW(_) => emit(Opcodes.ATHROW)
+ case STORE_FIELD(field, isStatic) =>
+ val owner = javaName(field.owner)
+ val fieldJName = javaName(field)
+ val fieldDescr = descriptor(field)
+ val opc = if (isStatic) Opcodes.PUTSTATIC else Opcodes.PUTFIELD
+ jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr)
- case DROP(kind) =>
- emit(if(kind.isWideType) Opcodes.POP2 else Opcodes.POP)
+ }
- case DUP(kind) =>
- emit(if(kind.isWideType) Opcodes.DUP2 else Opcodes.DUP)
+ case icodes.mthdsCat => (instr: @unchecked) match {
- case MONITOR_ENTER() => emit(Opcodes.MONITORENTER)
+ /** Special handling to access native Array.clone() */
+ case call @ CALL_METHOD(definitions.Array_clone, Dynamic) =>
+ val target: String = javaType(call.targetTypeKind).getInternalName
+ jcode.invokevirtual(target, "clone", mdesc_arrayClone)
- case MONITOR_EXIT() => emit(Opcodes.MONITOREXIT)
+ case call @ CALL_METHOD(method, style) => genCallMethod(call)
- case SCOPE_ENTER(lv) =>
- // locals removed by closelim (via CopyPropagation) may have left behind SCOPE_ENTER, SCOPE_EXIT that are to be ignored
- val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv))
- if(relevant) { // TODO check: does GenICode emit SCOPE_ENTER, SCOPE_EXIT for synthetic vars?
- // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes)
- // similarly, these labels aren't tracked in the `labels` map.
- val start = new asm.Label
- jmethod.visitLabel(start)
- scoping.pushScope(lv, start)
- }
+ }
- case SCOPE_EXIT(lv) =>
- val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv))
- if(relevant) {
- // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes)
- // similarly, these labels aren't tracked in the `labels` map.
- val end = new asm.Label
- jmethod.visitLabel(end)
- scoping.popScope(lv, end)
- }
+ case icodes.arraysCat => (instr: @unchecked) match {
+ case LOAD_ARRAY_ITEM(kind) => jcode.aload(kind)
+ case STORE_ARRAY_ITEM(kind) => jcode.astore(kind)
+ case CREATE_ARRAY(elem, 1) => jcode newarray elem
+ case CREATE_ARRAY(elem, dims) => jmethod.visitMultiANewArrayInsn(descriptor(ArrayN(elem, dims)), dims)
+ }
+
+ case icodes.jumpsCat => (instr: @unchecked) match {
+
+ case sw @ SWITCH(tagss, branches) =>
+ assert(branches.length == tagss.length + 1, sw)
+ val flatSize = sw.flatTagsCount
+ val flatKeys = new Array[Int](flatSize)
+ val flatBranches = new Array[asm.Label](flatSize)
+
+ var restTagss = tagss
+ var restBranches = branches
+ var k = 0 // ranges over flatKeys and flatBranches
+ while(restTagss.nonEmpty) {
+ val currLabel = labels(restBranches.head)
+ for(cTag <- restTagss.head) {
+ flatKeys(k) = cTag;
+ flatBranches(k) = currLabel
+ k += 1
+ }
+ restTagss = restTagss.tail
+ restBranches = restBranches.tail
+ }
+ val defaultLabel = labels(restBranches.head)
+ assert(restBranches.tail.isEmpty)
+ debuglog("Emitting SWITCH:\ntags: " + tagss + "\nbranches: " + branches)
+ jcode.emitSWITCH(flatKeys, flatBranches, defaultLabel, MIN_SWITCH_DENSITY)
+
+ case JUMP(whereto) =>
+ if (nextBlock != whereto) {
+ jcode goTo labels(whereto)
+ }
+
+ case CJUMP(success, failure, cond, kind) =>
+ if(kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
+ if (nextBlock == success) {
+ jcode.emitIF_ICMP(cond.negate, labels(failure))
+ // .. and fall through to success label
+ } else {
+ jcode.emitIF_ICMP(cond, labels(success))
+ if (nextBlock != failure) { jcode goTo labels(failure) }
+ }
+ } else if(kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_)
+ if (nextBlock == success) {
+ jcode.emitIF_ACMP(cond.negate, labels(failure))
+ // .. and fall through to success label
+ } else {
+ jcode.emitIF_ACMP(cond, labels(success))
+ if (nextBlock != failure) { jcode goTo labels(failure) }
+ }
+ } else {
+ (kind: @unchecked) match {
+ case LONG => emit(Opcodes.LCMP)
+ case FLOAT =>
+ if (cond == LT || cond == LE) emit(Opcodes.FCMPG)
+ else emit(Opcodes.FCMPL)
+ case DOUBLE =>
+ if (cond == LT || cond == LE) emit(Opcodes.DCMPG)
+ else emit(Opcodes.DCMPL)
+ }
+ if (nextBlock == success) {
+ jcode.emitIF(cond.negate, labels(failure))
+ // .. and fall through to success label
+ } else {
+ jcode.emitIF(cond, labels(success))
+ if (nextBlock != failure) { jcode goTo labels(failure) }
+ }
+ }
+
+ case CZJUMP(success, failure, cond, kind) =>
+ if(kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
+ if (nextBlock == success) {
+ jcode.emitIF(cond.negate, labels(failure))
+ } else {
+ jcode.emitIF(cond, labels(success))
+ if (nextBlock != failure) { jcode goTo labels(failure) }
+ }
+ } else if(kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_)
+ val Success = success
+ val Failure = failure
+ // @unchecked because references aren't compared with GT, GE, LT, LE.
+ ((cond, nextBlock) : @unchecked) match {
+ case (EQ, Success) => jcode emitIFNONNULL labels(failure)
+ case (NE, Failure) => jcode emitIFNONNULL labels(success)
+ case (EQ, Failure) => jcode emitIFNULL labels(success)
+ case (NE, Success) => jcode emitIFNULL labels(failure)
+ case (EQ, _) =>
+ jcode emitIFNULL labels(success)
+ jcode goTo labels(failure)
+ case (NE, _) =>
+ jcode emitIFNONNULL labels(success)
+ jcode goTo labels(failure)
+ }
+ } else {
+ (kind: @unchecked) match {
+ case LONG =>
+ emit(Opcodes.LCONST_0)
+ emit(Opcodes.LCMP)
+ case FLOAT =>
+ emit(Opcodes.FCONST_0)
+ if (cond == LT || cond == LE) emit(Opcodes.FCMPG)
+ else emit(Opcodes.FCMPL)
+ case DOUBLE =>
+ emit(Opcodes.DCONST_0)
+ if (cond == LT || cond == LE) emit(Opcodes.DCMPG)
+ else emit(Opcodes.DCMPL)
+ }
+ if (nextBlock == success) {
+ jcode.emitIF(cond.negate, labels(failure))
+ } else {
+ jcode.emitIF(cond, labels(success))
+ if (nextBlock != failure) { jcode goTo labels(failure) }
+ }
+ }
+
+ }
+
+ case icodes.retCat => (instr: @unchecked) match {
+ case RETURN(kind) => jcode emitRETURN kind
+ case THROW(_) => emit(Opcodes.ATHROW)
+ }
- case LOAD_EXCEPTION(_) =>
- ()
}
}
@@ -2863,7 +2927,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { /** builder of mirror classes */
- class JMirrorBuilder(bytecodeWriter: BytecodeWriter) extends JCommonBuilder(bytecodeWriter) {
+ class JMirrorBuilder(wuQ: WorkUnitQueue, needsOutfileForSymbol: Boolean) extends JCommonBuilder(wuQ, needsOutfileForSymbol) {
private var cunit: CompilationUnit = _
def getCurrentCUnit(): CompilationUnit = cunit;
@@ -2907,7 +2971,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { addInnerClasses(modsym, mirrorClass)
mirrorClass.visitEnd()
- writeIfNotTooBig("" + modsym.name, mirrorName, mirrorClass, modsym)
+ enqueue("" + modsym.name, mirrorName, mirrorClass, modsym)
}
@@ -2915,7 +2979,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { /** builder of bean info classes */
- class JBeanInfoBuilder(bytecodeWriter: BytecodeWriter) extends JBuilder(bytecodeWriter) {
+ class JBeanInfoBuilder(wuQ: WorkUnitQueue, needsOutfileForSymbol: Boolean) extends JBuilder(wuQ, needsOutfileForSymbol) {
/**
* Generate a bean info class that describes the given class.
@@ -3036,7 +3100,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { addInnerClasses(clasz.symbol, beanInfoClass)
beanInfoClass.visitEnd()
- writeIfNotTooBig("BeanInfo ", beanInfoName, beanInfoClass, clasz.symbol)
+ enqueue("BeanInfo ", beanInfoName, beanInfoClass, clasz.symbol)
}
} // end of class JBeanInfoBuilder
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala index 21260d399c..ad054015ef 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala @@ -183,7 +183,6 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with class BytecodeGenerator(bytecodeWriter: BytecodeWriter) extends BytecodeUtil { def this() = this(new ClassBytecodeWriter { }) def debugLevel = settings.debuginfo.indexOfChoice - import bytecodeWriter.writeClass val MIN_SWITCH_DENSITY = 0.7 val INNER_CLASSES_FLAGS = @@ -344,6 +343,15 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with writeClass("" + sym.name, jclass.getName(), toByteArray(jclass), sym) } + val needsOutfileForSymbol = bytecodeWriter.isInstanceOf[ClassBytecodeWriter] + + def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], sym: Symbol) { + val outF: scala.tools.nsc.io.AbstractFile = { + if(needsOutfileForSymbol) getFile(sym, jclassName, ".class") else null + } + bytecodeWriter.writeClass(label, jclassName, jclassBytes, outF) + } + /** Returns the ScalaSignature annotation if it must be added to this class, * none otherwise; furthermore, it adds to `jclass` the ScalaSig marker * attribute (marking that a scala signature annotation is present) or the diff --git a/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala b/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala index f332e8cfdd..44acfed411 100644 --- a/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala +++ b/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala @@ -12,6 +12,29 @@ import scala.tools.nsc.symtab._ import scala.reflect.internal.util.NoSourceFile /** + * Inliner balances two competing goals: + * (a) aggressive inlining of: + * (a.1) the apply methods of anonymous closures, so that their anon-classes can be eliminated; + * (a.2) higher-order-methods defined in an external library, e.g. `Range.foreach()` among many others. + * (b) circumventing the barrier to inter-library inlining that private accesses in the callee impose. + * + * Summing up the discussion in SI-5442 and SI-5891, + * the current implementation achieves to a large degree both goals above, and + * overcomes a problem exhibited by previous versions: + * + * (1) Problem: Attempting to access a private member `p` at runtime resulting in an `IllegalAccessError`, + * where `p` is defined in a library L, and is accessed from a library C (for Client), + * where C was compiled against L', an optimized version of L where the inliner made `p` public at the bytecode level. + * The only such members are fields, either synthetic or isParamAccessor, and thus having a dollar sign in their name + * (the accesibility of methods and constructors isn't touched by the inliner). + * + * Thus we add one more goal to our list: + * (c) Compile C (either optimized or not) against any of L or L', + * so that it runs with either L or L' (in particular, compile against L' and run with L). + * + * The chosen strategy is described in some detail in the comments for `accessRequirements()` and `potentiallyPublicized()`. + * Documentation at http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/2011Q4/Inliner.pdf + * * @author Iulian Dragos */ abstract class Inliners extends SubComponent { @@ -50,6 +73,8 @@ abstract class Inliners extends SubComponent { ) def lookup(clazz: Symbol): Symbol = { // println("\t\tlooking up " + meth + " in " + clazz.fullName + " meth.owner = " + meth.owner) + assert(clazz != NoSymbol, "Walked up past Object.superClass looking for " + sym + + ", most likely this reveals the TFA at fault (receiver and callee don't match).") if (sym.owner == clazz || isBottomType(clazz)) sym else sym.overridingSymbol(clazz) match { case NoSymbol => if (sym.owner.isTrait) sym else lookup(clazz.superClass) @@ -86,13 +111,27 @@ abstract class Inliners extends SubComponent { def name = phaseName val inliner = new Inliner - override def apply(c: IClass) { - inliner analyzeClass c + object iclassOrdering extends Ordering[IClass] { + def compare(a: IClass, b: IClass) = { + val sourceNamesComparison = (a.cunit.toString() compare b.cunit.toString()) + if(sourceNamesComparison != 0) sourceNamesComparison + else { + val namesComparison = (a.toString() compare b.toString()) + if(namesComparison != 0) namesComparison + else { + a.symbol.id compare b.symbol.id + } + } + } } + val queue = new mutable.PriorityQueue[IClass]()(iclassOrdering) + + override def apply(c: IClass) { queue += c } override def run() { try { super.run() + for(c <- queue) { inliner analyzeClass c } } finally { inliner.clearCaches() } @@ -138,7 +177,8 @@ abstract class Inliners extends SubComponent { private def warn(pos: Position, msg: String) = currentIClazz.cunit.inlinerWarning(pos, msg) val recentTFAs = mutable.Map.empty[Symbol, Tuple2[Boolean, analysis.MethodTFA]] - private def getRecentTFA(incm: IMethod): (Boolean, analysis.MethodTFA) = { + + private def getRecentTFA(incm: IMethod, forceable: Boolean): (Boolean, analysis.MethodTFA) = { def containsRETURN(blocks: List[BasicBlock]) = blocks exists { bb => bb.lastInstruction.isInstanceOf[RETURN] } @@ -154,7 +194,7 @@ abstract class Inliners extends SubComponent { var a: analysis.MethodTFA = null if(hasRETURN) { a = new analysis.MethodTFA(incm); a.run } - if(hasInline(incm.symbol)) { recentTFAs.put(incm.symbol, (hasRETURN, a)) } + if(forceable) { recentTFAs.put(incm.symbol, (hasRETURN, a)) } (hasRETURN, a) } @@ -174,12 +214,22 @@ abstract class Inliners extends SubComponent { tfa.isOnWatchlist.clear() } + object imethodOrdering extends Ordering[IMethod] { + def compare(a: IMethod, b: IMethod) = { + val namesComparison = (a.toString() compare b.toString()) + if(namesComparison != 0) namesComparison + else { + a.symbol.id compare b.symbol.id + } + } + } + def analyzeClass(cls: IClass): Unit = if (settings.inline.value) { debuglog("Analyzing " + cls) this.currentIClazz = cls - val ms = cls.methods filterNot { _.symbol.isConstructor } + val ms = cls.methods filterNot { _.symbol.isConstructor } sorted imethodOrdering ms foreach { im => if(hasInline(im.symbol)) { log("Not inlining into " + im.symbol.originalName.decode + " because it is marked @inline.") @@ -221,7 +271,7 @@ abstract class Inliners extends SubComponent { * The ensuing analysis of each candidate (performed by `analyzeInc()`) * may result in a CFG isomorphic to that of the callee being inserted in place of the callsite * (i.e. a CALL_METHOD instruction is replaced with a single-entry single-exit CFG, - * a situation we call "successful inlining"). + * a substitution we call "successful inlining"). * * (3) following iterations have `relevantBBs` updated to focus on the inlined basic blocks and their successors only. * Details in `MTFAGrowable.reinit()` @@ -280,20 +330,23 @@ abstract class Inliners extends SubComponent { } /** - Decides whether it's feasible and desirable to inline the body of the method given by `concreteMethod` - at the program point given by `i` (a callsite). The boolean result indicates whether inlining was performed. - + * Decides whether it's feasible and desirable to inline the body of the method given by `concreteMethod` + * at the program point given by `i` (a callsite). The boolean result indicates whether inlining was performed. + * */ def analyzeInc(i: CALL_METHOD, bb: BasicBlock, receiver: Symbol, stackLength: Int, concreteMethod: Symbol): Boolean = { + assert(bb.toList contains i, "Candidate callsite does not belong to BasicBlock.") + var inlined = false - val msym = i.method + val shouldWarn = hasInline(i.method) - def warnNoInline(reason: String) = { - if (hasInline(msym) && !caller.isBridge) - warn(i.pos, "Could not inline required method %s because %s.".format(msym.originalName.decode, reason)) - } + def warnNoInline(reason: String) = { + if (shouldWarn) { + warn(i.pos, "Could not inline required method %s because %s.".format(i.method.originalName.decode, reason)) + } + } - def isAvailable = icodes available concreteMethod.enclClass + var isAvailable = icodes available concreteMethod.enclClass if (!isAvailable && shouldLoadImplFor(concreteMethod, receiver)) { // Until r22824 this line was: @@ -304,21 +357,23 @@ abstract class Inliners extends SubComponent { // was the proximate cause for SI-3882: // error: Illegal index: 0 overlaps List((variable par1,LONG)) // error: Illegal index: 0 overlaps List((variable par1,LONG)) - icodes.load(concreteMethod.enclClass) + isAvailable = icodes.load(concreteMethod.enclClass) } - def isCandidate = ( - isClosureClass(receiver) - || concreteMethod.isEffectivelyFinal - || receiver.isEffectivelyFinal - ) - def isApply = concreteMethod.name == nme.apply - def isCountable = !( - isClosureClass(receiver) - || isApply - || isMonadicMethod(concreteMethod) - || receiver.enclosingPackage == definitions.RuntimePackage - ) // only count non-closures + def isCandidate = ( + isClosureClass(receiver) + || concreteMethod.isEffectivelyFinal + || receiver.isEffectivelyFinal + ) + + def isApply = concreteMethod.name == nme.apply + + def isCountable = !( + isClosureClass(receiver) + || isApply + || isMonadicMethod(concreteMethod) + || receiver.enclosingPackage == definitions.RuntimePackage + ) // only count non-closures debuglog("Treating " + i + "\n\treceiver: " + receiver @@ -327,42 +382,62 @@ abstract class Inliners extends SubComponent { if (isAvailable && isCandidate) { lookupIMethod(concreteMethod, receiver) match { - case Some(callee) => + + case Some(callee) if callee.hasCode => val inc = new IMethodInfo(callee) val pair = new CallerCalleeInfo(caller, inc, fresh, inlinedMethodCount) - if (pair isStampedForInlining stackLength) { - retry = true - inlined = true - if (isCountable) - count += 1 - - pair.doInline(bb, i) - if (!inc.inline || inc.isMonadic) - caller.inlinedCalls += 1 - inlinedMethodCount(inc.sym) += 1 - - /* Remove this method from the cache, as the calls-private relation - * might have changed after the inlining. - */ - usesNonPublics -= m - recentTFAs -= m.symbol - } - else { - if (settings.debug.value) - pair logFailure stackLength + (pair isStampedForInlining stackLength) match { + + case inlInfo if inlInfo.isSafe => + + (inlInfo: @unchecked) match { + + case FeasibleInline(accessNeeded, toBecomePublic) => + for(f <- toBecomePublic) { + debuglog("Making public (synthetic) field-symbol: " + f) + f setFlag Flags.notPRIVATE + f setFlag Flags.notPROTECTED + } + // only add to `knownSafe` after all `toBecomePublic` fields actually made public. + if(accessNeeded == NonPublicRefs.Public) { tfa.knownSafe += inc.sym } + + case InlineableAtThisCaller => () + + } + + retry = true + inlined = true + if (isCountable) { count += 1 }; + + pair.doInline(bb, i) + if (!pair.isInlineForced || inc.isMonadic) { caller.inlinedCalls += 1 }; + inlinedMethodCount(inc.sym) += 1 - warnNoInline(pair failureReason stackLength) + // Remove the caller from the cache (this inlining might have changed its calls-private relation). + usesNonPublics -= m + recentTFAs -= m.symbol + + + case DontInlineHere(msg) => + debuglog("inline failed, reason: " + msg) + warnNoInline(msg) + + case NeverSafeToInline => () } + + case Some(callee) => + assert(!callee.hasCode, "The case clause right before this one should have handled this case.") + warnNoInline("callee (" + callee + ") has no code") + () + case None => warnNoInline("bytecode was not available") debuglog("could not find icode\n\treceiver: " + receiver + "\n\tmethod: " + concreteMethod) } + } else { + warnNoInline(if(!isAvailable) "bytecode was not available" else "it can be overridden") } - else warnNoInline( - if (!isAvailable) "bytecode was not available" - else "it can be overridden" - ) inlined } @@ -398,6 +473,7 @@ abstract class Inliners extends SubComponent { tfa.reinit(m, staleOut.toList, splicedBlocks, staleIn) tfa.run + staleOut.clear() splicedBlocks.clear() staleIn.clear() @@ -491,11 +567,8 @@ abstract class Inliners extends SubComponent { def paramTypes = sym.info.paramTypes def minimumStack = paramTypes.length + 1 - def inline = hasInline(sym) - def noinline = hasNoInline(sym) - def isBridge = sym.isBridge - def isInClosure = isClosureClass(owner) + val isInClosure = isClosureClass(owner) val isHigherOrder = isHigherOrderMethod(sym) def isMonadic = isMonadicMethod(sym) @@ -511,20 +584,131 @@ abstract class Inliners extends SubComponent { def isLarge = length > MAX_INLINE_SIZE def isRecursive = m.recursive def hasHandlers = handlers.nonEmpty + + def isSynchronized = sym.hasFlag(Flags.SYNCHRONIZED) def hasNonFinalizerHandler = handlers exists { case _: Finalizer => true case _ => false } - // the number of inlined calls in 'm', used by 'shouldInline' + // the number of inlined calls in 'm', used by 'isScoreOK' var inlinedCalls = 0 def addLocals(ls: List[Local]) = m.locals ++= ls def addLocal(l: Local) = addLocals(List(l)) def addHandlers(exhs: List[ExceptionHandler]) = m.exh = exhs ::: m.exh + + /** + * This method inspects the callee's instructions, finding out the most restrictive accessibility implied by them. + * + * Rather than giving up upon encountering an access to a private field `p`, it provisorily admits `p` as "can-be-made-public", provided: + * - `p` is being compiled as part of this compilation run, and + * - `p` is synthetic or param-accessor. + * + * This method is side-effect free, in particular it lets the invoker decide + * whether the accessibility of the `toBecomePublic` fields should be changed or not. + */ + def accessRequirements: AccessReq = { + + var toBecomePublic: List[Symbol] = Nil + + def check(sym: Symbol, cond: Boolean) = + if (cond) Private + else if (sym.isProtected) Protected + else Public + + def canMakePublic(f: Symbol): Boolean = + (m.sourceFile ne NoSourceFile) && + (f.isSynthetic || f.isParamAccessor) && + { toBecomePublic = f :: toBecomePublic; true } + + /* A safety check to consider as private, for the purposes of inlining, a public field that: + * (1) is defined in an external library, and + * (2) can be presumed synthetic (due to a dollar sign in its name). + * Such field was made public by `doMakePublic()` and we don't want to rely on that, + * because under other compilation conditions (ie no -optimize) that won't be the case anymore. + * + * This allows aggressive intra-library inlining (making public if needed) + * that does not break inter-library scenarios (see comment for `Inliners`). + * + * TODO handle more robustly the case of a trait var changed at the source-level from public to private[this] + * (eg by having ICodeReader use unpickler, see SI-5442). + * */ + def potentiallyPublicized(f: Symbol): Boolean = { + (m.sourceFile eq NoSourceFile) && f.name.containsChar('$') + } + + def checkField(f: Symbol) = check(f, potentiallyPublicized(f) || + (f.isPrivate && !canMakePublic(f))) + def checkSuper(n: Symbol) = check(n, n.isPrivate || !n.isClassConstructor) + def checkMethod(n: Symbol) = check(n, n.isPrivate) + + def getAccess(i: Instruction) = i match { + case CALL_METHOD(n, SuperCall(_)) => checkSuper(n) + case CALL_METHOD(n, _) => checkMethod(n) + case LOAD_FIELD(f, _) => checkField(f) + case STORE_FIELD(f, _) => checkField(f) + case _ => Public + } + + var seen = Public + val iter = instructions.iterator + while((seen ne Private) && iter.hasNext) { + val i = iter.next() + getAccess(i) match { + case Private => + log("instruction " + i + " requires private access.") + toBecomePublic = Nil + seen = Private + case Protected => seen = Protected + case _ => () + } + } + + AccessReq(seen, toBecomePublic) + } + } - class CallerCalleeInfo(val caller: IMethodInfo, val inc: IMethodInfo, fresh: mutable.Map[String, Int], inlinedMethodCount: collection.Map[Symbol, Int]) { + /** + * Classifies a pair (caller, callee) into one of four categories: + * + * (a) inlining should be performed, classified in turn into: + * (a.1) `InlineableAtThisCaller`: unconditionally at this caller + * (a.2) `FeasibleInline`: it only remains for certain access requirements to be met (see `IMethodInfo.accessRequirements()`) + * + * (b) inlining shouldn't be performed, classified in turn into: + * (b.1) `DontInlineHere`: indicates that this particular occurrence of the callee at the caller shouldn't be inlined. + * - Nothing is said about the outcome for other callers, or for other occurrences of the callee for the same caller. + * - In particular inlining might be possible, but heuristics gave a low score for it. + * (b.2) `NeverSafeToInline`: the callee can't be inlined anywhere, irrespective of caller. + * + * The classification above is computed by `isStampedForInlining()` based on which `analyzeInc()` goes on to: + * - either log the reason for failure --- case (b) ---, + * - or perform inlining --- case (a) ---. + */ + sealed abstract class InlineSafetyInfo { + def isSafe = false + def isUnsafe = !isSafe + } + case object NeverSafeToInline extends InlineSafetyInfo + case object InlineableAtThisCaller extends InlineSafetyInfo { override def isSafe = true } + case class DontInlineHere(msg: String) extends InlineSafetyInfo + case class FeasibleInline(accessNeeded: NonPublicRefs.Value, + toBecomePublic: List[Symbol]) extends InlineSafetyInfo { + override def isSafe = true + } + + case class AccessReq( + accessNeeded: NonPublicRefs.Value, + toBecomePublic: List[Symbol] + ) + + final class CallerCalleeInfo(val caller: IMethodInfo, val inc: IMethodInfo, fresh: mutable.Map[String, Int], inlinedMethodCount: collection.Map[Symbol, Int]) { + + assert(!caller.isBridge && inc.m.hasCode, + "A guard in Inliner.analyzeClass() should have prevented from getting here.") + def isLargeSum = caller.length + inc.length - 1 > SMALL_METHOD_SIZE private def freshName(s: String): TermName = { @@ -532,6 +716,12 @@ abstract class Inliners extends SubComponent { newTermName(s + fresh(s)) } + private def isKnownToInlineSafely: Boolean = { tfa.knownSafe(inc.sym) } + + val isInlineForced = hasInline(inc.sym) + val isInlineForbidden = hasNoInline(inc.sym) + assert(!(isInlineForced && isInlineForbidden), "method ("+inc.m+") marked both @inline and @noinline.") + /** Inline 'inc' into 'caller' at the given block and instruction. * The instruction must be a CALL_METHOD. */ @@ -549,7 +739,7 @@ abstract class Inliners extends SubComponent { def newLocal(baseName: String, kind: TypeKind) = new Local(caller.sym.newVariable(freshName(baseName), targetPos), kind, false) - val (hasRETURN, a) = getRecentTFA(inc.m) + val (hasRETURN, a) = getRecentTFA(inc.m, isInlineForced) /* The exception handlers that are active at the current block. */ val activeHandlers = caller.handlers filter (_ covered block) @@ -709,129 +899,94 @@ abstract class Inliners extends SubComponent { if (settings.debug.value) icodes.checkValid(caller.m) } - def isStampedForInlining(stackLength: Int) = - !sameSymbols && inc.m.hasCode && shouldInline && - isSafeToInline(stackLength) // `isSafeToInline()` must be invoked last in this AND expr bc it mutates the `knownSafe` and `knownUnsafe` maps for good. - - def logFailure(stackLength: Int) = log( - """|inline failed for %s: - | pair.sameSymbols: %s - | inc.numInlined < 2: %s - | inc.m.hasCode: %s - | isSafeToInline: %s - | shouldInline: %s - """.stripMargin.format( - inc.m, sameSymbols, inlinedMethodCount(inc.sym) < 2, - inc.m.hasCode, isSafeToInline(stackLength), shouldInline - ) - ) - - def failureReason(stackLength: Int) = - if (!inc.m.hasCode) "bytecode was unavailable" - else if (inc.m.symbol.hasFlag(Flags.SYNCHRONIZED)) "method is synchronized" - else if (!isSafeToInline(stackLength)) "it is unsafe (target may reference private fields)" - else "of a bug (run with -Ylog:inline -Ydebug for more information)" + def isStampedForInlining(stackLength: Int): InlineSafetyInfo = { - def canAccess(level: NonPublicRefs.Value) = level match { - case Private => caller.owner == inc.owner - case Protected => caller.owner.tpe <:< inc.owner.tpe - case Public => true - } - private def sameSymbols = caller.sym == inc.sym - private def sameOwner = caller.owner == inc.owner + if(tfa.blackballed(inc.sym)) { return NeverSafeToInline } - /** A method is safe to inline when: - * - it does not contain calls to private methods when called from another class - * - it is not inlined into a position with non-empty stack, - * while having a top-level finalizer (see liftedTry problem) - * - it is not recursive - * Note: - * - synthetic private members are made public in this pass. - */ - def isSafeToInline(stackLength: Int): Boolean = { + if(!isKnownToInlineSafely) { - if(tfa.blackballed(inc.sym)) { return false } - if(tfa.knownSafe(inc.sym)) { return true } + if(inc.openBlocks.nonEmpty) { + val msg = ("Encountered " + inc.openBlocks.size + " open block(s) in isSafeToInline: this indicates a bug in the optimizer!\n" + + " caller = " + caller.m + ", callee = " + inc.m) + warn(inc.sym.pos, msg) + tfa.knownNever += inc.sym + return DontInlineHere("Open blocks in " + inc.m) + } - if(helperIsSafeToInline(stackLength)) { - tfa.knownSafe += inc.sym; true - } else { - tfa.knownUnsafe += inc.sym; false - } - } + val reasonWhyNever: String = { + var rs: List[String] = Nil + if(inc.isRecursive) { rs ::= "is recursive" } + if(isInlineForbidden) { rs ::= "is annotated @noinline" } + if(inc.isSynchronized) { rs ::= "is synchronized method" } + if(rs.isEmpty) null else rs.mkString("", ", and ", "") + } - private def helperIsSafeToInline(stackLength: Int): Boolean = { - def makePublic(f: Symbol): Boolean = - /* - * Completely disabling member publifying. This shouldn't have been done in the first place. :| - */ - false - // (inc.m.sourceFile ne NoSourceFile) && (f.isSynthetic || f.isParamAccessor) && { - // debuglog("Making not-private symbol out of synthetic: " + f) - - // f setNotFlag Flags.PRIVATE - // true - // } - - if (!inc.m.hasCode || inc.isRecursive) { return false } - if (inc.m.symbol.hasFlag(Flags.SYNCHRONIZED)) { return false } - - val accessNeeded = usesNonPublics.getOrElseUpdate(inc.m, { - // Avoiding crashing the compiler if there are open blocks. - inc.openBlocks foreach { b => - warn(inc.sym.pos, - "Encountered open block in isSafeToInline: this indicates a bug in the optimizer!\n" + - " caller = " + caller.m + ", callee = " + inc.m - ) - return false + if(reasonWhyNever != null) { + tfa.knownNever += inc.sym + // next time around NeverSafeToInline is returned, thus skipping (duplicate) msg, this is intended. + return DontInlineHere(inc.m + " " + reasonWhyNever) } - def check(sym: Symbol, cond: Boolean) = - if (cond) Private - else if (sym.isProtected) Protected - else Public - - def checkField(f: Symbol) = check(f, f.isPrivate && !makePublic(f)) - def checkSuper(m: Symbol) = check(m, m.isPrivate || !m.isClassConstructor) - def checkMethod(m: Symbol) = check(m, m.isPrivate) - - def getAccess(i: Instruction) = i match { - case CALL_METHOD(m, SuperCall(_)) => checkSuper(m) - case CALL_METHOD(m, _) => checkMethod(m) - case LOAD_FIELD(f, _) => checkField(f) - case STORE_FIELD(f, _) => checkField(f) - case _ => Public + + if(sameSymbols) { // TODO but this also amounts to recursive, ie should lead to adding to tfa.knownNever, right? + tfa.knownUnsafe += inc.sym; + return DontInlineHere("sameSymbols (ie caller == callee)") } - def iterate(): NonPublicRefs.Value = inc.instructions.foldLeft(Public)((res, inc) => getAccess(inc) match { - case Private => log("instruction " + inc + " requires private access.") ; return Private - case Protected => Protected - case Public => res - }) - iterate() - }) + } - canAccess(accessNeeded) && { - val isIllegalStack = (stackLength > inc.minimumStack && inc.hasNonFinalizerHandler) + /* + * From here on, two main categories of checks remain, (a) and (b) below: + * (a.1) either the scoring heuristics give green light; or + * (a.2) forced as candidate due to @inline. + * After that, safety proper is checked: + * (b.1) the callee does not contain calls to private methods when called from another class + * (b.2) the callee is not going to be inlined into a position with non-empty stack, + * while having a top-level finalizer (see liftedTry problem) + * As a result of (b), some synthetic private members can be chosen to become public. + */ - !isIllegalStack || { - debuglog("method " + inc.sym + " is used on a non-empty stack with finalizer.") - false - } + if(!isInlineForced && !isScoreOK) { + // During inlining retry, a previous caller-callee pair that scored low may pass. + // Thus, adding the callee to tfa.knownUnsafe isn't warranted. + return DontInlineHere("too low score (heuristics)") + } + + if(isKnownToInlineSafely) { return InlineableAtThisCaller } + + if(stackLength > inc.minimumStack && inc.hasNonFinalizerHandler) { + val msg = "method " + inc.sym + " is used on a non-empty stack with finalizer." + debuglog(msg) + // FYI: not reason enough to add inc.sym to tfa.knownUnsafe (because at other callsite in this caller, inlining might be ok) + return DontInlineHere(msg) + } + + val accReq = inc.accessRequirements + if(!canAccess(accReq.accessNeeded)) { + tfa.knownUnsafe += inc.sym + return DontInlineHere("access level required by callee not matched by caller") } + + FeasibleInline(accReq.accessNeeded, accReq.toBecomePublic) + } - /** Decide whether to inline or not. Heuristics: + def canAccess(level: NonPublicRefs.Value) = level match { + case Private => caller.owner == inc.owner + case Protected => caller.owner.tpe <:< inc.owner.tpe + case Public => true + } + private def sameSymbols = caller.sym == inc.sym + private def sameOwner = caller.owner == inc.owner + + /** Gives green light for inlining (which may still be vetoed later). Heuristics: * - it's bad to make the caller larger (> SMALL_METHOD_SIZE) if it was small * - it's bad to inline large methods * - it's good to inline higher order functions * - it's good to inline closures functions. * - it's bad (useless) to inline inside bridge methods */ - private def neverInline = caller.isBridge || !inc.m.hasCode || inc.noinline - private def alwaysInline = inc.inline - - def shouldInline: Boolean = !neverInline && (alwaysInline || { - debuglog("shouldInline: " + caller.m + " with " + inc.m) + def isScoreOK: Boolean = { + debuglog("shouldInline: " + caller.m + " , callee:" + inc.m) var score = 0 @@ -855,7 +1010,7 @@ abstract class Inliners extends SubComponent { log("shouldInline(" + inc.m + ") score: " + score) score > 0 - }) + } } def lookupIMethod(meth: Symbol, receiver: Symbol): Option[IMethod] = { diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index e99c31374e..960c210649 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -1614,6 +1614,13 @@ trait Infer { val saved = context.state var fallback = false context.setBufferErrors() + // We cache the current buffer because it is impossible to + // distinguish errors that occurred before entering tryTwice + // and our first attempt in 'withImplicitsDisabled'. If the + // first attempt fails we try with implicits on *and* clean + // buffer but that would also flush any pre-tryTwice valid + // errors, hence some manual buffer tweaking is necessary. + val errorsToRestore = context.flushAndReturnBuffer() try { context.withImplicitsDisabled(infer(false)) if (context.hasErrors) { @@ -1627,8 +1634,10 @@ trait Infer { case ex: TypeError => // recoverable cyclic references context.restoreState(saved) if (!fallback) infer(true) else () + } finally { + context.restoreState(saved) + context.updateBuffer(errorsToRestore) } - context.restoreState(saved) } else infer(true) } diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 44fd4e9afd..7318538de7 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -536,7 +536,7 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R def javaErasedOverridingSym(sym: Symbol): Symbol = clazz.tpe.nonPrivateMemberAdmitting(sym.name, BRIDGE).filter(other => - !other.isDeferred && other.isJavaDefined && { + !other.isDeferred && other.isJavaDefined && !sym.enclClass.isSubClass(other.enclClass) && { // #3622: erasure operates on uncurried types -- // note on passing sym in both cases: only sym.isType is relevant for uncurry.transformInfo // !!! erasure.erasure(sym, uncurry.transformInfo(sym, tp)) gives erreneous of inaccessible type - check whether that's still the case! diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 52cce11deb..018daf4568 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1753,6 +1753,12 @@ trait Typers extends Modes with Adaptations with Tags { if (clazz.info.firstParent.typeSymbol == AnyValClass) validateDerivedValueClass(clazz, body1) + if (clazz.isTrait) { + for (decl <- clazz.info.decls if decl.isTerm && decl.isEarlyInitialized) { + unit.warning(decl.pos, "Implementation restriction: early definitions in traits are not initialized before the super class is initialized.") + } + } + treeCopy.Template(templ, parents1, self1, body1) setType clazz.tpe } |