blob: 867af7cf4acb5e30a80244ecf0bb09923c4497ad [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011-2012 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 - initial API and
* implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.m2m.atl.emftvm.util;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.m2m.atl.emftvm.CodeBlock;
import org.eclipse.m2m.atl.emftvm.ExecEnv;
import org.eclipse.m2m.atl.emftvm.LineNumber;
import org.eclipse.m2m.atl.emftvm.LocalVariable;
import org.eclipse.m2m.atl.emftvm.Module;
/**
* EMFTVM stack frame. Stores local variables and code block invocation stack.
*
* @author <a href="dwagelaar@gmail.com">Dennis Wagelaar</a>
*/
public final class StackFrame {
private static final Object[] EMPTY = new Object[0];
private final ExecEnv env;
private final StackFrame parent;
private final CodeBlock codeBlock;
private final Method nativeMethod;
private String opName;
private final Object[] locals;
private int pc = -1; // Only set when necessary
/**
* Creates a new {@link StackFrame}. Use only for root frames!
*
* @param env
* the current {@link ExecEnv}
* @param codeBlock
* the codeBlock context for this stack frame
*/
public StackFrame(final ExecEnv env, final CodeBlock codeBlock) {
this.env = env;
this.parent = null;
this.codeBlock = codeBlock;
this.nativeMethod = null;
locals = new Object[codeBlock.getMaxLocals()];
}
/**
* Creates a new {@link StackFrame}.
*
* @param parent
* the parent stack frame, if any
* @param codeBlock
* the codeBlock context for this stack frame
*/
public StackFrame(final StackFrame parent, final CodeBlock codeBlock) {
this.env = parent.env;
this.parent = parent;
this.codeBlock = codeBlock;
this.nativeMethod = null;
locals = new Object[codeBlock.getMaxLocals()];
}
/**
* Creates a new {@link StackFrame}.
*
* @param parent
* the parent stack frame, if any
* @param nativeMethod
* the native Java method context for this stack frame
*/
public StackFrame(final StackFrame parent, final Method nativeMethod) {
this.env = parent.env;
this.parent = parent;
this.codeBlock = null;
this.nativeMethod = nativeMethod;
this.locals = EMPTY;
}
/**
* Creates a new {@link StackFrame}.
*
* @param parent
* the parent stack frame, if any
* @param opName
* the operation name for the debugger
*/
public StackFrame(final StackFrame parent, final String opName) {
this.env = parent.env;
this.parent = parent;
this.codeBlock = null;
this.nativeMethod = null;
this.locals = EMPTY;
this.opName = opName;
}
/**
* Returns the stack frame of cb, starting at the parent, if any, otherwise <code>null</code>.
*
* @param cb
* the code block
* @return the stack frame of cb, starting at the parent, if any, otherwise <code>null</code>
*/
private StackFrame getStackFrameFor(final CodeBlock cb) {
StackFrame parent = this.parent;
while (parent != null) {
if (parent.codeBlock == cb) {
return parent;
}
parent = parent.parent;
}
return null;
}
/**
* Returns the parent codeblock that lies <code>cbOffset</code> positions up, or <code>null</code>.
*
* @param cbOffset
* the codeblock offset
* @return the parent codeblock that lies <code>cbOffset</code> positions up, or <code>null</code>
*/
private CodeBlock getCodeBlock(final int cbOffset) {
CodeBlock cb = this.codeBlock;
for (int i = 0; i < cbOffset; i++) {
cb = cb.getNestedFor();
}
return cb;
}
/**
* Sets local variable with given <code>slot</code> to <code>value</code>.
*
* @param value
* the value to set
* @param cbOffset
* the codeblock offset
* @param slot
* the variable slot
*/
public void setLocal(final Object value, final int cbOffset, final int slot) {
if (cbOffset > 0) {
try {
final CodeBlock cb = getCodeBlock(cbOffset);
final StackFrame parent = getStackFrameFor(cb);
parent.locals[slot] = value;
} catch (Exception e) {
throw new IllegalArgumentException(String.format("Cannot address super-block local variable %d from %s", slot, this), e);
}
} else {
locals[slot] = value;
}
}
/**
* Sets local variable with given <code>slot</code> to <code>value</code>.
*
* @param value
* the value to set
* @param slot
* the variable slot
*/
public void setLocal(final Object value, final int slot) {
locals[slot] = value;
}
/**
* Sets the first local variables to the given values.
*
* @param self
* the "self" variable value
* @param values
* the other local variable values
*/
public void setLocals(final Object self, final Object[] values) {
locals[0] = self;
System.arraycopy(values, 0, locals, 1, values.length);
}
/**
* Sets the first local variables to the given values.
*
* @param values
* the local variable values
*/
public void setLocals(final Object[] values) {
System.arraycopy(values, 0, locals, 0, values.length);
}
/**
* Returns the local variable value with the given slot.
*
* @param cbOffset
* parent code block offset
* @param slot
* the local variable slot
* @return the local variable value with the given slot.
*/
public Object getLocal(final int cbOffset, final int slot) {
if (cbOffset > 0) {
try {
final CodeBlock cb = getCodeBlock(cbOffset);
final StackFrame parent = getStackFrameFor(cb);
return parent.locals[slot];
} catch (Exception e) {
throw new IllegalArgumentException(String.format("Cannot address super-block local variable %d from %s", slot, this), e);
}
}
return locals[slot];
}
/**
* Returns the local variable value with the given slot.
*
* @param slot
* the local variable slot
* @return the local variable value with the given slot.
*/
public Object getLocal(final int slot) {
return locals[slot];
}
/**
* Returns the parent stack frame.
*
* @return the parent stack frame
*/
public StackFrame getParent() {
return parent;
}
/**
* Returns the codeBlock.
*
* @return the codeBlock
*/
public CodeBlock getCodeBlock() {
return codeBlock;
}
/**
* Returns the {@link ExecEnv}.
*
* @return the env
*/
public ExecEnv getEnv() {
return env;
}
/**
* Returns the nativeMethod.
*
* @return the nativeMethod
*/
public Method getNativeMethod() {
return nativeMethod;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
final CodeBlock cb = getCodeBlock();
final int loc = getLocation();
final StringBuffer sb = new StringBuffer("at ");
sb.append(getOpName());
if (loc > -1) {
sb.append('#');
sb.append(loc);
if (cb != null) {
sb.append('(');
final Module module = cb.getModule();
if (module != null) {
sb.append(module);
} else {
sb.append("<unknown>");
}
final String sloc = getSourceLocation();
if (sloc != null) {
sb.append("#[");
sb.append(sloc);
sb.append(']');
}
sb.append(')');
}
} else if (cb != null) {
sb.append('(');
final Module module = cb.getModule();
if (module != null) {
sb.append(module);
} else {
sb.append("<unknown>");
}
sb.append(')');
}
sb.append("\n\tLocal variables: ");
if (cb != null && loc > -1) {
sb.append('[');
boolean first = true;
for (int slot = 0; slot < locals.length; slot++) {
if (!first) {
sb.append(", ");
}
first = false;
for (LocalVariable lv : cb.getLocalVariables()) {
if (lv.getSlot() == slot && lv.getStartInstructionIndex() <= loc && lv.getEndInstructionIndex() >= loc) {
sb.append(lv.toString());
sb.append(" = ");
try {
sb.append(EMFTVMUtil.toPrettyString(locals[slot], getEnv()));
} catch (VMException e) {
sb.append("<VMException>");
}
break;
}
}
}
sb.append(']');
} else {
try {
sb.append(EMFTVMUtil.toPrettyString(locals, getEnv()));
} catch (VMException e) {
sb.append("<VMException>");
}
}
final StackFrame parent = getParent();
if (parent != null) {
sb.append('\n');
sb.append(parent.toString());
}
return sb.toString();
}
/**
* Sets the pc.
*
* @param pc
* the pc to set
*/
public void setPc(int pc) {
this.pc = pc;
}
/**
* Returns the pc.
*
* @return the pc
*/
public int getPc() {
return pc;
}
/**
* Retrieves a new stack frame that is a sub-frame of <code>this</code>.
*
* @param cb
* the code block of the sub-frame
* @param args
* the arguments to pass into the sub-frame
* @return a new stack frame
*/
public StackFrame getSubFrame(final CodeBlock cb, final Object[] args) {
final StackFrame frame = new StackFrame(this, cb);
frame.setLocals(args);
return frame;
}
/**
* Retrieves a new stack frame that is a sub-frame of <code>this</code>.
*
* @param cb
* the code block of the sub-frame
* @param context
* the <code>self</code> argument to pass into the sub-frame
* @param args
* the other arguments to pass into the sub-frame
* @return a new stack frame
*/
public StackFrame getSubFrame(final CodeBlock cb, final Object context, final Object[] args) {
final StackFrame frame = new StackFrame(this, cb);
frame.setLocals(context, args);
return frame;
}
/**
* Retrieves a new stack frame that is a sub-frame of <code>this</code>.
*
* @param cb
* the code block of the sub-frame
* @param context
* the <code>self</code> argument to pass into the sub-frame
* @return a new stack frame
*/
public StackFrame getSubFrame(final CodeBlock cb, final Object context) {
final StackFrame frame = new StackFrame(this, cb);
frame.locals[0] = context;
return frame;
}
/**
* Retrieves a new stack frame that is a sub-frame of <code>this</code>.
*
* @param cb
* the code block of the sub-frame
* @param context
* the <code>self</code> argument to pass into the sub-frame
* @param arg
* the other argument to pass into the sub-frame
* @return a new stack frame
*/
public StackFrame getSubFrame(final CodeBlock cb, final Object context, final Object arg) {
final StackFrame frame = new StackFrame(this, cb);
frame.locals[0] = context;
frame.locals[1] = arg;
return frame;
}
/**
* Prepares <code>args</code> instances of {@link CodeBlock} by setting their parent frame (for VM re-entry), and instances of
* {@link EnumLiteral} for conversion to the method's corresponding parameter type. Creates a sub-frame only when necessary for VM
* re-entry.
*
* @param method
* the native method to be invoked
* @param args
* the method arguments
* @return the sub-frame, if necessary
*/
public StackFrame prepareNativeArgs(final Method method, final Object[] args) {
StackFrame subFrame = null;
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg instanceof CodeBlock) {
if (subFrame == null) {
subFrame = new StackFrame(this, method);
}
((CodeBlock) arg).setParentFrame(subFrame);
} else if (arg instanceof EnumLiteral) {
args[i] = EMFTVMUtil.convertEnumLiteral((EnumLiteral) arg, method.getParameterTypes()[i]);
}
}
return subFrame;
}
/**
* Prepares <code>context</code> and <code>args</code> instances of {@link CodeBlock} by setting their parent frame (for VM re-entry),
* and instances of {@link EnumLiteral} for conversion to the method's corresponding parameter type. Creates a sub-frame only when
* necessary for VM re-entry.
*
* @param method
* the native method to be invoked
* @param context
* the method context (i.e. self)
* @param args
* the method arguments
* @return the sub-frame, if necessary
*/
public StackFrame prepareNativeArgs(final Method method, final Object context, final Object[] args) {
StackFrame subFrame = null;
if (context instanceof CodeBlock) {
subFrame = new StackFrame(this, method);
((CodeBlock) context).setParentFrame(subFrame);
} // context can never be an enumeration literal
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg instanceof CodeBlock) {
if (subFrame == null) {
subFrame = new StackFrame(this, method);
}
((CodeBlock) arg).setParentFrame(subFrame);
} else if (arg instanceof EnumLiteral) {
args[i] = EMFTVMUtil.convertEnumLiteral((EnumLiteral) arg, method.getParameterTypes()[i]);
}
}
return subFrame;
}
/**
* Prepares <code>context</code> instances of {@link CodeBlock} by setting their parent frame (for VM re-entry). Creates a sub-frame
* only when necessary for VM re-entry.
*
* @param method
* the native method to be invoked
* @param context
* the method context (i.e. self)
* @return the sub-frame, if necessary
*/
public StackFrame prepareNativeContext(final Method method, final Object context) {
StackFrame subFrame = null;
if (context instanceof CodeBlock) {
subFrame = new StackFrame(this, method);
((CodeBlock) context).setParentFrame(subFrame);
} // context can never be an enumeration literal
return subFrame;
}
/**
* Gets a sequence of nested stacks (for debugger).
*
* @return the Stack list
*/
public LazyList<StackFrame> getStack() {
LazyList<StackFrame> res = new LazyList<StackFrame>();
StackFrame f = this;
while (f != null) {
res = res.prepend(f);
f = f.getParent();
}
return res;
}
/**
* Returns the local variables map (for debugger).
*
* @return the local variables map
*/
public Map<String, Object> getLocalVariables() {
final Map<String, Object> ret = new HashMap<String, Object>();
for (int slot = 0; slot < locals.length; slot++) {
ret.put(String.valueOf(slot), locals[slot]);
}
return ret;
}
/**
* Returns the local variable name at the given <code>slot</code> (for debugger).
*
* @param slot
* the local variable slot
* @return the variable name at the given slot
*/
public String resolveVariableName(int slot) {
final CodeBlock cb = getCodeBlock();
final int loc = getLocation();
if (cb != null && loc > -1) {
for (LocalVariable lv : cb.getLocalVariables()) {
if (lv.getSlot() == slot && lv.getStartInstructionIndex() <= loc && lv.getEndInstructionIndex() >= loc) {
return lv.getName();
}
}
}
return String.valueOf(slot);
}
/**
* Returns the {@link CodeBlock} (for debugger).
*
* @return the {@link CodeBlock}.
* @see #getCodeBlock()
*/
public CodeBlock getOperation() {
CodeBlock cb = getCodeBlock();
if (cb == null) {
cb = getParent().getOperation();
}
return cb;
}
/**
* Returns the current instruction pointer value (starts at 0, for debugger).
*
* @return the current instruction pointer value
* @see #getPc()
*/
public int getLocation() {
return getPc() - 1;
}
/**
* Returns the source code location of the current instruction location (for debugger).
*
* @return the source code location of the instruction at the current location.
* @see #getLocation()
* @see #getLineNumber()
*/
public String getSourceLocation() {
final LineNumber ln = getLineNumber();
if (ln != null) {
return ln.toString();
}
return null;
}
/**
* Returns the source code line number of the current instruction location (for debugger).
*
* @return the source code line number of the instruction at the current location.
* @see #getLocation()
*/
public LineNumber getLineNumber() {
final int location = getLocation();
final CodeBlock cb = getCodeBlock();
if (location > -1 && cb != null) {
final LineNumber ln = cb.getCode().get(location).getLineNumber();
if (ln != null) {
return ln;
}
}
return null;
}
/**
* Returns the "operation" name for this stack frame (for debugger).
*
* @return the "operation" name for this stack frame
*/
public String getOpName() {
if (opName == null) {
final CodeBlock cb = getCodeBlock();
if (cb != null) {
opName = cb.toString();
} else {
final Method m = getNativeMethod();
if (m != null) {
opName = m.toString();
} else {
opName = "<unknown>";
}
}
}
return opName;
}
}