/* NSC -- new Scala compiler * Copyright 2005-2013 LAMP/EPFL * @author Martin Odersky */ package scala package tools.nsc package backend package icode import scala.reflect.internal.util.{Position,NoPosition} /* A pattern match // 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) => */ /** * The ICode intermediate representation. It is a stack-based * representation, very close to the JVM and .NET. It uses the * erased types of Scala and references Symbols to refer named entities * in the source files. */ trait Opcodes { self: ICodes => import global.{Symbol, NoSymbol, 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 private lazy val ObjectReferenceList = ObjectReference :: Nil /** This class represents an instruction of the intermediate code. * Each case subclass will represent a specific operation. */ abstract class Instruction extends Cloneable { // Vlad: I used these for checking the quality of the implementation, and we should regularly run a build with them // enabled. But for production these should definitely be disabled, unless we enjoy getting angry emails from Greg :) //if (!this.isInstanceOf[opcodes.LOAD_EXCEPTION]) // assert(consumed == consumedTypes.length) //assert(produced == producedTypes.length) def category: Int = 0 // undefined /** This abstract method returns the number of used elements on the stack */ def consumed : Int = 0 /** This abstract method returns the number of produced elements on the stack */ def produced : Int = 0 /** This instruction consumes these types from the top of the stack, the first * element in the list is the deepest element on the stack. */ def consumedTypes: List[TypeKind] = Nil /** This instruction produces these types on top of the stack. */ // Vlad: I wonder why we keep producedTypes around -- it looks like an useless thing to have def producedTypes: List[TypeKind] = Nil /** The corresponding position in the source file */ private var _pos: Position = NoPosition def pos: Position = _pos def setPos(p: Position): this.type = { _pos = p this } /** Clone this instruction. */ override def clone(): Instruction = super.clone.asInstanceOf[Instruction] } object opcodes { /** Loads "this" on top of the stack. * Stack: ... * ->: ...:ref */ case class THIS(clasz: Symbol) extends Instruction { /** Returns a string representation of this constant */ override def toString = "THIS(" + clasz.name + ")" override def consumed = 0 override def produced = 1 override def producedTypes = // we're not allowed to have REFERENCE(Array), but what about compiling the Array class? Well, we use object for it. if (clasz != global.definitions.ArrayClass) REFERENCE(clasz) :: Nil else ObjectReference :: Nil override def category = localsCat } /** Loads a constant on the stack. * Stack: ... * ->: ...:constant */ case class CONSTANT(constant: Constant) extends Instruction { override def toString = "CONSTANT(" + constant.escapedStringValue + ")" override def consumed = 0 override def produced = 1 override def producedTypes = toTypeKind(constant.tpe) :: Nil override def category = constCat } /** Loads an element of an array. The array and the index should * be on top of the stack. * Stack: ...:array[a](Ref):index(Int) * ->: ...:element(a) */ case class LOAD_ARRAY_ITEM(kind: TypeKind) extends Instruction { override def consumed = 2 override def produced = 1 override def consumedTypes = ARRAY(kind) :: INT :: Nil override def producedTypes = kind :: Nil override def category = arraysCat } /** Load a local variable on the stack. It can be a method argument. * Stack: ... * ->: ...:value */ case class LOAD_LOCAL(local: Local) extends Instruction { override def consumed = 0 override def produced = 1 override def producedTypes = local.kind :: Nil override def category = localsCat } /** Load a field on the stack. The object to which it refers should be * on the stack. * Stack: ...:ref (assuming isStatic = false) * ->: ...:value */ case class LOAD_FIELD(field: Symbol, isStatic: Boolean) extends Instruction { /** Returns a string representation of this instruction */ override def toString(): String = "LOAD_FIELD " + (if (isStatic) field.fullName else field.toString()) override def consumed = if (isStatic) 0 else 1 override def produced = 1 override def consumedTypes = if (isStatic) Nil else REFERENCE(field.owner) :: Nil override def producedTypes = toTypeKind(field.tpe) :: Nil // more precise information about how to load this field // 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 { assert(module != NoSymbol, "Invalid module symbol") /** Returns a string representation of this instruction */ override def toString(): String = "LOAD_MODULE " + module override def consumed = 0 override def produced = 1 override def producedTypes = REFERENCE(module) :: Nil override def category = stackCat } /** Store a value into an array at a specified index. * Stack: ...:array[a](Ref):index(Int):value(a) * ->: ... */ case class STORE_ARRAY_ITEM(kind: TypeKind) extends Instruction { override def consumed = 3 override def produced = 0 override def consumedTypes = ARRAY(kind) :: INT :: kind :: Nil override def category = arraysCat } /** Store a value into a local variable. It can be an argument. * Stack: ...:value * ->: ... */ case class STORE_LOCAL(local: Local) extends Instruction { override def consumed = 1 override def produced = 0 override def consumedTypes = local.kind :: Nil override def category = localsCat } /** Store a value into a field. * Stack: ...:ref:value (assuming isStatic=false) * ->: ... */ case class STORE_FIELD(field: Symbol, isStatic: Boolean) extends Instruction { /** Returns a string representation of this instruction */ override def toString(): String = "STORE_FIELD "+field + (if (isStatic) " (static)" else " (dynamic)") override def consumed = if(isStatic) 1 else 2 override def produced = 0 override def consumedTypes = if (isStatic) toTypeKind(field.tpe) :: Nil else REFERENCE(field.owner) :: toTypeKind(field.tpe) :: Nil override def category = fldsCat } /** Store a value into the 'this' pointer. * Stack: ...:ref * ->: ... */ case class STORE_THIS(kind: TypeKind) extends Instruction { override def consumed = 1 override def produced = 0 override def consumedTypes = kind :: Nil override def category = localsCat } /** Call a primitive function. * Stack: ...:arg1:arg2:...:argn * ->: ...:result */ case class CALL_PRIMITIVE(primitive: Primitive) extends Instruction { override def consumed = primitive match { case Negation(_) => 1 case Test(_,_, true) => 1 case Test(_,_, false) => 2 case Comparison(_,_) => 2 case Arithmetic(NOT,_) => 1 case Arithmetic(_,_) => 2 case Logical(_,_) => 2 case Shift(_,_) => 2 case Conversion(_,_) => 1 case ArrayLength(_) => 1 case StringConcat(_) => 2 case StartConcat => 0 case EndConcat => 1 } override def produced = 1 override def consumedTypes = primitive match { case Negation(kind) => kind :: Nil case Test(_, kind, true) => kind :: Nil case Test(_, kind, false) => kind :: kind :: Nil case Comparison(_, kind) => kind :: kind :: Nil case Arithmetic(NOT, kind) => kind :: Nil case Arithmetic(_, kind) => kind :: kind :: Nil case Logical(_, kind) => kind :: kind :: Nil case Shift(_, kind) => kind :: INT :: Nil case Conversion(from, _) => from :: Nil case ArrayLength(kind) => ARRAY(kind) :: Nil case StringConcat(kind) => ConcatClass :: kind :: Nil case StartConcat => Nil case EndConcat => ConcatClass :: Nil } override def producedTypes = primitive match { case Negation(kind) => kind :: Nil case Test(_, _, true) => BOOL :: Nil case Test(_, _, false) => BOOL :: Nil case Comparison(_, _) => INT :: Nil case Arithmetic(_, kind) => kind :: Nil case Logical(_, kind) => kind :: Nil case Shift(_, kind) => kind :: Nil case Conversion(_, to) => to :: Nil case ArrayLength(_) => INT :: Nil case StringConcat(_) => ConcatClass :: Nil case StartConcat => ConcatClass :: Nil case EndConcat => REFERENCE(global.definitions.StringClass) :: Nil } override def category = arilogCat } /** This class represents a CALL_METHOD instruction * STYLE: dynamic / static(StaticInstance) * Stack: ...:ref:arg1:arg2:...:argn * ->: ...:result * * STYLE: static(StaticClass) * Stack: ...:arg1:arg2:...:argn * ->: ...:result * */ case class CALL_METHOD(method: Symbol, style: InvokeStyle) extends Instruction with ReferenceEquality { def toShortString = "CALL_METHOD " + method.name +" ("+style+")" /** Returns a string representation of this instruction */ override def toString(): String = "CALL_METHOD " + method.fullName +" ("+style+")" var hostClass: Symbol = method.owner def setHostClass(cls: Symbol): this.type = { hostClass = cls; this } /** This is specifically for preserving the target native Array type long * enough that clone() can generate the right call. */ var targetTypeKind: TypeKind = UNIT // the default should never be used, so UNIT should fail fast. def setTargetTypeKind(tk: TypeKind) = targetTypeKind = tk private def params = method.info.paramTypes private def consumesInstance = style match { case Static(false) => 0 case _ => 1 } override def consumed = params.length + consumesInstance override def consumedTypes = { val args = params map toTypeKind if (consumesInstance > 0) ObjectReference :: args else args } private val producedList = toTypeKind(method.info.resultType) match { case UNIT => Nil case _ if method.isConstructor => Nil case kind => kind :: Nil } override def produced = producedList.size override def producedTypes = producedList /** object identity is equality for CALL_METHODs. Needed for * being able to store such instructions into maps, when more * than one CALL_METHOD to the same method might exist. */ override def category = mthdsCat } /** * A place holder entry that allows us to parse class files with invoke dynamic * instructions. Because the compiler doesn't yet really understand the * behavior of invokeDynamic, this op acts as a poison pill. Any attempt to analyze * this instruction will cause a failure. The only optimization that * should ever look at non-Scala generated icode is the inliner, and it * has been modified to not examine any method with invokeDynamic * instructions. So if this poison pill ever causes problems then * there's been a serious misunderstanding */ // TODO do the real thing case class INVOKE_DYNAMIC(poolEntry: Int) extends Instruction { private def error = sys.error("INVOKE_DYNAMIC is not fully implemented and should not be analyzed") override def consumed = error override def produced = error override def producedTypes = error override def category = error } case class BOX(boxType: TypeKind) extends Instruction { assert(boxType.isValueType && (boxType ne UNIT)) // documentation override def toString(): String = "BOX " + boxType override def consumed = 1 override def consumedTypes = boxType :: Nil override def produced = 1 override def producedTypes = BOXED(boxType) :: Nil override def category = objsCat } case class UNBOX(boxType: TypeKind) extends Instruction { assert(boxType.isValueType && !boxType.isInstanceOf[BOXED] && (boxType ne UNIT)) // documentation override def toString(): String = "UNBOX " + boxType override def consumed = 1 override def consumedTypes = ObjectReferenceList override def produced = 1 override def producedTypes = boxType :: Nil override def category = objsCat } /** Create a new instance of a class through the specified constructor * Stack: ...:arg1:arg2:...:argn * ->: ...:ref */ case class NEW(kind: REFERENCE) extends Instruction { /** Returns a string representation of this instruction */ override def toString(): String = "NEW "+ kind override def consumed = 0 override def produced = 1 override def producedTypes = kind :: Nil /** The corresponding constructor call. */ var init: CALL_METHOD = _ override def category = objsCat } /** This class represents a CREATE_ARRAY instruction * Stack: ...:size_1:size_2:..:size_n * ->: ...:arrayref */ case class CREATE_ARRAY(elem: TypeKind, dims: Int) extends Instruction { /** Returns a string representation of this instruction */ override def toString(): String ="CREATE_ARRAY "+elem + " x " + dims override def consumed = dims override def consumedTypes = List.fill(dims)(INT) override def produced = 1 override def producedTypes = ARRAY(elem) :: Nil override def category = arraysCat } /** This class represents a IS_INSTANCE instruction * Stack: ...:ref * ->: ...:result(boolean) */ case class IS_INSTANCE(typ: TypeKind) extends Instruction { /** Returns a string representation of this instruction */ override def toString(): String ="IS_INSTANCE "+typ override def consumed = 1 override def produced = 1 override def consumedTypes = ObjectReferenceList override def producedTypes = BOOL :: Nil override def category = castsCat } /** This class represents a CHECK_CAST instruction * Stack: ...:ref(oldtype) * ->: ...:ref(typ <=: oldtype) */ case class CHECK_CAST(typ: TypeKind) extends Instruction { /** Returns a string representation of this instruction */ override def toString(): String ="CHECK_CAST "+typ override def consumed = 1 override def produced = 1 override def consumedTypes = ObjectReferenceList override def producedTypes = typ :: Nil override def category = castsCat } /** This class represents a SWITCH instruction * Stack: ...:index(int) * ->: ...: * * The tags array contains one entry per label, each entry consisting of * an array of ints, any of which will trigger the jump to the corresponding label. * labels should contain an extra label, which is the 'default' jump. */ case class SWITCH(tags: List[List[Int]], labels: List[BasicBlock]) extends Instruction { /** Returns a string representation of this instruction */ override def toString(): String ="SWITCH ..." override def consumed = 1 override def produced = 0 override def consumedTypes = INT :: Nil 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 * Stack: ... * ->: ... */ case class JUMP(whereto: BasicBlock) extends Instruction { /** Returns a string representation of this instruction */ override def toString(): String ="JUMP "+whereto.label override def consumed = 0 override def produced = 0 override def category = jumpsCat } /** This class represents a CJUMP instruction * It compares the two values on the stack with the 'cond' test operator * Stack: ...:value1:value2 * ->: ... */ case class CJUMP(successBlock: BasicBlock, failureBlock: BasicBlock, cond: TestOp, kind: TypeKind) extends Instruction { /** Returns a string representation of this instruction */ override def toString(): String = ( "CJUMP (" + kind + ")" + cond + " ? "+successBlock.label+" : "+failureBlock.label ) override def consumed = 2 override def produced = 0 override def consumedTypes = kind :: kind :: Nil override def category = jumpsCat } /** This class represents a CZJUMP instruction * It compares the one value on the stack and zero with the 'cond' test operator * Stack: ...:value: * ->: ... */ case class CZJUMP(successBlock: BasicBlock, failureBlock: BasicBlock, cond: TestOp, kind: TypeKind) extends Instruction { /** Returns a string representation of this instruction */ override def toString(): String = ( "CZJUMP (" + kind + ")" + cond + " ? "+successBlock.label+" : "+failureBlock.label ) override def consumed = 1 override def produced = 0 override def consumedTypes = kind :: Nil override def category = jumpsCat } /** This class represents a RETURN instruction * Stack: ... * ->: ... */ case class RETURN(kind: TypeKind) extends Instruction { override def consumed = if (kind == UNIT) 0 else 1 override def produced = 0 override def consumedTypes = if (kind == UNIT) Nil else kind :: Nil override def category = retCat } /** This class represents a THROW instruction * Stack: ...:Throwable(Ref) * ->: ...: */ case class THROW(clasz: Symbol) extends Instruction { /** PP to ID: We discussed parameterizing LOAD_EXCEPTION but * not THROW, which came about organically. It seems like the * right thing, but can you confirm? */ override def toString = "THROW(" + clasz.name + ")" override def consumed = 1 override def produced = 0 override def consumedTypes = toTypeKind(clasz.tpe) :: Nil override def category = retCat } /** This class represents a DROP instruction * Stack: ...:something * ->: ... */ case class DROP (typ: TypeKind) extends Instruction { /** Returns a string representation of this instruction */ override def toString(): String ="DROP "+typ override def consumed = 1 override def produced = 0 override def consumedTypes = typ :: Nil override def category = stackCat } /** This class represents a DUP instruction * Stack: ...:something * ->: ...:something:something */ case class DUP (typ: TypeKind) extends Instruction { override def consumed = 1 override def produced = 2 override def consumedTypes = typ :: Nil override def producedTypes = typ :: typ :: Nil override def category = stackCat } /** This class represents a MONITOR_ENTER instruction * Stack: ...:object(ref) * ->: ...: */ case class MONITOR_ENTER() extends Instruction { /** Returns a string representation of this instruction */ override def toString(): String ="MONITOR_ENTER" override def consumed = 1 override def produced = 0 override def consumedTypes = ObjectReference :: Nil override def category = objsCat } /** This class represents a MONITOR_EXIT instruction * Stack: ...:object(ref) * ->: ...: */ case class MONITOR_EXIT() extends Instruction { /** Returns a string representation of this instruction */ override def toString(): String ="MONITOR_EXIT" override def consumed = 1 override def produced = 0 override def consumedTypes = ObjectReference :: Nil override def category = objsCat } /** A local variable becomes visible at this point in code. * Used only for generating precise local variable tables as * debugging information. */ case class SCOPE_ENTER(lv: Local) extends Instruction { 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. * Used only for generating precise local variable tables as * debugging information. */ case class SCOPE_EXIT(lv: Local) extends Instruction { 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 * on top of the /empty/ stack at the beginning of each exception handler. * Note: Unlike other instructions, it consumes all elements on the stack! * then pushes one exception instance. */ case class LOAD_EXCEPTION(clasz: Symbol) extends Instruction { 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. */ sealed abstract class InvokeStyle { /** Is this a dynamic method call? */ def isDynamic: Boolean = false /** Is this a static method call? */ def isStatic: Boolean = false def isSuper: Boolean = false /** Is this an instance method call? */ def hasInstance: Boolean = true /** Returns a string representation of this style. */ override def toString(): String } /** Virtual calls. * On JVM, translated to either `invokeinterface` or `invokevirtual`. */ case object Dynamic extends InvokeStyle { override def isDynamic = true override def toString(): String = "dynamic" } /** * Special invoke: * Static(true) is used for calls to private members, ie `invokespecial` on JVM. * Static(false) is used for calls to class-level instance-less static methods, ie `invokestatic` on JVM. */ case class Static(onInstance: Boolean) extends InvokeStyle { override def isStatic = true override def hasInstance = onInstance override def toString(): String = { if(onInstance) "static-instance" else "static-class" } } /** Call through super[mix]. * On JVM, translated to `invokespecial`. */ case class SuperCall(mix: Name) extends InvokeStyle { override def isSuper = true override def toString(): String = { "super(" + mix + ")" } } } }