Created
July 6, 2011 14:39
-
-
Save uehaj/1067380 to your computer and use it in GitHub Desktop.
GVM: JVM written in Groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* gvm.groovy: Java VM written in Groovy | |
*/ | |
import org.objectweb.asm.* | |
import org.objectweb.asm.util.* | |
import org.objectweb.asm.tree.* | |
import static GVM.* | |
class GVMClass { | |
def methods = [:] | |
def fields = [:] | |
def createInstance() { | |
def result = [:] | |
fields.each { | |
result += [fields] | |
} | |
return result | |
} | |
} | |
class GVMFrame { | |
def locals | |
def labels | |
def pc | |
GVMFrame(int maxLocals, labels) { | |
this.locals = new Object[maxLocals] | |
this.labels = labels | |
} | |
} | |
class GVM { | |
static verbose | |
static log(msg) { | |
if (verbose) { | |
println msg | |
} | |
} | |
def classes = [ | |
// pre loaded classes | |
"java/lang/Object":new GVMClass(methods:["<init>":{param->[:]}]), // simplified version of Object class | |
"java/io/PrintStream":new GVMClass( | |
methods:["println": | |
{param->println(param.head())}, | |
"print": | |
{param->print(param.head())} | |
] | |
) // simplified version of PrintStream | |
] | |
def frames = [] | |
def getCurrentFrame() { frames[-1] } | |
def opeStack = [] | |
def loadClass(InputStream is) { | |
ClassReader cr = new ClassReader(is) | |
ClassNode cn = new ClassNode() | |
if (verbose) { | |
cr.accept(new TraceClassVisitor(new PrintWriter(System.out)), ClassReader.SKIP_DEBUG) | |
} | |
cr.accept(new CheckClassAdapter(cn, false), ClassReader.SKIP_DEBUG) | |
def cls = new GVMClass() | |
cn.methods.each { | |
cls.methods[it.name] = it | |
log "method register [class(${cn.name}).methods[${it.name}]]=$it" | |
} | |
cn.fields.each { | |
cls.fields[it.name] = it | |
} | |
classes[cn.name] = cls | |
} | |
def calcArgCount(desc) { | |
desc = desc.replaceAll(/\[/, '') | |
desc = desc.replaceAll(/\(/, '') | |
desc = desc.replaceAll(/\).*/, '') | |
desc = desc.replaceAll(/L[^;]+;/, '*').size() | |
return desc | |
} | |
//assert calcArgCount("III") == 3 | |
//assert calcArgCount("Ljava/lang/String;II") == 3 | |
//assert calcArgCount("DLjava/lang/String;IILjava/lang/String;J") == 6 | |
def invokeMethod(String owner, String name, env, boolean isStatic, Object[] args) { | |
args.each { | |
env.opeStack.push(it); | |
} | |
invokeMethod(owner, name, env, isStatic) | |
} | |
def invokeMethod(String owner, String name, env, boolean isStatic) { | |
log "invokeMethod $owner.$name(isStatic=$isStatic)" | |
def method = env.classes[owner].methods[name] | |
if (method instanceof Closure) { | |
log "## method is closure(gvm only shorthand..)" | |
def result = method.call(env.opeStack) | |
method.getParameterTypes().size().times { | |
// TODO if !static, pop this object | |
env.opeStack.pop() | |
} | |
return result | |
} | |
log "# ${env.opeStack}" | |
env.frames += new GVMFrame(method.maxLocals, method.labels) | |
def n = 0 | |
if (!isStatic) { | |
env.currentFrame.locals[method.maxLocals-1-n] = env.opeStack.pop() | |
n++; | |
} | |
(0 ..< calcArgCount(method.desc)).eachWithIndex { it, idx-> | |
env.currentFrame.locals[method.maxLocals-1-n] = env.opeStack.pop() | |
n++; | |
} | |
env.currentFrame.pc = 0 | |
try { | |
while (env.currentFrame.pc < method.instructions.size()) { | |
log "# env.currentFrame.pc=$env.currentFrame.pc" | |
log "# opeStack = ${env.opeStack}" | |
log "# locals = ${env.currentFrame.locals}" | |
def insn = method.instructions.get(env.currentFrame.pc) | |
if (insn.eval(env) == -1) { // *RETURN | |
log "#[2] opeStack = ${env.opeStack}" | |
return | |
} | |
env.currentFrame.pc++; | |
} | |
} | |
finally { | |
env.frames.pop(); | |
log "return from invokeMethod $owner.$name($isStatic)" | |
} | |
} | |
} | |
MethodNode.metaClass.getLabels = { | |
def result = [:] | |
delegate.instructions.eachWithIndex { insn, idx -> | |
if (insn instanceof LabelNode) { | |
result[insn.label] = idx | |
} | |
} | |
return result | |
} | |
TypeInsnNode.metaClass.eval = { env -> | |
log "TypeInsnNode.metaClass.eval" | |
switch (delegate.opcode) { | |
case Opcodes.NEW: | |
def cls = env.classes[delegate.desc] | |
if (cls == null) { | |
throw new Error("Unknown class ${delegate.desc}") | |
} | |
env.opeStack += cls.createInstance() | |
break; | |
// case Opcodes.ANEWARRAY: break; | |
// case Opcodes.CHECKCAST: break; | |
// case Opcodes.INSTANCEOF: break; | |
default: throw new Error("Unknown instruction:"+delegate.opcode); | |
} | |
} | |
InsnNode.metaClass.eval = { env -> | |
log "InsnNode(${delegate.opcode}).metaClass.eval" | |
switch (delegate.opcode) { | |
// case Opcodes.NOP: break; | |
// case Opcodes.ACONST_NULL: break; | |
// case Opcodes.ICONST_M1: break; | |
case Opcodes.ICONST_0: | |
log "iconst 0"; | |
env.opeStack.push(0); | |
break; | |
case Opcodes.ICONST_1: | |
log "iconst 1"; | |
env.opeStack.push(1); | |
break; | |
case Opcodes.ICONST_2: | |
log "iconst 2"; | |
env.opeStack.push(2); | |
break; | |
case Opcodes.ICONST_3: | |
log "iconst 3"; | |
env.opeStack.push(3); | |
break; | |
case Opcodes.ICONST_4: | |
log "iconst 4"; | |
env.opeStack.push(4); | |
break; | |
case Opcodes.ICONST_5: | |
log "iconst 5"; | |
env.opeStack.push(5); | |
break; | |
// case Opcodes.LCONST_0: break; | |
// case Opcodes.LCONST_1: break; | |
// case Opcodes.FCONST_0: break; | |
// case Opcodes.FCONST_1: break; | |
// case Opcodes.FCONST_2: break; | |
// case Opcodes.DCONST_0: break; | |
// case Opcodes.DCONST_1: break; | |
// case Opcodes.IALOAD: break; | |
// case Opcodes.LALOAD: break; | |
// case Opcodes.FALOAD: break; | |
// case Opcodes.DALOAD: break; | |
// case Opcodes.AALOAD: break; | |
// case Opcodes.BALOAD: break; | |
// case Opcodes.CALOAD: break; | |
// case Opcodes.SALOAD: break; | |
// case Opcodes.IASTORE: break; | |
// case Opcodes.LASTORE: break; | |
// case Opcodes.FASTORE: break; | |
// case Opcodes.DASTORE: break; | |
// case Opcodes.AASTORE: break; | |
// case Opcodes.BASTORE: break; | |
// case Opcodes.CASTORE: break; | |
// case Opcodes.SASTORE: break; | |
case Opcodes.POP: | |
log "pop"; | |
env.opeStack.pop() | |
break; | |
// case Opcodes.POP2: break; | |
case Opcodes.DUP: | |
log "dup" | |
env.opeStack += env.opeStack[-1] | |
break; | |
// case Opcodes.DUP_X1: break; | |
// case Opcodes.DUP_X2: break; | |
// case Opcodes.DUP2: break; | |
// case Opcodes.DUP2_X1: break; | |
// case Opcodes.DUP2_X2: break; | |
// case Opcodes.SWAP: break; | |
case Opcodes.IADD: | |
log "iadd"; | |
env.opeStack += env.opeStack.pop()+env.opeStack.pop() | |
break; | |
// case Opcodes.LADD: break; | |
// case Opcodes.FADD: break; | |
// case Opcodes.DADD: break; | |
// case Opcodes.ISUB: break; | |
// case Opcodes.LSUB: break; | |
// case Opcodes.FSUB: break; | |
// case Opcodes.DSUB: break; | |
// case Opcodes.IMUL: break; | |
// case Opcodes.LMUL: break; | |
// case Opcodes.FMUL: break; | |
// case Opcodes.DMUL: break; | |
// case Opcodes.IDIV: break; | |
// case Opcodes.LDIV: break; | |
// case Opcodes.FDIV: break; | |
// case Opcodes.DDIV: break; | |
// case Opcodes.IREM: break; | |
// case Opcodes.LREM: break; | |
// case Opcodes.FREM: break; | |
// case Opcodes.DREM: break; | |
// case Opcodes.INEG: break; | |
// case Opcodes.LNEG: break; | |
// case Opcodes.FNEG: break; | |
// case Opcodes.DNEG: break; | |
// case Opcodes.ISHL: break; | |
// case Opcodes.LSHL: break; | |
// case Opcodes.ISHR: break; | |
// case Opcodes.LSHR: break; | |
// case Opcodes.IUSHR: break; | |
// case Opcodes.LUSHR: break; | |
// case Opcodes.IAND: break; | |
// case Opcodes.LAND: break; | |
// case Opcodes.IOR: break; | |
// case Opcodes.LOR: break; | |
// case Opcodes.IXOR: break; | |
// case Opcodes.LXOR: break; | |
// case Opcodes.I2L: break; | |
// case Opcodes.I2F: break; | |
// case Opcodes.I2D: break; | |
// case Opcodes.L2I: break; | |
// case Opcodes.L2F: break; | |
// case Opcodes.L2D: break; | |
// case Opcodes.F2I: break; | |
// case Opcodes.F2L: break; | |
// case Opcodes.F2D: break; | |
// case Opcodes.D2I: break; | |
// case Opcodes.D2L: break; | |
// case Opcodes.D2F: break; | |
// case Opcodes.I2B: break; | |
// case Opcodes.I2C: break; | |
// case Opcodes.I2S: break; | |
// case Opcodes.LCMP: break; | |
// case Opcodes.FCMPL: break; | |
// case Opcodes.FCMPG: break; | |
// case Opcodes.DCMPL: break; | |
// case Opcodes.DCMPG: break; | |
case Opcodes.IRETURN: | |
log "ireturn" | |
return -1; | |
break; | |
// case Opcodes.LRETURN: break; | |
// case Opcodes.FRETURN: break; | |
// case Opcodes.DRETURN: break; | |
// case Opcodes.ARETURN: break; | |
case Opcodes.RETURN: | |
log "return" | |
return -1 | |
break; | |
// case Opcodes.ARRAYLENGTH: break; | |
// case Opcodes.ATHROW: break; | |
// case Opcodes.MONITORENTER: break; | |
// case Opcodes.MONITOREXIT: break; | |
default: throw new Error("Not implemented opecode:"+delegate.opcode); | |
} | |
} | |
MethodInsnNode.metaClass.eval = { env -> | |
log "MethodInsnNode.metaClass.eval" | |
switch (delegate.opcode) { | |
case Opcodes.INVOKEVIRTUAL: | |
log "invokevirtual" | |
env.invokeMethod(delegate.owner, delegate.name, env, false) | |
break; | |
case Opcodes.INVOKESPECIAL: | |
log "invokespecial(${delegate.owner}, ${delegate.name})"; | |
env.invokeMethod(delegate.owner, delegate.name, env, false) | |
break; | |
case Opcodes.INVOKESTATIC: | |
log "invokestatic"; | |
env.invokeMethod(delegate.owner, delegate.name, env, true) | |
break; | |
// case Opcodes.INVOKEINTERFACE: break; | |
// case Opcodes.INVOKEDYNAMIC: break; | |
default: throw new Error("Unknown instruction:"+delegate.opcode); | |
} | |
} | |
VarInsnNode.metaClass.eval = { env -> | |
log "VarInsnNode.metaClass.eval" | |
switch (delegate.opcode) { | |
case Opcodes.ILOAD: | |
log "iload"; | |
env.opeStack.push(env.currentFrame.locals[delegate.var]); | |
break; | |
// case Opcodes.LLOAD: break; | |
// case Opcodes.FLOAD: break; | |
// case Opcodes.DLOAD: break; | |
case Opcodes.ALOAD: | |
log "aload" | |
env.opeStack.push(env.currentFrame.locals[delegate.var]); | |
break; | |
case Opcodes.ISTORE: | |
log "istore"; | |
env.currentFrame.locals[delegate.var] = env.opeStack.pop(); | |
break; | |
// case Opcodes.LSTORE: break; | |
// case Opcodes.FSTORE: break; | |
// case Opcodes.DSTORE: break; | |
case Opcodes.ASTORE: | |
log "astore"; | |
env.currentFrame.locals[delegate.var] = env.opeStack.pop(); | |
break; | |
case Opcodes.RET: | |
log "return"; | |
break; | |
default: throw new Error("Unknown instruction:"+delegate.opcode); | |
} | |
} | |
FieldInsnNode.metaClass.eval = { env -> | |
log "FieldInsnNode.metaClass.eval" | |
switch (delegate.opcode) { | |
case Opcodes.GETSTATIC: | |
log "getstatic"; | |
break; | |
// case Opcodes.PUTSTATIC: break; | |
// case Opcodes.GETFIELD: break; | |
// case Opcodes.PUTFIELD: break; | |
default: throw new Error("Unknown instruction:"+delegate.opcode); | |
} | |
} | |
LdcInsnNode.metaClass.eval = { env -> | |
log "LdcInsnNode.metaClass.eval" | |
switch (delegate.opcode) { | |
case Opcodes.LDC: | |
log "ldc"; | |
env.opeStack.push(delegate.cst); | |
break; | |
default: throw new Error("Unknown instruction:"+delegate.opcode); | |
} | |
} | |
LabelNode.metaClass.eval = { env -> | |
log "LabelNode.metaClass.eval" | |
} | |
FrameNode.metaClass.eval = { env -> | |
} | |
IntInsnNode.metaClass.eval = { env -> | |
log "IntInsnNode.metaClass.eval" | |
switch (delegate.opcode) { | |
case Opcodes.BIPUSH: | |
case Opcodes.SIPUSH: | |
case Opcodes.NEWARRAY: | |
env.opeStack.push(delegate.operand); | |
break; | |
default: throw new Error("Unknown instruction:"+delegate.opcode); | |
} | |
} | |
JumpInsnNode.metaClass.eval = { env -> | |
log "JumpInsnNode.metaClass.eval" | |
def dest = env.currentFrame.labels[delegate.label.label] | |
if (dest == null) { | |
throw new Error("Label not registered:"+delegate.label.label) | |
} | |
switch (delegate.opcode) { | |
// case Opcodes.IFEQ: break; | |
// case Opcodes.IFNE: break; | |
// case Opcodes.IFLT: break; | |
// case Opcodes.IFGE: break; | |
// case Opcodes.IFGT: break; | |
// case Opcodes.IFLE: break; | |
// case Opcodes.IF_ICMPEQ: break; | |
// case Opcodes.IF_ICMPNE: break; | |
// case Opcodes.IF_ICMPLT: break; | |
case Opcodes.IF_ICMPGE: | |
value1 = env.opeStack.pop() | |
value2 = env.opeStack.pop() | |
log "IF_ICMPGE("+value2+">="+value1+")" | |
if (value2 >= value1) { | |
env.currentFrame.pc = dest | |
} | |
break; | |
// case Opcodes.IF_ICMPGT: println "IF_ICMPGT"; break; | |
// case Opcodes.IF_ICMPLE: println "IF_ICMPLE"; break; | |
// case Opcodes.IF_ACMPEQ: println "IF_ACMPEQ"; break; | |
// case Opcodes.IF_ACMPNE: println "IF_ACMPNE"; break; | |
case Opcodes.GOTO: | |
log "GOTO:"+dest | |
env.currentFrame.pc = dest | |
break | |
// case Opcodes.JSR: println "JSR"; break; | |
// case Opcodes.IFNULL: println "IFNULL"; break; | |
// case Opcodes.IFNONNULL: println "IFNONNULL"; break; | |
default: throw new Error("Unknown instruction:"+delegate.opcode); | |
} | |
} | |
IincInsnNode.metaClass.eval = { env -> | |
log "IincInsnNode.metaClass.eval" | |
switch (delegate.opcode) { | |
case Opcodes.IINC: | |
env.currentFrame.locals[delegate.var] += delegate.incr | |
break | |
default: throw new Error("Unknown instruction:"+delegate.opcode); | |
} | |
} | |
def loadAndRun(is, fqcn, args) { | |
def env = new GVM() | |
env.loadClass(is) | |
env.invokeMethod(fqcn.replaceAll(/\./, "/"), 'main', env, true, [args] as Object[]) | |
} | |
def scriptMain(args) { | |
def cli = new CliBuilder(usage:'groovy gvm.groovy <class FQCN>') | |
cli.v(longOpt:'verbose', 'verbose') | |
cli.cp(longOpt:'classpath', args:1, 'classpath') | |
cli.demo(longOpt:'demo', 'demo') | |
cli.h(longOpt:'help', 'usage') | |
opt = cli.parse(args) | |
if (!opt) {System.exit 1} | |
verbose = opt.v | |
if (opt.demo) { | |
demo() | |
return | |
} | |
if (opt.h || opt.arguments().size() < 1) { | |
cli.usage() | |
return | |
} | |
def classPath = opt.cp ?: '.' | |
def className = opt.arguments()[0] | |
def classFileName = className.replaceAll(/\./, "/")+".class" | |
classFile = classPath.split(System.properties."path.separator").collect { | |
new File(it + System.properties."file.separator" + classFileName) | |
}.find {it.exists()} | |
if (classFile) { | |
classFile.withInputStream { | |
loadAndRun(it, className, new String[0]) | |
} | |
} | |
else { | |
System.err.println("class not found:"+classFileName) | |
} | |
} | |
def demo() { | |
/* | |
* class file data compiled from following java source: | |
* <code> | |
package sample; | |
public class Hello { | |
public static void main(String[] args){ | |
Hello h = new Hello(); | |
h.hello(); | |
System.out.println(h.plus(1,2)); | |
for (int i=0; i<10; i++) { | |
System.out.print("i="); | |
System.out.println(i); | |
} | |
} | |
void hello() { | |
System.out.println("Hello World"); | |
} | |
int plus(int i, int j) { | |
return i+j; | |
} | |
} | |
* </code> | |
*/ | |
data = """\ | |
yv66vgAAADIALwoADAAaBwAbCgACABoKAAIAHAkAHQAeCgACAB8KACAAIQgAIgoA | |
IAAjCAAkCgAgACUHACYBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJl | |
clRhYmxlAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAA1TdGFja01h | |
cFRhYmxlBwAbAQAFaGVsbG8BAARwbHVzAQAFKElJKUkBAApTb3VyY2VGaWxlAQAK | |
SGVsbG8uamF2YQwADQAOAQAMc2FtcGxlL0hlbGxvDAAVAA4HACcMACgAKQwAFgAX | |
BwAqDAArACwBAAJpPQwALQAuAQALSGVsbG8gV29ybGQMACsALgEAEGphdmEvbGFu | |
Zy9PYmplY3QBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJp | |
bnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BAAQoSSlW | |
AQAFcHJpbnQBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYAIQACAAwAAAAAAAQAAQAN | |
AA4AAQAPAAAAHQABAAEAAAAFKrcAAbEAAAABABAAAAAGAAEAAAACAAkAEQASAAEA | |
DwAAAHwABAADAAAANrsAAlm3AANMK7YABLIABSsEBbYABrYABwM9HBAKogAYsgAF | |
Egi2AAmyAAUctgAHhAIBp//osQAAAAIAEAAAACIACAAAAAQACAAFAAwABgAYAAcA | |
IAAIACgACQAvAAcANQALABMAAAAMAAL9ABoHABQB+gAaAAAAFQAOAAEADwAAACUA | |
AgABAAAACbIABRIKtgALsQAAAAEAEAAAAAoAAgAAAA0ACAAOAAAAFgAXAAEADwAA | |
ABwAAgADAAAABBscYKwAAAABABAAAAAGAAEAAAAQAAEAGAAAAAIAGQ==""".decodeBase64() | |
loadAndRun(new ByteArrayInputStream(data), "sample.Hello", new String[0]) | |
} | |
if (args.size() == 0) { | |
scriptMain(["-demo"]) | |
} | |
else { | |
scriptMain(args) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment