scala.tools.nsc.backend.jvm.BCodeGlue.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala-compiler Show documentation
Show all versions of scala-compiler Show documentation
Compiler for the SubScript extension of the Scala Programming Language
/* NSC -- new Scala compiler
* Copyright 2005-2012 LAMP/EPFL
* @author Martin Odersky
*/
package scala
package tools.nsc
package backend.jvm
import scala.tools.asm
import scala.annotation.switch
import scala.collection.{ immutable, mutable }
/*
* Immutable representations of bytecode-level types.
*
* @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded
* @version 1.0
*
*/
abstract class BCodeGlue extends SubComponent {
import global._
protected val bCodeICodeCommon: BCodeICodeCommon[global.type] = new BCodeICodeCommon(global)
object BType {
import global.chrs
// ------------- sorts -------------
val VOID : Int = 0
val BOOLEAN: Int = 1
val CHAR : Int = 2
val BYTE : Int = 3
val SHORT : Int = 4
val INT : Int = 5
val FLOAT : Int = 6
val LONG : Int = 7
val DOUBLE : Int = 8
val ARRAY : Int = 9
val OBJECT : Int = 10
val METHOD : Int = 11
// ------------- primitive types -------------
val VOID_TYPE = new BType(VOID, ('V' << 24) | (5 << 16) | (0 << 8) | 0, 1)
val BOOLEAN_TYPE = new BType(BOOLEAN, ('Z' << 24) | (0 << 16) | (5 << 8) | 1, 1)
val CHAR_TYPE = new BType(CHAR, ('C' << 24) | (0 << 16) | (6 << 8) | 1, 1)
val BYTE_TYPE = new BType(BYTE, ('B' << 24) | (0 << 16) | (5 << 8) | 1, 1)
val SHORT_TYPE = new BType(SHORT, ('S' << 24) | (0 << 16) | (7 << 8) | 1, 1)
val INT_TYPE = new BType(INT, ('I' << 24) | (0 << 16) | (0 << 8) | 1, 1)
val FLOAT_TYPE = new BType(FLOAT, ('F' << 24) | (2 << 16) | (2 << 8) | 1, 1)
val LONG_TYPE = new BType(LONG, ('J' << 24) | (1 << 16) | (1 << 8) | 2, 1)
val DOUBLE_TYPE = new BType(DOUBLE, ('D' << 24) | (3 << 16) | (3 << 8) | 2, 1)
/*
* Returns the Java type corresponding to the given type descriptor.
*
* @param off the offset of this descriptor in the chrs buffer.
* @return the Java type corresponding to the given type descriptor.
*
* can-multi-thread
*/
def getType(off: Int): BType = {
var len = 0
chrs(off) match {
case 'V' => VOID_TYPE
case 'Z' => BOOLEAN_TYPE
case 'C' => CHAR_TYPE
case 'B' => BYTE_TYPE
case 'S' => SHORT_TYPE
case 'I' => INT_TYPE
case 'F' => FLOAT_TYPE
case 'J' => LONG_TYPE
case 'D' => DOUBLE_TYPE
case '[' =>
len = 1
while (chrs(off + len) == '[') {
len += 1
}
if (chrs(off + len) == 'L') {
len += 1
while (chrs(off + len) != ';') {
len += 1
}
}
new BType(ARRAY, off, len + 1)
case 'L' =>
len = 1
while (chrs(off + len) != ';') {
len += 1
}
new BType(OBJECT, off + 1, len - 1)
// case '(':
case _ =>
assert(chrs(off) == '(')
var resPos = off + 1
while (chrs(resPos) != ')') { resPos += 1 }
val resType = getType(resPos + 1)
val len = resPos - off + 1 + resType.len;
new BType(
METHOD,
off,
if (resType.hasObjectSort) {
len + 2 // "+ 2" accounts for the "L ... ;" in a descriptor for a non-array reference.
} else {
len
}
)
}
}
/* Params denote an internal name.
* can-multi-thread
*/
def getObjectType(index: Int, length: Int): BType = {
val sort = if (chrs(index) == '[') ARRAY else OBJECT;
new BType(sort, index, length)
}
/*
* @param methodDescriptor a method descriptor.
*
* must-single-thread
*/
def getMethodType(methodDescriptor: String): BType = {
val n = global.newTypeName(methodDescriptor)
new BType(BType.METHOD, n.start, n.length) // TODO assert isValidMethodDescriptor
}
/*
* Returns the Java method type corresponding to the given argument and return types.
*
* @param returnType the return type of the method.
* @param argumentTypes the argument types of the method.
* @return the Java type corresponding to the given argument and return types.
*
* must-single-thread
*/
def getMethodType(returnType: BType, argumentTypes: Array[BType]): BType = {
val n = global.newTypeName(getMethodDescriptor(returnType, argumentTypes))
new BType(BType.METHOD, n.start, n.length)
}
/*
* Returns the Java types corresponding to the argument types of method descriptor whose first argument starts at idx0.
*
* @param idx0 index into chrs of the first argument.
* @return the Java types corresponding to the argument types of the given method descriptor.
*
* can-multi-thread
*/
private def getArgumentTypes(idx0: Int): Array[BType] = {
assert(chrs(idx0 - 1) == '(', "doesn't look like a method descriptor.")
val args = new Array[BType](getArgumentCount(idx0))
var off = idx0
var size = 0
while (chrs(off) != ')') {
args(size) = getType(off)
off += args(size).len
if (args(size).sort == OBJECT) { off += 2 }
// debug: assert("LVZBSCIJFD[)".contains(chrs(off)))
size += 1
}
// debug: var check = 0; while (check < args.length) { assert(args(check) != null); check += 1 }
args
}
/*
* Returns the number of argument types of this method type, whose first argument starts at idx0.
*
* @param idx0 index into chrs of the first argument.
* @return the number of argument types of this method type.
*
* can-multi-thread
*/
private def getArgumentCount(idx0: Int): Int = {
assert(chrs(idx0 - 1) == '(', "doesn't look like a method descriptor.")
var off = idx0
var size = 0
var keepGoing = true
while (keepGoing) {
val car = chrs(off)
off += 1
if (car == ')') {
keepGoing = false
} else if (car == 'L') {
while (chrs(off) != ';') { off += 1 }
off += 1
size += 1
} else if (car != '[') {
size += 1
}
}
size
}
/*
* Returns the Java type corresponding to the return type of the given
* method descriptor.
*
* @param methodDescriptor a method descriptor.
* @return the Java type corresponding to the return type of the given method descriptor.
*
* must-single-thread
*/
def getReturnType(methodDescriptor: String): BType = {
val n = global.newTypeName(methodDescriptor)
val delta = n.pos(')') // `delta` is relative to the Name's zero-based start position, not a valid index into chrs.
assert(delta < n.length, s"not a valid method descriptor: $methodDescriptor")
getType(n.start + delta + 1)
}
/*
* Returns the descriptor corresponding to the given argument and return types.
* Note: no BType is created here for the resulting method descriptor,
* if that's desired the invoker is responsible for that.
*
* @param returnType the return type of the method.
* @param argumentTypes the argument types of the method.
* @return the descriptor corresponding to the given argument and return types.
*
* can-multi-thread
*/
def getMethodDescriptor(
returnType: BType,
argumentTypes: Array[BType]): String =
{
val buf = new StringBuffer()
buf.append('(')
var i = 0
while (i < argumentTypes.length) {
argumentTypes(i).getDescriptor(buf)
i += 1
}
buf.append(')')
returnType.getDescriptor(buf)
buf.toString()
}
} // end of object BType
/*
* Based on ASM's Type class. Namer's chrs is used in this class for the same purposes as the `buf` char array in asm.Type.
*
* All methods of this classs can-multi-thread
*/
final class BType(val sort: Int, val off: Int, val len: Int) {
import global.chrs
/*
* can-multi-thread
*/
def toASMType: scala.tools.asm.Type = {
import scala.tools.asm
// using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match"
(sort: @switch) match {
case asm.Type.VOID => asm.Type.VOID_TYPE
case asm.Type.BOOLEAN => asm.Type.BOOLEAN_TYPE
case asm.Type.CHAR => asm.Type.CHAR_TYPE
case asm.Type.BYTE => asm.Type.BYTE_TYPE
case asm.Type.SHORT => asm.Type.SHORT_TYPE
case asm.Type.INT => asm.Type.INT_TYPE
case asm.Type.FLOAT => asm.Type.FLOAT_TYPE
case asm.Type.LONG => asm.Type.LONG_TYPE
case asm.Type.DOUBLE => asm.Type.DOUBLE_TYPE
case asm.Type.ARRAY |
asm.Type.OBJECT => asm.Type.getObjectType(getInternalName)
case asm.Type.METHOD => asm.Type.getMethodType(getDescriptor)
}
}
/*
* Unlike for ICode's REFERENCE, isBoxedType(t) implies isReferenceType(t)
* Also, `isReferenceType(RT_NOTHING) == true` , similarly for RT_NULL.
* Use isNullType() , isNothingType() to detect Nothing and Null.
*
* can-multi-thread
*/
def hasObjectSort = (sort == BType.OBJECT)
/*
* Returns the number of dimensions of this array type. This method should
* only be used for an array type.
*
* @return the number of dimensions of this array type.
*
* can-multi-thread
*/
def getDimensions: Int = {
var i = 1
while (chrs(off + i) == '[') {
i += 1
}
i
}
/*
* Returns the (ultimate) element type of this array type.
* This method should only be used for an array type.
*
* @return Returns the type of the elements of this array type.
*
* can-multi-thread
*/
def getElementType: BType = {
assert(isArray, s"Asked for the element type of a non-array type: $this")
BType.getType(off + getDimensions)
}
/*
* Returns the internal name of the class corresponding to this object or
* array type. The internal name of a class is its fully qualified name (as
* returned by Class.getName(), where '.' are replaced by '/'. This method
* should only be used for an object or array type.
*
* @return the internal name of the class corresponding to this object type.
*
* can-multi-thread
*/
def getInternalName: String = {
new String(chrs, off, len)
}
/*
* @return the suffix of the internal name until the last '/' (if '/' present), internal name otherwise.
*
* can-multi-thread
*/
def getSimpleName: String = {
assert(hasObjectSort, s"not of object sort: $toString")
val iname = getInternalName
val idx = iname.lastIndexOf('/')
if (idx == -1) iname
else iname.substring(idx + 1)
}
/*
* Returns the argument types of methods of this type.
* This method should only be used for method types.
*
* @return the argument types of methods of this type.
*
* can-multi-thread
*/
def getArgumentTypes: Array[BType] = {
BType.getArgumentTypes(off + 1)
}
/*
* Returns the return type of methods of this type.
* This method should only be used for method types.
*
* @return the return type of methods of this type.
*
* can-multi-thread
*/
def getReturnType: BType = {
assert(chrs(off) == '(', s"doesn't look like a method descriptor: $toString")
var resPos = off + 1
while (chrs(resPos) != ')') { resPos += 1 }
BType.getType(resPos + 1)
}
// ------------------------------------------------------------------------
// Inspector methods
// ------------------------------------------------------------------------
def isPrimitiveOrVoid = (sort < BType.ARRAY) // can-multi-thread
def isValueType = (sort < BType.ARRAY) // can-multi-thread
def isArray = (sort == BType.ARRAY) // can-multi-thread
def isUnitType = (sort == BType.VOID) // can-multi-thread
def isRefOrArrayType = { hasObjectSort || isArray } // can-multi-thread
def isNonUnitValueType = { isValueType && !isUnitType } // can-multi-thread
def isNonSpecial = { !isValueType && !isArray && !isPhantomType } // can-multi-thread
def isNothingType = { (this == RT_NOTHING) || (this == CT_NOTHING) } // can-multi-thread
def isNullType = { (this == RT_NULL) || (this == CT_NULL) } // can-multi-thread
def isPhantomType = { isNothingType || isNullType } // can-multi-thread
/*
* can-multi-thread
*/
def isBoxed = {
this match {
case BOXED_UNIT | BOXED_BOOLEAN | BOXED_CHAR |
BOXED_BYTE | BOXED_SHORT | BOXED_INT |
BOXED_FLOAT | BOXED_LONG | BOXED_DOUBLE
=> true
case _
=> false
}
}
/* On the JVM,
* BOOL, BYTE, CHAR, SHORT, and INT
* are like Ints for the purpose of lub calculation.
*
* can-multi-thread
*/
def isIntSizedType = {
(sort : @switch) match {
case BType.BOOLEAN | BType.CHAR |
BType.BYTE | BType.SHORT | BType.INT
=> true
case _
=> false
}
}
/* On the JVM, similar to isIntSizedType except that BOOL isn't integral while LONG is.
*
* can-multi-thread
*/
def isIntegralType = {
(sort : @switch) match {
case BType.CHAR |
BType.BYTE | BType.SHORT | BType.INT |
BType.LONG
=> true
case _
=> false
}
}
/* On the JVM, FLOAT and DOUBLE.
*
* can-multi-thread
*/
def isRealType = { (sort == BType.FLOAT ) || (sort == BType.DOUBLE) }
def isNumericType = (isIntegralType || isRealType) // can-multi-thread
/* Is this type a category 2 type in JVM terms? (ie, is it LONG or DOUBLE?)
*
* can-multi-thread
*/
def isWideType = (getSize == 2)
/*
* Element vs. Component type of an array:
* Quoting from the JVMS, Sec. 2.4 "Reference Types and Values"
*
* An array type consists of a component type with a single dimension (whose
* length is not given by the type). The component type of an array type may itself be
* an array type. If, starting from any array type, one considers its component type,
* and then (if that is also an array type) the component type of that type, and so on,
* eventually one must reach a component type that is not an array type; this is called
* the element type of the array type. The element type of an array type is necessarily
* either a primitive type, or a class type, or an interface type.
*
*/
/* The type of items this array holds.
*
* can-multi-thread
*/
def getComponentType: BType = {
assert(isArray, s"Asked for the component type of a non-array type: $this")
BType.getType(off + 1)
}
// ------------------------------------------------------------------------
// Conversion to type descriptors
// ------------------------------------------------------------------------
/*
* @return the descriptor corresponding to this Java type.
*
* can-multi-thread
*/
def getDescriptor: String = {
val buf = new StringBuffer()
getDescriptor(buf)
buf.toString()
}
/*
* Appends the descriptor corresponding to this Java type to the given string buffer.
*
* @param buf the string buffer to which the descriptor must be appended.
*
* can-multi-thread
*/
private def getDescriptor(buf: StringBuffer) {
if (isPrimitiveOrVoid) {
// descriptor is in byte 3 of 'off' for primitive types (buf == null)
buf.append(((off & 0xFF000000) >>> 24).asInstanceOf[Char])
} else if (sort == BType.OBJECT) {
buf.append('L')
buf.append(chrs, off, len)
buf.append(';')
} else { // sort == ARRAY || sort == METHOD
buf.append(chrs, off, len)
}
}
// ------------------------------------------------------------------------
// Corresponding size and opcodes
// ------------------------------------------------------------------------
/*
* Returns the size of values of this type.
* This method must not be used for method types.
*
* @return the size of values of this type, i.e., 2 for long and
* double, 0 for void and 1 otherwise.
*
* can-multi-thread
*/
def getSize: Int = {
// the size is in byte 0 of 'off' for primitive types (buf == null)
if (isPrimitiveOrVoid) (off & 0xFF) else 1
}
/*
* Returns a JVM instruction opcode adapted to this Java type. This method
* must not be used for method types.
*
* @param opcode a JVM instruction opcode. This opcode must be one of ILOAD,
* ISTORE, IALOAD, IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, ISHL,
* ISHR, IUSHR, IAND, IOR, IXOR and IRETURN.
* @return an opcode that is similar to the given opcode, but adapted to
* this Java type. For example, if this type is float and
* opcode is IRETURN, this method returns FRETURN.
*
* can-multi-thread
*/
def getOpcode(opcode: Int): Int = {
import scala.tools.asm.Opcodes
if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) {
// the offset for IALOAD or IASTORE is in byte 1 of 'off' for
// primitive types (buf == null)
opcode + (if (isPrimitiveOrVoid) (off & 0xFF00) >> 8 else 4)
} else {
// the offset for other instructions is in byte 2 of 'off' for
// primitive types (buf == null)
opcode + (if (isPrimitiveOrVoid) (off & 0xFF0000) >> 16 else 4)
}
}
// ------------------------------------------------------------------------
// Equals, hashCode and toString
// ------------------------------------------------------------------------
/*
* Tests if the given object is equal to this type.
*
* @param o the object to be compared to this type.
* @return true if the given object is equal to this type.
*
* can-multi-thread
*/
override def equals(o: Any): Boolean = {
if (!(o.isInstanceOf[BType])) {
return false
}
val t = o.asInstanceOf[BType]
if (this eq t) {
return true
}
if (sort != t.sort) {
return false
}
if (sort >= BType.ARRAY) {
if (len != t.len) {
return false
}
// sort checked already
if (off == t.off) {
return true
}
var i = 0
while (i < len) {
if (chrs(off + i) != chrs(t.off + i)) {
return false
}
i += 1
}
// If we reach here, we could update the largest of (this.off, t.off) to match the other, so as to simplify future == comparisons.
// But that would require a var rather than val.
}
true
}
/*
* @return a hash code value for this type.
*
* can-multi-thread
*/
override def hashCode(): Int = {
var hc = 13 * sort;
if (sort >= BType.ARRAY) {
var i = off
val end = i + len
while (i < end) {
hc = 17 * (hc + chrs(i))
i += 1
}
}
hc
}
/*
* @return the descriptor of this type.
*
* can-multi-thread
*/
override def toString: String = { getDescriptor }
}
/*
* Creates a TypeName and the BType token for it.
* This method does not add to `innerClassBufferASM`, use `internalName()` or `asmType()` or `toTypeKind()` for that.
*
* must-single-thread
*/
def brefType(iname: String): BType = { brefType(newTypeName(iname.toCharArray(), 0, iname.length())) }
/*
* Creates a BType token for the TypeName received as argument.
* This method does not add to `innerClassBufferASM`, use `internalName()` or `asmType()` or `toTypeKind()` for that.
*
* can-multi-thread
*/
def brefType(iname: TypeName): BType = { BType.getObjectType(iname.start, iname.length) }
// due to keyboard economy only
val UNIT = BType.VOID_TYPE
val BOOL = BType.BOOLEAN_TYPE
val CHAR = BType.CHAR_TYPE
val BYTE = BType.BYTE_TYPE
val SHORT = BType.SHORT_TYPE
val INT = BType.INT_TYPE
val LONG = BType.LONG_TYPE
val FLOAT = BType.FLOAT_TYPE
val DOUBLE = BType.DOUBLE_TYPE
val BOXED_UNIT = brefType("java/lang/Void")
val BOXED_BOOLEAN = brefType("java/lang/Boolean")
val BOXED_BYTE = brefType("java/lang/Byte")
val BOXED_SHORT = brefType("java/lang/Short")
val BOXED_CHAR = brefType("java/lang/Character")
val BOXED_INT = brefType("java/lang/Integer")
val BOXED_LONG = brefType("java/lang/Long")
val BOXED_FLOAT = brefType("java/lang/Float")
val BOXED_DOUBLE = brefType("java/lang/Double")
/*
* RT_NOTHING and RT_NULL exist at run-time only.
* They are the bytecode-level manifestation (in method signatures only) of what shows up as NothingClass resp. NullClass in Scala ASTs.
* Therefore, when RT_NOTHING or RT_NULL are to be emitted,
* a mapping is needed: the internal names of NothingClass and NullClass can't be emitted as-is.
*/
val RT_NOTHING = brefType("scala/runtime/Nothing$")
val RT_NULL = brefType("scala/runtime/Null$")
val CT_NOTHING = brefType("scala/Nothing") // TODO needed?
val CT_NULL = brefType("scala/Null") // TODO needed?
val srBooleanRef = brefType("scala/runtime/BooleanRef")
val srByteRef = brefType("scala/runtime/ByteRef")
val srCharRef = brefType("scala/runtime/CharRef")
val srIntRef = brefType("scala/runtime/IntRef")
val srLongRef = brefType("scala/runtime/LongRef")
val srFloatRef = brefType("scala/runtime/FloatRef")
val srDoubleRef = brefType("scala/runtime/DoubleRef")
/* Map from type kinds to the Java reference types.
* Useful when pushing class literals onto the operand stack (ldc instruction taking a class literal).
* @see Predef.classOf
* @see genConstant()
*/
val classLiteral = immutable.Map[BType, BType](
UNIT -> BOXED_UNIT,
BOOL -> BOXED_BOOLEAN,
BYTE -> BOXED_BYTE,
SHORT -> BOXED_SHORT,
CHAR -> BOXED_CHAR,
INT -> BOXED_INT,
LONG -> BOXED_LONG,
FLOAT -> BOXED_FLOAT,
DOUBLE -> BOXED_DOUBLE
)
case class MethodNameAndType(mname: String, mdesc: String)
val asmBoxTo: Map[BType, MethodNameAndType] = {
Map(
BOOL -> MethodNameAndType("boxToBoolean", "(Z)Ljava/lang/Boolean;" ) ,
BYTE -> MethodNameAndType("boxToByte", "(B)Ljava/lang/Byte;" ) ,
CHAR -> MethodNameAndType("boxToCharacter", "(C)Ljava/lang/Character;") ,
SHORT -> MethodNameAndType("boxToShort", "(S)Ljava/lang/Short;" ) ,
INT -> MethodNameAndType("boxToInteger", "(I)Ljava/lang/Integer;" ) ,
LONG -> MethodNameAndType("boxToLong", "(J)Ljava/lang/Long;" ) ,
FLOAT -> MethodNameAndType("boxToFloat", "(F)Ljava/lang/Float;" ) ,
DOUBLE -> MethodNameAndType("boxToDouble", "(D)Ljava/lang/Double;" )
)
}
val asmUnboxTo: Map[BType, MethodNameAndType] = {
Map(
BOOL -> MethodNameAndType("unboxToBoolean", "(Ljava/lang/Object;)Z") ,
BYTE -> MethodNameAndType("unboxToByte", "(Ljava/lang/Object;)B") ,
CHAR -> MethodNameAndType("unboxToChar", "(Ljava/lang/Object;)C") ,
SHORT -> MethodNameAndType("unboxToShort", "(Ljava/lang/Object;)S") ,
INT -> MethodNameAndType("unboxToInt", "(Ljava/lang/Object;)I") ,
LONG -> MethodNameAndType("unboxToLong", "(Ljava/lang/Object;)J") ,
FLOAT -> MethodNameAndType("unboxToFloat", "(Ljava/lang/Object;)F") ,
DOUBLE -> MethodNameAndType("unboxToDouble", "(Ljava/lang/Object;)D")
)
}
}