scala.tools.nsc.symtab.classfile.ICodeReader.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 Scala Programming Language
The newest version!
/* NSC -- new Scala compiler
* Copyright 2005-2013 LAMP/EPFL
* @author Iulian Dragos
*/
package scala
package tools.nsc
package symtab
package classfile
import scala.collection.{ mutable, immutable }
import mutable.ListBuffer
import ClassfileConstants._
import scala.reflect.internal.JavaAccFlags
/** ICode reader from Java bytecode.
*
* @author Iulian Dragos
* @version 1.0
*/
abstract class ICodeReader extends ClassfileParser {
val global: Global
val symbolTable: global.type
val loaders: global.loaders.type
import global._
import icodes._
var instanceCode: IClass = null // the ICode class for the current symbol
var staticCode: IClass = null // the ICode class static members
var method: IMethod = NoIMethod // the current IMethod
var isScalaModule = false
override protected type ThisConstantPool = ICodeConstantPool
override protected def newConstantPool = new ICodeConstantPool
/** Try to force the chain of enclosing classes for the given name. Otherwise
* flatten would not lift classes that were not referenced in the source code.
*/
def forceMangledName(name: Name, module: Boolean): Symbol = {
val parts = name.decode.toString.split(Array('.', '$'))
var sym: Symbol = rootMirror.RootClass
// was "at flatten.prev"
enteringFlatten {
for (part0 <- parts; if !(part0 == ""); part = newTermName(part0)) {
val sym1 = enteringIcode {
sym.linkedClassOfClass.info
sym.info.decl(part.encode)
}//.suchThat(module == _.isModule)
sym = sym1 orElse sym.info.decl(part.encode.toTypeName)
}
}
sym
}
protected class ICodeConstantPool extends ConstantPool {
/** Return the symbol of the class member at `index`.
* The following special cases exist:
* - If the member refers to special `MODULE$` static field, return
* the symbol of the corresponding module.
* - If the member is a field, and is not found with the given name,
* another try is made by appending `nme.LOCAL_SUFFIX_STRING`
* - If no symbol is found in the right tpe, a new try is made in the
* companion class, in case the owner is an implementation class.
*/
def getMemberSymbol(index: Int, static: Boolean): Symbol = {
if (index <= 0 || len <= index) errorBadIndex(index)
var f = values(index).asInstanceOf[Symbol]
if (f eq null) {
val start = starts(index)
val first = in.buf(start).toInt
if (first != CONSTANT_FIELDREF &&
first != CONSTANT_METHODREF &&
first != CONSTANT_INTFMETHODREF) errorBadTag(start)
val ownerTpe = getClassOrArrayType(in.getChar(start + 1).toInt)
debuglog("getMemberSymbol(static: " + static + "): owner type: " + ownerTpe + " " + ownerTpe.typeSymbol.originalName)
val (name0, tpe0) = getNameAndType(in.getChar(start + 3).toInt, ownerTpe)
debuglog("getMemberSymbol: name and tpe: " + name0 + ": " + tpe0)
forceMangledName(tpe0.typeSymbol.name, module = false)
val (name, tpe) = getNameAndType(in.getChar(start + 3).toInt, ownerTpe)
if (name == nme.MODULE_INSTANCE_FIELD) {
val index = in.getChar(start + 1).toInt
val name = getExternalName(in.getChar(starts(index).toInt + 1).toInt)
//assert(name.endsWith("$"), "Not a module class: " + name)
f = forceMangledName(name dropRight 1, module = true)
if (f == NoSymbol)
f = rootMirror.getModuleByName(name dropRight 1)
} else {
val origName = nme.unexpandedName(name)
val owner = if (static) ownerTpe.typeSymbol.linkedClassOfClass else ownerTpe.typeSymbol
f = owner.info.findMember(origName, 0, 0, stableOnly = false).suchThat(_.tpe.widen =:= tpe)
if (f == NoSymbol)
f = owner.info.findMember(newTermName(origName + nme.LOCAL_SUFFIX_STRING), 0, 0, stableOnly = false).suchThat(_.tpe =:= tpe)
if (f == NoSymbol) {
// if it's an impl class, try to find it's static member inside the class
if (ownerTpe.typeSymbol.isImplClass) {
f = ownerTpe.findMember(origName, 0, 0, stableOnly = false).suchThat(_.tpe =:= tpe)
} else {
log("Couldn't find " + name + ": " + tpe + " inside: \n" + ownerTpe)
f = tpe match {
case MethodType(_, _) => owner.newMethod(name.toTermName, owner.pos)
case _ => owner.newVariable(name.toTermName, owner.pos)
}
f setInfo tpe
log("created fake member " + f.fullName)
}
}
}
assert(f != NoSymbol,
s"could not find $name: $tpe in $ownerTpe" + (
if (settings.debug.value) ownerTpe.members.mkString(", members are:\n ", "\n ", "") else ""
)
)
values(index) = f
}
f
}
}
/** Read back bytecode for the given class symbol. It returns
* two IClass objects, one for static members and one
* for non-static members.
*/
def readClass(cls: Symbol): (IClass, IClass) = {
cls.info // ensure accurate type information
isScalaModule = cls.isModule && !cls.isJavaDefined
log("ICodeReader reading " + cls)
val name = cls.javaClassName
classPath.findSourceFile(name) match {
case Some(classFile) => parse(classFile, cls)
case _ => MissingRequirementError.notFound("Could not find bytecode for " + cls)
}
(staticCode, instanceCode)
}
override def parseClass() {
this.instanceCode = new IClass(clazz)
this.staticCode = new IClass(staticModule)
u2
pool getClassSymbol u2
parseInnerClasses()
in.skip(2) // super class
in.skip(2 * u2) // interfaces
val fieldCount = u2
for (i <- 0 until fieldCount) parseField()
val methodCount = u2
for (i <- 0 until methodCount) parseMethod()
instanceCode.methods = instanceCode.methods.reverse
staticCode.methods = staticCode.methods.reverse
}
override def parseField() {
val (jflags, sym) = parseMember(field = true)
getCode(jflags) addField new IField(sym)
skipAttributes()
}
private def parseMember(field: Boolean): (JavaAccFlags, Symbol) = {
val jflags = JavaAccFlags(u2)
val name = pool getName u2
/* If we're parsing a scala module, the owner of members is always
* the module symbol.
*/
val owner = (
if (isScalaModule) staticModule
else if (jflags.isStatic) moduleClass
else clazz
)
val dummySym = owner.newMethod(name.toTermName, owner.pos, jflags.toScalaFlags)
try {
val ch = u2
val tpe = pool.getType(dummySym, ch)
if ("" == name.toString)
(jflags, NoSymbol)
else {
var sym = owner.info.findMember(name, 0, 0, stableOnly = false).suchThat(old => sameType(old.tpe, tpe))
if (sym == NoSymbol)
sym = owner.info.findMember(newTermName(name + nme.LOCAL_SUFFIX_STRING), 0, 0, stableOnly = false).suchThat(_.tpe =:= tpe)
if (sym == NoSymbol) {
sym = if (field) owner.newValue(name.toTermName, owner.pos, jflags.toScalaFlags) else dummySym
sym setInfoAndEnter tpe
log(s"ICodeReader could not locate ${name.decode} in $owner. Created ${sym.defString}.")
}
(jflags, sym)
}
} catch {
case e: MissingRequirementError =>
(jflags, NoSymbol)
}
}
/** Checks if `tp1` is the same type as `tp2`, modulo implicit methods.
* We don't care about the distinction between implicit and explicit
* methods as this point, and we can't get back the information from
* bytecode anyway.
*/
private def sameType(tp1: Type, tp2: Type): Boolean = (tp1, tp2) match {
case (mt1 @ MethodType(args1, resTpe1), mt2 @ MethodType(args2, resTpe2)) if mt1.isImplicit || mt2.isImplicit =>
MethodType(args1, resTpe1) =:= MethodType(args2, resTpe2)
case _ =>
tp1 =:= tp2
}
override def parseMethod() {
val (jflags, sym) = parseMember(field = false)
val beginning = in.bp
try {
if (sym != NoSymbol) {
this.method = new IMethod(sym)
this.method.returnType = toTypeKind(sym.tpe.resultType)
getCode(jflags).addMethod(this.method)
if (jflags.isNative)
this.method.native = true
val attributeCount = u2
for (i <- 0 until attributeCount) parseAttribute()
} else {
debuglog("Skipping non-existent method.")
skipAttributes()
}
} catch {
case e: MissingRequirementError =>
in.bp = beginning; skipAttributes()
debuglog("Skipping non-existent method. " + e.msg)
}
}
def parseAttribute() {
val attrName = pool.getName(u2).toTypeName
val attrLen = u4
attrName match {
case tpnme.CodeATTR =>
parseByteCode()
case _ =>
in.skip(attrLen)
}
}
override def classNameToSymbol(name: Name) = {
val sym = if (name == fulltpnme.RuntimeNothing)
definitions.NothingClass
else if (name == fulltpnme.RuntimeNull)
definitions.NullClass
else if (nme.isImplClassName(name)) {
val iface = rootMirror.getClassByName(tpnme.interfaceName(name))
log("forcing " + iface.owner + " at phase: " + phase + " impl: " + iface.implClass)
iface.owner.info // force the mixin type-transformer
rootMirror.getClassByName(name)
}
else if (nme.isModuleName(name)) {
val strippedName = name.dropModule
forceMangledName(newTermName(strippedName.decode), module = true) orElse rootMirror.getModuleByName(strippedName)
}
else {
forceMangledName(name, module = false)
exitingFlatten(rootMirror.getClassByName(name.toTypeName))
}
if (sym.isModule)
sym.moduleClass
else
sym
}
var maxStack: Int = _
var maxLocals: Int = _
val JVM = ClassfileConstants // shorter, uppercase alias for use in case patterns
def toUnsignedByte(b: Byte): Int = b.toInt & 0xff
var pc = 0
/** Parse java bytecode into ICode */
def parseByteCode() {
maxStack = u2
maxLocals = u2
val codeLength = u4
val code = new LinearCode
def parseInstruction() {
import opcodes._
import code._
var size = 1 // instruction size
/* Parse 16 bit jump target. */
def parseJumpTarget = {
size += 2
val offset = u2.toShort
val target = pc + offset
assert(target >= 0 && target < codeLength, "Illegal jump target: " + target)
target
}
/* Parse 32 bit jump target. */
def parseJumpTargetW: Int = {
size += 4
val offset = u4
val target = pc + offset
assert(target >= 0 && target < codeLength, "Illegal jump target: " + target + "pc: " + pc + " offset: " + offset)
target
}
u1 match {
case JVM.nop => parseInstruction()
case JVM.aconst_null => code emit CONSTANT(Constant(null))
case JVM.iconst_m1 => code emit CONSTANT(Constant(-1))
case JVM.iconst_0 => code emit CONSTANT(Constant(0))
case JVM.iconst_1 => code emit CONSTANT(Constant(1))
case JVM.iconst_2 => code emit CONSTANT(Constant(2))
case JVM.iconst_3 => code emit CONSTANT(Constant(3))
case JVM.iconst_4 => code emit CONSTANT(Constant(4))
case JVM.iconst_5 => code emit CONSTANT(Constant(5))
case JVM.lconst_0 => code emit CONSTANT(Constant(0l))
case JVM.lconst_1 => code emit CONSTANT(Constant(1l))
case JVM.fconst_0 => code emit CONSTANT(Constant(0.0f))
case JVM.fconst_1 => code emit CONSTANT(Constant(1.0f))
case JVM.fconst_2 => code emit CONSTANT(Constant(2.0f))
case JVM.dconst_0 => code emit CONSTANT(Constant(0.0))
case JVM.dconst_1 => code emit CONSTANT(Constant(1.0))
case JVM.bipush => code.emit(CONSTANT(Constant(u1))); size += 1
case JVM.sipush => code.emit(CONSTANT(Constant(u2))); size += 2
case JVM.ldc => code.emit(CONSTANT(pool.getConstant(u1))); size += 1
case JVM.ldc_w => code.emit(CONSTANT(pool.getConstant(u2))); size += 2
case JVM.ldc2_w => code.emit(CONSTANT(pool.getConstant(u2))); size += 2
case JVM.iload => code.emit(LOAD_LOCAL(code.getLocal(u1, INT))); size += 1
case JVM.lload => code.emit(LOAD_LOCAL(code.getLocal(u1, LONG))); size += 1
case JVM.fload => code.emit(LOAD_LOCAL(code.getLocal(u1, FLOAT))); size += 1
case JVM.dload => code.emit(LOAD_LOCAL(code.getLocal(u1, DOUBLE))); size += 1
case JVM.aload =>
val local = u1.toInt; size += 1
if (local == 0 && !method.isStatic)
code.emit(THIS(method.symbol.owner))
else
code.emit(LOAD_LOCAL(code.getLocal(local, ObjectReference)))
case JVM.iload_0 => code.emit(LOAD_LOCAL(code.getLocal(0, INT)))
case JVM.iload_1 => code.emit(LOAD_LOCAL(code.getLocal(1, INT)))
case JVM.iload_2 => code.emit(LOAD_LOCAL(code.getLocal(2, INT)))
case JVM.iload_3 => code.emit(LOAD_LOCAL(code.getLocal(3, INT)))
case JVM.lload_0 => code.emit(LOAD_LOCAL(code.getLocal(0, LONG)))
case JVM.lload_1 => code.emit(LOAD_LOCAL(code.getLocal(1, LONG)))
case JVM.lload_2 => code.emit(LOAD_LOCAL(code.getLocal(2, LONG)))
case JVM.lload_3 => code.emit(LOAD_LOCAL(code.getLocal(3, LONG)))
case JVM.fload_0 => code.emit(LOAD_LOCAL(code.getLocal(0, FLOAT)))
case JVM.fload_1 => code.emit(LOAD_LOCAL(code.getLocal(1, FLOAT)))
case JVM.fload_2 => code.emit(LOAD_LOCAL(code.getLocal(2, FLOAT)))
case JVM.fload_3 => code.emit(LOAD_LOCAL(code.getLocal(3, FLOAT)))
case JVM.dload_0 => code.emit(LOAD_LOCAL(code.getLocal(0, DOUBLE)))
case JVM.dload_1 => code.emit(LOAD_LOCAL(code.getLocal(1, DOUBLE)))
case JVM.dload_2 => code.emit(LOAD_LOCAL(code.getLocal(2, DOUBLE)))
case JVM.dload_3 => code.emit(LOAD_LOCAL(code.getLocal(3, DOUBLE)))
case JVM.aload_0 =>
if (!method.isStatic)
code.emit(THIS(method.symbol.owner))
else
code.emit(LOAD_LOCAL(code.getLocal(0, ObjectReference)))
case JVM.aload_1 => code.emit(LOAD_LOCAL(code.getLocal(1, ObjectReference)))
case JVM.aload_2 => code.emit(LOAD_LOCAL(code.getLocal(2, ObjectReference)))
case JVM.aload_3 => code.emit(LOAD_LOCAL(code.getLocal(3, ObjectReference)))
case JVM.iaload => code.emit(LOAD_ARRAY_ITEM(INT))
case JVM.laload => code.emit(LOAD_ARRAY_ITEM(LONG))
case JVM.faload => code.emit(LOAD_ARRAY_ITEM(FLOAT))
case JVM.daload => code.emit(LOAD_ARRAY_ITEM(DOUBLE))
case JVM.aaload => code.emit(LOAD_ARRAY_ITEM(ObjectReference))
case JVM.baload => code.emit(LOAD_ARRAY_ITEM(BYTE))
case JVM.caload => code.emit(LOAD_ARRAY_ITEM(CHAR))
case JVM.saload => code.emit(LOAD_ARRAY_ITEM(SHORT))
case JVM.istore => code.emit(STORE_LOCAL(code.getLocal(u1, INT))); size += 1
case JVM.lstore => code.emit(STORE_LOCAL(code.getLocal(u1, LONG))); size += 1
case JVM.fstore => code.emit(STORE_LOCAL(code.getLocal(u1, FLOAT))); size += 1
case JVM.dstore => code.emit(STORE_LOCAL(code.getLocal(u1, DOUBLE))); size += 1
case JVM.astore => code.emit(STORE_LOCAL(code.getLocal(u1, ObjectReference))); size += 1
case JVM.istore_0 => code.emit(STORE_LOCAL(code.getLocal(0, INT)))
case JVM.istore_1 => code.emit(STORE_LOCAL(code.getLocal(1, INT)))
case JVM.istore_2 => code.emit(STORE_LOCAL(code.getLocal(2, INT)))
case JVM.istore_3 => code.emit(STORE_LOCAL(code.getLocal(3, INT)))
case JVM.lstore_0 => code.emit(STORE_LOCAL(code.getLocal(0, LONG)))
case JVM.lstore_1 => code.emit(STORE_LOCAL(code.getLocal(1, LONG)))
case JVM.lstore_2 => code.emit(STORE_LOCAL(code.getLocal(2, LONG)))
case JVM.lstore_3 => code.emit(STORE_LOCAL(code.getLocal(3, LONG)))
case JVM.fstore_0 => code.emit(STORE_LOCAL(code.getLocal(0, FLOAT)))
case JVM.fstore_1 => code.emit(STORE_LOCAL(code.getLocal(1, FLOAT)))
case JVM.fstore_2 => code.emit(STORE_LOCAL(code.getLocal(2, FLOAT)))
case JVM.fstore_3 => code.emit(STORE_LOCAL(code.getLocal(3, FLOAT)))
case JVM.dstore_0 => code.emit(STORE_LOCAL(code.getLocal(0, DOUBLE)))
case JVM.dstore_1 => code.emit(STORE_LOCAL(code.getLocal(1, DOUBLE)))
case JVM.dstore_2 => code.emit(STORE_LOCAL(code.getLocal(2, DOUBLE)))
case JVM.dstore_3 => code.emit(STORE_LOCAL(code.getLocal(3, DOUBLE)))
case JVM.astore_0 =>
if (method.isStatic)
code.emit(STORE_LOCAL(code.getLocal(0, ObjectReference)))
else
code.emit(STORE_THIS(ObjectReference))
case JVM.astore_1 => code.emit(STORE_LOCAL(code.getLocal(1, ObjectReference)))
case JVM.astore_2 => code.emit(STORE_LOCAL(code.getLocal(2, ObjectReference)))
case JVM.astore_3 => code.emit(STORE_LOCAL(code.getLocal(3, ObjectReference)))
case JVM.iastore => code.emit(STORE_ARRAY_ITEM(INT))
case JVM.lastore => code.emit(STORE_ARRAY_ITEM(LONG))
case JVM.fastore => code.emit(STORE_ARRAY_ITEM(FLOAT))
case JVM.dastore => code.emit(STORE_ARRAY_ITEM(DOUBLE))
case JVM.aastore => code.emit(STORE_ARRAY_ITEM(ObjectReference))
case JVM.bastore => code.emit(STORE_ARRAY_ITEM(BYTE))
case JVM.castore => code.emit(STORE_ARRAY_ITEM(CHAR))
case JVM.sastore => code.emit(STORE_ARRAY_ITEM(SHORT))
case JVM.pop => code.emit(DROP(INT)) // any 1-word type would do
case JVM.pop2 => code.emit(DROP(LONG)) // any 2-word type would do
case JVM.dup => code.emit(DUP(ObjectReference)) // TODO: Is the kind inside DUP ever needed?
case JVM.dup_x1 => code.emit(DUP_X1) // sys.error("Unsupported JVM bytecode: dup_x1")
case JVM.dup_x2 => code.emit(DUP_X2) // sys.error("Unsupported JVM bytecode: dup_x2")
case JVM.dup2 => code.emit(DUP(LONG)) // TODO: Is the kind inside DUP ever needed?
case JVM.dup2_x1 => code.emit(DUP2_X1) // sys.error("Unsupported JVM bytecode: dup2_x1")
case JVM.dup2_x2 => code.emit(DUP2_X2) // sys.error("Unsupported JVM bytecode: dup2_x2")
case JVM.swap => sys.error("Unsupported JVM bytecode: swap")
case JVM.iadd => code.emit(CALL_PRIMITIVE(Arithmetic(ADD, INT)))
case JVM.ladd => code.emit(CALL_PRIMITIVE(Arithmetic(ADD, LONG)))
case JVM.fadd => code.emit(CALL_PRIMITIVE(Arithmetic(ADD, FLOAT)))
case JVM.dadd => code.emit(CALL_PRIMITIVE(Arithmetic(ADD, DOUBLE)))
case JVM.isub => code.emit(CALL_PRIMITIVE(Arithmetic(SUB, INT)))
case JVM.lsub => code.emit(CALL_PRIMITIVE(Arithmetic(SUB, LONG)))
case JVM.fsub => code.emit(CALL_PRIMITIVE(Arithmetic(SUB, FLOAT)))
case JVM.dsub => code.emit(CALL_PRIMITIVE(Arithmetic(SUB, DOUBLE)))
case JVM.imul => code.emit(CALL_PRIMITIVE(Arithmetic(MUL, INT)))
case JVM.lmul => code.emit(CALL_PRIMITIVE(Arithmetic(MUL, LONG)))
case JVM.fmul => code.emit(CALL_PRIMITIVE(Arithmetic(MUL, FLOAT)))
case JVM.dmul => code.emit(CALL_PRIMITIVE(Arithmetic(MUL, DOUBLE)))
case JVM.idiv => code.emit(CALL_PRIMITIVE(Arithmetic(DIV, INT)))
case JVM.ldiv => code.emit(CALL_PRIMITIVE(Arithmetic(DIV, LONG)))
case JVM.fdiv => code.emit(CALL_PRIMITIVE(Arithmetic(DIV, FLOAT)))
case JVM.ddiv => code.emit(CALL_PRIMITIVE(Arithmetic(DIV, DOUBLE)))
case JVM.irem => code.emit(CALL_PRIMITIVE(Arithmetic(REM, INT)))
case JVM.lrem => code.emit(CALL_PRIMITIVE(Arithmetic(REM, LONG)))
case JVM.frem => code.emit(CALL_PRIMITIVE(Arithmetic(REM, FLOAT)))
case JVM.drem => code.emit(CALL_PRIMITIVE(Arithmetic(REM, DOUBLE)))
case JVM.ineg => code.emit(CALL_PRIMITIVE(Negation(INT)))
case JVM.lneg => code.emit(CALL_PRIMITIVE(Negation(LONG)))
case JVM.fneg => code.emit(CALL_PRIMITIVE(Negation(FLOAT)))
case JVM.dneg => code.emit(CALL_PRIMITIVE(Negation(DOUBLE)))
case JVM.ishl => code.emit(CALL_PRIMITIVE(Shift(LSL, INT)))
case JVM.lshl => code.emit(CALL_PRIMITIVE(Shift(LSL, LONG)))
case JVM.ishr => code.emit(CALL_PRIMITIVE(Shift(LSR, INT)))
case JVM.lshr => code.emit(CALL_PRIMITIVE(Shift(LSR, LONG)))
case JVM.iushr => code.emit(CALL_PRIMITIVE(Shift(ASR, INT)))
case JVM.lushr => code.emit(CALL_PRIMITIVE(Shift(ASR, LONG)))
case JVM.iand => code.emit(CALL_PRIMITIVE(Logical(AND, INT)))
case JVM.land => code.emit(CALL_PRIMITIVE(Logical(AND, LONG)))
case JVM.ior => code.emit(CALL_PRIMITIVE(Logical(OR, INT)))
case JVM.lor => code.emit(CALL_PRIMITIVE(Logical(OR, LONG)))
case JVM.ixor => code.emit(CALL_PRIMITIVE(Logical(XOR, INT)))
case JVM.lxor => code.emit(CALL_PRIMITIVE(Logical(XOR, LONG)))
case JVM.iinc =>
size += 2
val local = code.getLocal(u1, INT)
code.emit(LOAD_LOCAL(local))
code.emit(CONSTANT(Constant(u1)))
code.emit(CALL_PRIMITIVE(Arithmetic(ADD, INT)))
code.emit(STORE_LOCAL(local))
case JVM.i2l => code.emit(CALL_PRIMITIVE(Conversion(INT, LONG)))
case JVM.i2f => code.emit(CALL_PRIMITIVE(Conversion(INT, FLOAT)))
case JVM.i2d => code.emit(CALL_PRIMITIVE(Conversion(INT, DOUBLE)))
case JVM.l2i => code.emit(CALL_PRIMITIVE(Conversion(LONG, INT)))
case JVM.l2f => code.emit(CALL_PRIMITIVE(Conversion(LONG, FLOAT)))
case JVM.l2d => code.emit(CALL_PRIMITIVE(Conversion(LONG, DOUBLE)))
case JVM.f2i => code.emit(CALL_PRIMITIVE(Conversion(FLOAT, INT)))
case JVM.f2l => code.emit(CALL_PRIMITIVE(Conversion(FLOAT, LONG)))
case JVM.f2d => code.emit(CALL_PRIMITIVE(Conversion(FLOAT, DOUBLE)))
case JVM.d2i => code.emit(CALL_PRIMITIVE(Conversion(DOUBLE, INT)))
case JVM.d2l => code.emit(CALL_PRIMITIVE(Conversion(DOUBLE, LONG)))
case JVM.d2f => code.emit(CALL_PRIMITIVE(Conversion(DOUBLE, FLOAT)))
case JVM.i2b => code.emit(CALL_PRIMITIVE(Conversion(INT, BYTE)))
case JVM.i2c => code.emit(CALL_PRIMITIVE(Conversion(INT, CHAR)))
case JVM.i2s => code.emit(CALL_PRIMITIVE(Conversion(INT, SHORT)))
case JVM.lcmp => code.emit(CALL_PRIMITIVE(Comparison(CMP, LONG)))
case JVM.fcmpl => code.emit(CALL_PRIMITIVE(Comparison(CMPL, FLOAT)))
case JVM.fcmpg => code.emit(CALL_PRIMITIVE(Comparison(CMPG, FLOAT)))
case JVM.dcmpl => code.emit(CALL_PRIMITIVE(Comparison(CMPL, DOUBLE)))
case JVM.dcmpg => code.emit(CALL_PRIMITIVE(Comparison(CMPG, DOUBLE)))
case JVM.ifeq => code.emit(LCZJUMP(parseJumpTarget, pc + size, EQ, INT))
case JVM.ifne => code.emit(LCZJUMP(parseJumpTarget, pc + size, NE, INT))
case JVM.iflt => code.emit(LCZJUMP(parseJumpTarget, pc + size, LT, INT))
case JVM.ifge => code.emit(LCZJUMP(parseJumpTarget, pc + size, GE, INT))
case JVM.ifgt => code.emit(LCZJUMP(parseJumpTarget, pc + size, GT, INT))
case JVM.ifle => code.emit(LCZJUMP(parseJumpTarget, pc + size, LE, INT))
case JVM.if_icmpeq => code.emit(LCJUMP(parseJumpTarget, pc + size, EQ, INT))
case JVM.if_icmpne => code.emit(LCJUMP(parseJumpTarget, pc + size, NE, INT))
case JVM.if_icmplt => code.emit(LCJUMP(parseJumpTarget, pc + size, LT, INT))
case JVM.if_icmpge => code.emit(LCJUMP(parseJumpTarget, pc + size, GE, INT))
case JVM.if_icmpgt => code.emit(LCJUMP(parseJumpTarget, pc + size, GT, INT))
case JVM.if_icmple => code.emit(LCJUMP(parseJumpTarget, pc + size, LE, INT))
case JVM.if_acmpeq => code.emit(LCJUMP(parseJumpTarget, pc + size, EQ, ObjectReference))
case JVM.if_acmpne => code.emit(LCJUMP(parseJumpTarget, pc + size, NE, ObjectReference))
case JVM.goto => emit(LJUMP(parseJumpTarget))
case JVM.jsr => sys.error("Cannot handle jsr/ret")
case JVM.ret => sys.error("Cannot handle jsr/ret")
case JVM.tableswitch =>
val padding = if ((pc + size) % 4 != 0) 4 - ((pc + size) % 4) else 0
size += padding
in.bp += padding
assert((pc + size % 4) != 0, pc)
/* var byte1 = u1; size += 1;
while (byte1 == 0) { byte1 = u1; size += 1; }
val default = byte1 << 24 | u1 << 16 | u1 << 8 | u1;
size = size + 3
*/
val default = pc + u4; size += 4
val low = u4
val high = u4
size += 8
assert(low <= high, "Value low not <= high for tableswitch.")
val tags = List.tabulate(high - low + 1)(n => List(low + n))
val targets = for (_ <- tags) yield parseJumpTargetW
code.emit(LSWITCH(tags, targets ::: List(default)))
case JVM.lookupswitch =>
val padding = if ((pc + size) % 4 != 0) 4 - ((pc + size) % 4) else 0
size += padding
in.bp += padding
assert((pc + size % 4) != 0, pc)
val default = pc + u4; size += 4
val npairs = u4; size += 4
var tags: List[List[Int]] = Nil
var targets: List[Int] = Nil
var i = 0
while (i < npairs) {
tags = List(u4) :: tags; size += 4
targets = parseJumpTargetW :: targets; // parseJumpTargetW updates 'size' itself
i += 1
}
targets = default :: targets
code.emit(LSWITCH(tags.reverse, targets.reverse))
case JVM.ireturn => code.emit(RETURN(INT))
case JVM.lreturn => code.emit(RETURN(LONG))
case JVM.freturn => code.emit(RETURN(FLOAT))
case JVM.dreturn => code.emit(RETURN(DOUBLE))
case JVM.areturn => code.emit(RETURN(ObjectReference))
case JVM.return_ => code.emit(RETURN(UNIT))
case JVM.getstatic =>
val field = pool.getMemberSymbol(u2, static = true); size += 2
if (field.hasModuleFlag)
code emit LOAD_MODULE(field)
else
code emit LOAD_FIELD(field, isStatic = true)
case JVM.putstatic =>
val field = pool.getMemberSymbol(u2, static = true); size += 2
code.emit(STORE_FIELD(field, isStatic = true))
case JVM.getfield =>
val field = pool.getMemberSymbol(u2, static = false); size += 2
code.emit(LOAD_FIELD(field, isStatic = false))
case JVM.putfield =>
val field = pool.getMemberSymbol(u2, static = false); size += 2
code.emit(STORE_FIELD(field, isStatic = false))
case JVM.invokevirtual =>
val m = pool.getMemberSymbol(u2, static = false); size += 2
code.emit(CALL_METHOD(m, Dynamic))
method.updateRecursive(m)
case JVM.invokeinterface =>
val m = pool.getMemberSymbol(u2, static = false); size += 4
in.skip(2)
code.emit(CALL_METHOD(m, Dynamic))
// invokeinterface can't be recursive
case JVM.invokespecial =>
val m = pool.getMemberSymbol(u2, static = false); size += 2
val style = if (m.name == nme.CONSTRUCTOR || m.isPrivate) Static(onInstance = true)
else SuperCall(m.owner.name)
code.emit(CALL_METHOD(m, style))
method.updateRecursive(m)
case JVM.invokestatic =>
val m = pool.getMemberSymbol(u2, static = true); size += 2
if (isBox(m))
code.emit(BOX(toTypeKind(m.info.paramTypes.head)))
else if (isUnbox(m))
code.emit(UNBOX(toTypeKind(m.info.resultType)))
else {
code.emit(CALL_METHOD(m, Static(onInstance = false)))
method.updateRecursive(m)
}
case JVM.invokedynamic =>
// TODO, this is just a place holder. A real implementation must parse the class constant entry
debuglog("Found JVM invokedynamic instructionm, inserting place holder ICode INVOKE_DYNAMIC.")
containsInvokeDynamic = true
val poolEntry = in.nextChar.toInt
in.skip(2)
code.emit(INVOKE_DYNAMIC(poolEntry))
case JVM.new_ =>
code.emit(NEW(REFERENCE(pool.getClassSymbol(u2))))
size += 2
case JVM.newarray =>
val kind = u1 match {
case T_BOOLEAN => BOOL
case T_CHAR => CHAR
case T_FLOAT => FLOAT
case T_DOUBLE => DOUBLE
case T_BYTE => BYTE
case T_SHORT => SHORT
case T_INT => INT
case T_LONG => LONG
}
size += 1
code.emit(CREATE_ARRAY(kind, 1))
case JVM.anewarray =>
val tpe = pool.getClassOrArrayType(u2); size += 2
code.emit(CREATE_ARRAY(toTypeKind(tpe), 1))
case JVM.arraylength => code.emit(CALL_PRIMITIVE(ArrayLength(ObjectReference))); // the kind does not matter
case JVM.athrow => code.emit(THROW(definitions.ThrowableClass))
case JVM.checkcast =>
code.emit(CHECK_CAST(toTypeKind(pool.getClassOrArrayType(u2)))); size += 2
case JVM.instanceof =>
code.emit(IS_INSTANCE(toTypeKind(pool.getClassOrArrayType(u2)))); size += 2
case JVM.monitorenter => code.emit(MONITOR_ENTER())
case JVM.monitorexit => code.emit(MONITOR_EXIT())
case JVM.wide =>
size += 1
u1 match {
case JVM.iload => code.emit(LOAD_LOCAL(code.getLocal(u2, INT))); size += 2
case JVM.lload => code.emit(LOAD_LOCAL(code.getLocal(u2, LONG))); size += 2
case JVM.fload => code.emit(LOAD_LOCAL(code.getLocal(u2, FLOAT))); size += 2
case JVM.dload => code.emit(LOAD_LOCAL(code.getLocal(u2, DOUBLE))); size += 2
case JVM.aload => code.emit(LOAD_LOCAL(code.getLocal(u2, ObjectReference))); size += 2
case JVM.istore => code.emit(STORE_LOCAL(code.getLocal(u2, INT))); size += 2
case JVM.lstore => code.emit(STORE_LOCAL(code.getLocal(u2, LONG))); size += 2
case JVM.fstore => code.emit(STORE_LOCAL(code.getLocal(u2, FLOAT))); size += 2
case JVM.dstore => code.emit(STORE_LOCAL(code.getLocal(u2, DOUBLE))); size += 2
case JVM.astore => code.emit(STORE_LOCAL(code.getLocal(u2, ObjectReference))); size += 2
case JVM.ret => sys.error("Cannot handle jsr/ret")
case JVM.iinc =>
size += 4
val local = code.getLocal(u2, INT)
code.emit(CONSTANT(Constant(u2)))
code.emit(CALL_PRIMITIVE(Arithmetic(ADD, INT)))
code.emit(STORE_LOCAL(local))
case _ => sys.error("Invalid 'wide' operand")
}
case JVM.multianewarray =>
size += 3
val tpe = toTypeKind(pool getClassOrArrayType u2)
val dim = u1
// assert(dim == 1, "Cannot handle multidimensional arrays yet.")
code emit CREATE_ARRAY(tpe, dim)
case JVM.ifnull => code emit LCZJUMP(parseJumpTarget, pc + size, EQ, ObjectReference)
case JVM.ifnonnull => code emit LCZJUMP(parseJumpTarget, pc + size, NE, ObjectReference)
case JVM.goto_w => code emit LJUMP(parseJumpTargetW)
case JVM.jsr_w => sys.error("Cannot handle jsr/ret")
// case _ => sys.error("Unknown bytecode")
}
pc += size
}
// add parameters
var idx = if (method.isStatic) 0 else 1
for (t <- method.symbol.tpe.paramTypes) {
val kind = toTypeKind(t)
this.method addParam code.enterParam(idx, kind)
val width = if (kind.isWideType) 2 else 1
idx += width
}
pc = 0
while (pc < codeLength) parseInstruction()
val exceptionEntries = u2.toInt
code.containsEHs = (exceptionEntries != 0)
var i = 0
while (i < exceptionEntries) {
// skip start end PC
in.skip(4)
// read the handler PC
code.jmpTargets += u2
// skip the exception type
in.skip(2)
i += 1
}
skipAttributes()
code.toBasicBlock
assert(method.hasCode, method)
// reverse parameters, as they were prepended during code generation
method.params = method.params.reverse
if (code.containsDUPX)
code.resolveDups()
if (code.containsNEW)
code.resolveNEWs()
}
/** Note: these methods are different from the methods of the same name found
* in Definitions. These test whether a symbol represents one of the boxTo/unboxTo
* methods found in BoxesRunTime. The others test whether a symbol represents a
* synthetic method from one of the fake companion classes of the primitive types,
* such as Int.box(5).
*/
def isBox(m: Symbol): Boolean =
(m.owner == definitions.BoxesRunTimeClass
&& m.name.startsWith("boxTo"))
def isUnbox(m: Symbol): Boolean =
(m.owner == definitions.BoxesRunTimeClass
&& m.name.startsWith("unboxTo"))
/** Return the icode class that should include members with the given flags.
* There are two possible classes, the static part and the instance part.
*/
def getCode(flags: JavaAccFlags): IClass =
if (isScalaModule || flags.isStatic) staticCode else instanceCode
class LinearCode {
val instrs: ListBuffer[(Int, Instruction)] = new ListBuffer
val jmpTargets: mutable.Set[Int] = perRunCaches.newSet[Int]()
val locals: mutable.Map[Int, List[(Local, TypeKind)]] = perRunCaches.newMap()
var containsDUPX = false
var containsNEW = false
var containsEHs = false
var containsInvokeDynamic = false
def emit(i: Instruction) {
instrs += ((pc, i))
if (i.isInstanceOf[DupX])
containsDUPX = true
if (i.isInstanceOf[opcodes.NEW])
containsNEW = true
}
/** Break this linear code in basic block representation
* As a side effect, it sets the `code` field of the current
*/
def toBasicBlock: Code = {
import opcodes._
val code = new Code(method)
method.setCode(code)
method.bytecodeHasEHs = containsEHs
method.bytecodeHasInvokeDynamic = containsInvokeDynamic
var bb = code.startBlock
def makeBasicBlocks: mutable.Map[Int, BasicBlock] =
mutable.Map(jmpTargets.toSeq map (_ -> code.newBlock): _*)
val blocks = makeBasicBlocks
var otherBlock: BasicBlock = NoBasicBlock
for ((pc, instr) <- instrs.iterator) {
// Console.println("> " + pc + ": " + instr);
if (jmpTargets(pc)) {
otherBlock = blocks(pc)
if (!bb.closed && otherBlock != bb) {
bb.emit(JUMP(otherBlock))
bb.close()
// Console.println("\t> closing bb: " + bb)
}
bb = otherBlock
// Console.println("\t> entering bb: " + bb)
}
if (bb.closed) {
// the basic block is closed, i.e. the previous instruction was a jump, return or throw,
// but the next instruction is not a jump target. this means that the next instruction is
// dead code. we can therefore advance until the next jump target.
debuglog(s"ICode reader skipping dead instruction $instr in classfile $instanceCode")
} else {
instr match {
case LJUMP(target) =>
otherBlock = blocks(target)
bb.emitOnly(JUMP(otherBlock))
case LCJUMP(success, failure, cond, kind) =>
otherBlock = blocks(success)
val failBlock = blocks(failure)
bb.emitOnly(CJUMP(otherBlock, failBlock, cond, kind))
case LCZJUMP(success, failure, cond, kind) =>
otherBlock = blocks(success)
val failBlock = blocks(failure)
bb.emitOnly(CZJUMP(otherBlock, failBlock, cond, kind))
case LSWITCH(tags, targets) =>
bb.emitOnly(SWITCH(tags, targets map blocks))
case RETURN(_) =>
bb emitOnly instr
case THROW(clasz) =>
bb emitOnly instr
case _ =>
bb emit instr
}
}
}
method.code
}
def resolveDups() {
import opcodes._
val tfa = new analysis.MethodTFA() {
import analysis._
/** Abstract interpretation for one instruction. */
override def mutatingInterpret(out: typeFlowLattice.Elem, i: Instruction): typeFlowLattice.Elem = {
val stack = out.stack
import stack.push
i match {
case DUP_X1 =>
val (one, two) = stack.pop2
push(one); push(two); push(one)
case DUP_X2 =>
val (one, two, three) = stack.pop3
push(one); push(three); push(two); push(one)
case DUP2_X1 =>
val (one, two) = stack.pop2
if (one.isWideType) {
push(one); push(two); push(one)
} else {
val three = stack.pop
push(two); push(one); push(three); push(two); push(one)
}
case DUP2_X2 =>
val (one, two) = stack.pop2
if (one.isWideType && two.isWideType) {
push(one); push(two); push(one)
} else if (one.isWideType) {
val three = stack.pop
assert(!three.isWideType, "Impossible")
push(one); push(three); push(two); push(one)
} else {
val three = stack.pop
if (three.isWideType) {
push(two); push(one); push(one); push(three); push(two); push(one)
} else {
val four = stack.pop
push(two); push(one); push(four); push(one); push(three); push(two); push(one)
}
}
case _ =>
super.mutatingInterpret(out, i)
}
out
}
}
// method.dump
tfa.init(method)
tfa.run()
for (bb <- linearizer.linearize(method)) {
var info = tfa.in(bb)
for (i <- bb.toList) {
i match {
case DUP_X1 =>
val one = info.stack.types(0)
val two = info.stack.types(1)
assert(!one.isWideType, "DUP_X1 expects values of size 1 on top of stack " + info.stack)
val tmp1 = freshLocal(one)
val tmp2 = freshLocal(two)
bb.replaceInstruction(i, List(STORE_LOCAL(tmp1),
STORE_LOCAL(tmp2),
LOAD_LOCAL(tmp1),
LOAD_LOCAL(tmp2),
LOAD_LOCAL(tmp1)))
case DUP_X2 =>
val one = info.stack.types(0)
val two = info.stack.types(1)
assert (!one.isWideType, "DUP_X2 expects values of size 1 on top of stack " + info.stack)
val tmp1 = freshLocal(one)
val tmp2 = freshLocal(two)
if (two.isWideType)
bb.replaceInstruction(i, List(STORE_LOCAL(tmp1),
STORE_LOCAL(tmp2),
LOAD_LOCAL(tmp1),
LOAD_LOCAL(tmp2),
LOAD_LOCAL(tmp1)))
else {
val tmp3 = freshLocal(info.stack.types(2))
bb.replaceInstruction(i, List(STORE_LOCAL(tmp1),
STORE_LOCAL(tmp2),
STORE_LOCAL(tmp3),
LOAD_LOCAL(tmp1),
LOAD_LOCAL(tmp3),
LOAD_LOCAL(tmp2),
LOAD_LOCAL(tmp1)))
}
case DUP2_X1 =>
val one = info.stack.types(0)
val two = info.stack.types(1)
val tmp1 = freshLocal(one)
val tmp2 = freshLocal(two)
if (one.isWideType) {
assert(!two.isWideType, "Impossible")
bb.replaceInstruction(i, List(STORE_LOCAL(tmp1),
STORE_LOCAL(tmp2),
LOAD_LOCAL(tmp1),
LOAD_LOCAL(tmp2),
LOAD_LOCAL(tmp1)))
} else {
val tmp3 = freshLocal(info.stack.types(2))
bb.replaceInstruction(i, List(STORE_LOCAL(tmp1),
STORE_LOCAL(tmp2),
STORE_LOCAL(tmp3),
LOAD_LOCAL(tmp1),
LOAD_LOCAL(tmp3),
LOAD_LOCAL(tmp2),
LOAD_LOCAL(tmp1)))
}
case DUP2_X2 =>
val one = info.stack.types(0)
val two = info.stack.types(1)
val tmp1 = freshLocal(one)
val tmp2 = freshLocal(two)
if (one.isWideType && two.isWideType) {
bb.replaceInstruction(i, List(STORE_LOCAL(tmp1),
STORE_LOCAL(tmp2),
LOAD_LOCAL(tmp1),
LOAD_LOCAL(tmp2),
LOAD_LOCAL(tmp1)))
} else if (one.isWideType) {
val three = info.stack.types(2)
assert(!two.isWideType && !three.isWideType, "Impossible")
val tmp3 = freshLocal(three)
bb.replaceInstruction(i, List(STORE_LOCAL(tmp1),
STORE_LOCAL(tmp2),
STORE_LOCAL(tmp3),
LOAD_LOCAL(tmp1),
LOAD_LOCAL(tmp3),
LOAD_LOCAL(tmp2),
LOAD_LOCAL(tmp1)))
} else {
val three = info.stack.types(2)
val tmp3 = freshLocal(three)
if (three.isWideType) {
bb.replaceInstruction(i, List(STORE_LOCAL(tmp1),
STORE_LOCAL(tmp2),
STORE_LOCAL(tmp3),
LOAD_LOCAL(tmp2),
LOAD_LOCAL(tmp1),
LOAD_LOCAL(tmp3),
LOAD_LOCAL(tmp2),
LOAD_LOCAL(tmp1)))
} else {
val four = info.stack.types(3)
val tmp4 = freshLocal(three)
assert(!four.isWideType, "Impossible")
bb.replaceInstruction(i, List(STORE_LOCAL(tmp1),
STORE_LOCAL(tmp2),
STORE_LOCAL(tmp3),
STORE_LOCAL(tmp4),
LOAD_LOCAL(tmp2),
LOAD_LOCAL(tmp1),
LOAD_LOCAL(tmp4),
LOAD_LOCAL(tmp3),
LOAD_LOCAL(tmp2),
LOAD_LOCAL(tmp1)))
}
}
case _ =>
}
info = tfa.interpret(info, i)
}
}
}
/** Recover def-use chains for NEW and initializers. */
def resolveNEWs() {
import opcodes._
val rdef = new reachingDefinitions.ReachingDefinitionsAnalysis
rdef.init(method)
rdef.run()
for (bb <- method.code.blocks ; (i, idx) <- bb.toList.zipWithIndex) i match {
case cm @ CALL_METHOD(m, Static(true)) if m.isClassConstructor =>
def loop(bb0: BasicBlock, idx0: Int, depth: Int): Unit = {
rdef.findDefs(bb0, idx0, 1, depth) match {
case ((bb1, idx1)) :: _ =>
bb1(idx1) match {
case _: DUP => loop(bb1, idx1, 0)
case x: NEW => x.init = cm
case _: THIS => () // super constructor call
case producer => dumpMethodAndAbort(method, "producer: " + producer)
}
case _ => ()
}
}
loop(bb, idx, m.info.paramTypes.length)
case _ => ()
}
}
/** Return the local at given index, with the given type. */
def getLocal(idx: Char, kind: TypeKind): Local = getLocal(idx.toInt, kind)
def getLocal(idx: Int, kind: TypeKind): Local = {
assert(idx < maxLocals, "Index too large for local variable.")
def checkValidIndex() {
locals.get(idx - 1) match {
case Some(others) if others exists (_._2.isWideType) =>
global.globalError("Illegal index: " + idx + " points in the middle of another local")
case _ => ()
}
kind match {
case LONG | DOUBLE if (locals.isDefinedAt(idx + 1)) =>
global.globalError("Illegal index: " + idx + " overlaps " + locals(idx + 1) + "\nlocals: " + locals)
case _ => ()
}
}
locals.get(idx) match {
case Some(ls) =>
val l = ls find { loc => loc._2 isAssignabledTo kind }
l match {
case Some((loc, _)) => loc
case None =>
val l = freshLocal(kind)
locals(idx) = (l, kind) :: locals(idx)
log("Expected kind " + kind + " for local " + idx +
" but only " + ls + " found. Added new local.")
l
}
case None =>
checkValidIndex()
val l = freshLocal(idx, kind, isArg = false)
debuglog("Added new local for idx " + idx + ": " + kind)
locals += (idx -> List((l, kind)))
l
}
}
override def toString(): String = instrs.toList.mkString("", "\n", "")
/** Return a fresh Local variable for the given index.
*/
private def freshLocal(idx: Int, kind: TypeKind, isArg: Boolean) = {
val sym = method.symbol.newVariable(newTermName("loc" + idx)).setInfo(kind.toType)
val l = new Local(sym, kind, isArg)
method.addLocal(l)
l
}
private var count = 0
/** Invent a new local, with a new index value outside the range of
* the original method. */
def freshLocal(kind: TypeKind): Local = {
count += 1
freshLocal(maxLocals + count, kind, isArg = false)
}
/** add a method param with the given index. */
def enterParam(idx: Int, kind: TypeKind) = {
val sym = method.symbol.newVariable(newTermName("par" + idx)).setInfo(kind.toType)
val l = new Local(sym, kind, true)
assert(!locals.isDefinedAt(idx), locals(idx))
locals += (idx -> List((l, kind)))
l
}
/** Base class for branch instructions that take addresses. */
abstract class LazyJump(pc: Int) extends Instruction {
override def toString() = "LazyJump " + pc
jmpTargets += pc
}
case class LJUMP(pc: Int) extends LazyJump(pc)
case class LCJUMP(success: Int, failure: Int, cond: TestOp, kind: TypeKind)
extends LazyJump(success) {
override def toString(): String = "LCJUMP (" + kind + ") " + success + " : " + failure
jmpTargets += failure
}
case class LCZJUMP(success: Int, failure: Int, cond: TestOp, kind: TypeKind)
extends LazyJump(success) {
override def toString(): String = "LCZJUMP (" + kind + ") " + success + " : " + failure
jmpTargets += failure
}
case class LSWITCH(tags: List[List[Int]], targets: List[Int]) extends LazyJump(targets.head) {
override def toString(): String = "LSWITCH (tags: " + tags + ") targets: " + targets
jmpTargets ++= targets.tail
}
/** Duplicate and exchange pseudo-instruction. Should be later
* replaced by proper ICode */
abstract class DupX extends Instruction
case object DUP_X1 extends DupX
case object DUP_X2 extends DupX
case object DUP2_X1 extends DupX
case object DUP2_X2 extends DupX
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy