blob: a0afebc3a7150c1942fc06853a74cc8a6eed6e2a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011-2014 Dennis Wagelaar, Vrije Universiteit Brussel.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dennis Wagelaar, Vrije Universiteit Brussel
*******************************************************************************/
package org.eclipse.m2m.atl.emftvm.jit;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.emf.common.util.EList;
import org.eclipse.m2m.atl.common.ATLLogger;
import org.eclipse.m2m.atl.emftvm.CodeBlock;
import org.eclipse.m2m.atl.emftvm.ExecEnv;
import org.eclipse.m2m.atl.emftvm.Instruction;
import org.eclipse.m2m.atl.emftvm.util.StackFrame;
import org.eclipse.m2m.atl.emftvm.util.VMException;
import org.eclipse.m2m.atl.emftvm.util.VMMonitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
/**
* JIT that generates and loads a natively generated {@link CodeBlock}
* for a given EMFTVM bytecode {@link CodeBlock}.
* @author <a href="mailto:dennis.wagelaar@vub.ac.be">Dennis Wagelaar</a>
*/
public class CodeBlockJIT implements Opcodes {
public static final String BASE_PACKAGE = "org.eclipse.m2m.atl.emftvm.jit.generated";
private int counter = 0;
private boolean dumpBytecode = false;
/**
* {@link ClassLoader} that loads classes generated by the JIT compiler.
*/
class JITClassLoader extends ClassLoader {
/**
* Creates a new {@link JITClassLoader}.
*/
public JITClassLoader(final ClassLoader parent) {
super(parent);
}
/**
* {@inheritDoc}
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if (codeBlocks.containsKey(name)) {
final byte[] b = internalJit(codeBlocks.get(name), name);
byteCode.put(name, b);
if (isDumpBytecode()) {
dumpByteCode(name, b);
}
return defineClass(name, b, 0, b.length);
}
return super.findClass(name);
}
}
/**
* {@link Map} of class names to input {@link CodeBlock}s for the JIT.
*/
protected final Map<String, CodeBlock> codeBlocks = Collections.synchronizedMap(new HashMap<String, CodeBlock>());
/**
* Internal {@link ClassLoader} instance for loading the generated classes.
*/
protected final JITClassLoader classLoader = new JITClassLoader(getClass().getClassLoader());
/**
* The execution environment to JIT for.
*/
protected final ExecEnv env;
/**
* {@link Map} of class names to generated bytecode.
*/
protected final Map<String, byte[]> byteCode = Collections.synchronizedMap(new HashMap<String, byte[]>());
/**
* Creates a new {@link CodeBlockJIT}.
* @param env the execution environment to JIT for
*/
public CodeBlockJIT(final ExecEnv env) {
super();
this.env = env;
}
/**
* JIT-compiles <code>cb</code> into a natively implemented {@link CodeBlock}.
* @param cb the regular EMFTVM bytecode {@link CodeBlock}
* @return an instance of the JIT-generated {@link CodeBlock}
* @throws ClassNotFoundException
* @throws IllegalArgumentException
* @throws SecurityException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws NoSuchMethodException
*/
public JITCodeBlock jit(final CodeBlock cb) throws ClassNotFoundException,
IllegalArgumentException, SecurityException,
InstantiationException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
final String className = getNextClassName();
codeBlocks.put(className, cb);
try {
return (JITCodeBlock)classLoader.findClass(className)
.getConstructor(CodeBlock.class).newInstance(cb);
} catch (VerifyError e) {
final byte[] b = byteCode.get(className);
if (b != null) {
dumpByteCode(className, b);
}
throw e;
}
}
/**
* Returns the next class name to use for JIT output.
* @return the next class name to use for JIT output
*/
protected synchronized String getNextClassName() {
return BASE_PACKAGE + ".CB" + counter++;
}
/**
* Performs the actual JIT-compile of <code>cb</code> into a {@link JITCodeBlock} class
* with name <code>className</code>.
* @param cb the {@link CodeBlock} to JIT-compile
* @param className the class name for the generated class
* @return the generated class data
*/
protected byte[] internalJit(final CodeBlock cb, final String className) {
final String internalName = className.replace('.', '/');
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(V1_5, ACC_PUBLIC, internalName, null, Type.getInternalName(JITCodeBlock.class), new String[0]);
generateConstructor(
cw.visitMethod(ACC_PUBLIC,
"<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(CodeBlock.class)}),
null,
null),
internalName);
generateExecute(
cw.visitMethod(ACC_PUBLIC,
"execute",
Type.getMethodDescriptor(Type.getType(Object.class), new Type[] { Type.getType(StackFrame.class) }),
null,
null),
cb,
internalName);
cw.visitEnd();
return cw.toByteArray();
}
/**
* Generates a constructor for the {@link JITCodeBlock}.
* @param init the constructor visitor
* @param className the name of the generated class
*/
protected static void generateConstructor(final MethodVisitor init, final String className) {
init.visitCode();
// Generate labels
final Label start = new Label();
final Label end = new Label();
// Generate bytecode
init.visitLabel(start);
init.visitVarInsn(ALOAD, 0); // this
init.visitVarInsn(ALOAD, 1); // cb
init.visitMethodInsn(INVOKESPECIAL, // super(cb)
Type.getInternalName(JITCodeBlock.class),
"<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(CodeBlock.class)}),
false);
init.visitInsn(RETURN);
init.visitLabel(end);
// Create local variable table
init.visitLocalVariable("this", "L" + className + ";", null, start, end, 0);
init.visitLocalVariable("cb", Type.getDescriptor(CodeBlock.class), null, start, end, 1);
// Finalise
init.visitMaxs(2, 2);
init.visitEnd();
}
/**
* Generates an execute method for the {@link JITCodeBlock}.
* @param execute the execute method visitor
* @param cb the {@link CodeBlock} to JIT-compile
* @param className the name of the generated class
*/
protected void generateExecute(final MethodVisitor execute, final CodeBlock cb, final String className) {
execute.visitCode();
// Generate labels
final LabelSwitch ls = new LabelSwitch();
for (Instruction instr : cb.getCode()) {
ls.doSwitch(instr);
}
// Default labels
final Label start = new Label();
final Label end = new Label();
final Label tryStart = new Label();
final Label vmExceptionHandler = new Label();
final Label exceptionHandler = new Label();
final Label catchEnd = new Label();
// Create exception table
execute.visitTryCatchBlock(tryStart, vmExceptionHandler, vmExceptionHandler, Type.getInternalName(VMException.class));
execute.visitTryCatchBlock(tryStart, vmExceptionHandler, exceptionHandler, Type.getInternalName(Exception.class));
// Generate bytecode
execute.visitLabel(start);
execute.visitVarInsn(ALOAD, 1); // frame
execute.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(StackFrame.class), "getEnv", Type.getMethodDescriptor(Type.getType(ExecEnv.class), new Type[0]), false);
execute.visitVarInsn(ASTORE, 2);
execute.visitVarInsn(ALOAD, 2); // env
execute.visitMethodInsn(INVOKEINTERFACE, Type.getInternalName(ExecEnv.class), "getMonitor", Type.getMethodDescriptor(Type.getType(VMMonitor.class), new Type[0]), true);
execute.visitVarInsn(ASTORE, 3); // monitor
final boolean hasMonitor = getEnv().getMonitor() != null;
if (hasMonitor) {
execute.visitVarInsn(ALOAD, 3); // monitor: [..., monitor]
execute.visitVarInsn(ALOAD, 1); // frame: [..., monitor, frame]
execute.visitMethodInsn(INVOKEINTERFACE, // monitor.enter(frame): [...]
Type.getInternalName(VMMonitor.class),
"enter",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{
Type.getType(StackFrame.class)
}),
true);
}
execute.visitLabel(tryStart);
final ByteCodeSwitch bs = new ByteCodeSwitch(this, execute, ls);
final EList<Instruction> code = cb.getCode();
for (Instruction instr : code) {
if (hasMonitor) {
// do checkMonitor() before each instruction
generateCheckMonitor(execute, code.indexOf(instr) + 1); // pc
}
// generate instruction-specific code
bs.doSwitch(instr);
}
execute.visitJumpInsn(GOTO, catchEnd);
// catch (VMException e)
execute.visitLabel(vmExceptionHandler);
execute.visitVarInsn(ASTORE, 4); // e
execute.visitVarInsn(ALOAD, 4); // e
execute.visitInsn(ATHROW); // throw e
// catch (Exception e)
execute.visitLabel(exceptionHandler);
execute.visitVarInsn(ASTORE, 4); // e
execute.visitTypeInsn(NEW, Type.getInternalName(VMException.class)); // new VMException(): [vme, ...]
execute.visitInsn(DUP); // [vme, vme, e, ...]
execute.visitVarInsn(ALOAD, 1); // frame: [frame, vme, vme, ...]
execute.visitVarInsn(ALOAD, 4); // e: [e, frame, vme, vme, ...]
execute.visitMethodInsn(INVOKESPECIAL, // new VMException(frame, e): [vme, ...]
Type.getInternalName(VMException.class),
"<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{
Type.getType(StackFrame.class),
Type.getType(Throwable.class)
}),
false);
execute.visitInsn(ATHROW); // throw vme
// Regular method return
execute.visitLabel(catchEnd);
if (hasMonitor) {
execute.visitVarInsn(ALOAD, 3); // monitor: [..., monitor]
execute.visitVarInsn(ALOAD, 1); // frame: [..., monitor, frame]
execute.visitMethodInsn(INVOKEINTERFACE, // monitor.leave(frame): [...]
Type.getInternalName(VMMonitor.class),
"leave",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{
Type.getType(StackFrame.class)
}),
true);
}
if (cb.getStackLevel() == 0) {
execute.visitInsn(ACONST_NULL); // push null
}
execute.visitInsn(ARETURN); // return result
execute.visitLabel(end);
// Create local variable table
execute.visitLocalVariable("this", "L" + className + ";", null, start, end, 0);
execute.visitLocalVariable("frame", Type.getDescriptor(StackFrame.class), null, start, end, 1);
execute.visitLocalVariable("env", Type.getDescriptor(ExecEnv.class), null, start, end, 2);
execute.visitLocalVariable("monitor", Type.getDescriptor(VMMonitor.class), null, start, end, 3);
execute.visitLocalVariable("e", Type.getDescriptor(VMException.class), null, vmExceptionHandler, exceptionHandler, 4);
execute.visitLocalVariable("e", Type.getDescriptor(Exception.class), null, exceptionHandler, catchEnd, 4);
// Finalise
execute.visitMaxs(0, 0); // recalculate
execute.visitEnd();
}
/**
* Generates bytecode for checking the VM monitor
* @param mv the method visitor to generate code for
* @param pc the current program counter
*/
protected static void generateCheckMonitor(final MethodVisitor mv, final int pc) {
// Labels
final Label notTerminated = new Label();
// Generate bytecode
mv.visitVarInsn(ALOAD, 3); // monitor: [..., monitor]
mv.visitMethodInsn(INVOKEINTERFACE, // monitor.isTerminated(): [..., boolean]
Type.getInternalName(VMMonitor.class),
"isTerminated",
Type.getMethodDescriptor(Type.BOOLEAN_TYPE, new Type[0]),
true);
mv.visitJumpInsn(IFEQ, notTerminated); // jump if isTerminated == false: [...]
mv.visitTypeInsn(NEW, Type.getInternalName(VMException.class)); // new VMException: [..., vme]
mv.visitInsn(DUP); // [..., vme, vme]
mv.visitVarInsn(ALOAD, 1); // frame: [..., vme, vme, frame]
mv.visitLdcInsn("Execution terminated."); // [..., vme, vme, frame, msg]
mv.visitMethodInsn(INVOKESPECIAL, // vme.<init>(frame, msg): [..., vme]
Type.getInternalName(VMException.class),
"<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{
Type.getType(StackFrame.class),
Type.getType(String.class)
}),
false);
mv.visitInsn(ATHROW); // throw vme: [...]
mv.visitLabel(notTerminated);
mv.visitVarInsn(ALOAD, 1); // frame: [..., frame]
generatePushInt(mv, pc); // [..., frame, pc]
mv.visitMethodInsn(INVOKEVIRTUAL, // frame.setPc(pc): [...]
Type.getInternalName(StackFrame.class),
"setPc",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{
Type.INT_TYPE
}),
false);
mv.visitVarInsn(ALOAD, 3); // monitor: [..., monitor]
mv.visitVarInsn(ALOAD, 1); // frame: [..., monitor, frame]
mv.visitMethodInsn(INVOKEINTERFACE, // monitor.step(frame): [...]
Type.getInternalName(VMMonitor.class),
"step",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{
Type.getType(StackFrame.class)
}),
true);
}
/**
* The execution environment to JIT for.
* @return the env
*/
public ExecEnv getEnv() {
return env;
}
/**
* Generates an optimised instruction for pushing a constant integer <code>value</code>
* onto the stack.
* @param value the constant integer value to push
*/
static void generatePushInt(final MethodVisitor mv, final int value) {
if (value >= -1 && value <= 5) {
switch (value) {
case -1: mv.visitInsn(ICONST_M1); break;
case 0: mv.visitInsn(ICONST_0); break;
case 1: mv.visitInsn(ICONST_1); break;
case 2: mv.visitInsn(ICONST_2); break;
case 3: mv.visitInsn(ICONST_3); break;
case 4: mv.visitInsn(ICONST_4); break;
default: mv.visitInsn(ICONST_5); assert value == 5; break;
}
} else if (value < Byte.MAX_VALUE && value > Byte.MIN_VALUE) {
mv.visitIntInsn(BIPUSH, value);
} else if (value < Short.MAX_VALUE && value > Short.MIN_VALUE) {
mv.visitIntInsn(SIPUSH, value);
} else {
mv.visitLdcInsn(value);
}
}
/**
* Returns whether or not to dump the generated bytecode to a class file.
* @return whether or not to dump the generated bytecode to a class file
*/
public boolean isDumpBytecode() {
return dumpBytecode;
}
/**
* Sets whether or not to dump the generated bytecode to a class file.
* @param dumpBytecode whether or not to dump the generated bytecode to a class file
*/
public void setDumpBytecode(boolean dumpBytecode) {
this.dumpBytecode = dumpBytecode;
}
/**
* Cleans up all generated bytecode.
*/
public synchronized void cleanup() {
for (CodeBlock cb : codeBlocks.values()) {
cb.setJITCodeBlock(null);
}
codeBlocks.clear();
byteCode.clear();
}
/**
* Dumps generated bytecode to the temp directory.
* @param name the class name
* @param b the bytecode to dump
*/
private void dumpByteCode(String name, byte[] b) {
try {
final String path = name.substring(0, name.lastIndexOf('.')).replace('.', File.separatorChar);
final File p = new File(System.getProperty("java.io.tmpdir") + File.separatorChar + path);
p.mkdirs();
final File f = new File(p, name.substring(name.lastIndexOf('.') + 1) + ".class");
f.createNewFile();
final FileOutputStream fos = new FileOutputStream(f);
try {
fos.write(b);
} finally {
fos.close();
}
ATLLogger.info(String.format("Wrote JIT-ed code block %s to %s",
codeBlocks.get(name), f.getAbsolutePath()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}