/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.compiler;
import static com.github.anba.es6draft.semantics.StaticSemantics.IsStrict;
import static com.github.anba.es6draft.semantics.StaticSemantics.TailCallNodes;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.github.anba.es6draft.ast.*;
import com.github.anba.es6draft.ast.scope.Scope;
import com.github.anba.es6draft.compiler.DefaultCodeGenerator.ValType;
import com.github.anba.es6draft.compiler.Labels.BreakLabel;
import com.github.anba.es6draft.compiler.Labels.ContinueLabel;
import com.github.anba.es6draft.compiler.Labels.ReturnLabel;
import com.github.anba.es6draft.compiler.Labels.TempLabel;
import com.github.anba.es6draft.compiler.StatementGenerator.Completion;
import com.github.anba.es6draft.compiler.assembler.*;
import com.github.anba.es6draft.compiler.assembler.Code.MethodCode;
import com.github.anba.es6draft.runtime.ExecutionContext;
import com.github.anba.es6draft.runtime.internal.InlineArrayList;
import com.github.anba.es6draft.runtime.internal.ResumptionPoint;
import com.github.anba.es6draft.runtime.internal.ScriptRuntime;
/**
*
*/
abstract class CodeVisitor extends InstructionVisitor {
private static final class Methods {
// class: ResumptionPoint
static final MethodName ResumptionPoint_create = MethodName.findStatic(Types.ResumptionPoint, "create",
Type.methodType(Types.ResumptionPoint, Types.Object_, Types.Object_, Type.INT_TYPE));
static final MethodName ResumptionPoint_createWithNext = MethodName.findStatic(Types.ResumptionPoint, "create",
Type.methodType(Types.ResumptionPoint, Types.Object_, Types.Object_, Type.INT_TYPE,
Types.ResumptionPoint));
static final MethodName ResumptionPoint_getLocals = MethodName.findVirtual(Types.ResumptionPoint, "getLocals",
Type.methodType(Types.Object_));
static final MethodName ResumptionPoint_getOffset = MethodName.findVirtual(Types.ResumptionPoint, "getOffset",
Type.methodType(Type.INT_TYPE));
static final MethodName ResumptionPoint_getStack = MethodName.findVirtual(Types.ResumptionPoint, "getStack",
Type.methodType(Types.Object_));
}
private static final class Labels {
// unlabelled breaks and continues
final ArrayDeque<Jump> breakTargets = new ArrayDeque<>(4);
final ArrayDeque<Jump> continueTargets = new ArrayDeque<>(4);
// labelled breaks and continues
final HashMap<String, Jump> namedBreakLabels = new HashMap<>(4);
final HashMap<String, Jump> namedContinueLabels = new HashMap<>(4);
Jump returnLabel = null;
// temporary labels in try-catch-finally blocks
ArrayList<TempLabel> tempLabels = null;
final Labels parent;
final MutableValue<Object> completion;
Labels(Labels parent, MutableValue<Object> completion) {
this.parent = parent;
this.completion = completion;
}
private TempLabel newTemp(Jump actual) {
assert actual != null;
TempLabel temp = new TempLabel(actual);
if (tempLabels == null) {
tempLabels = new ArrayList<>(4);
}
tempLabels.add(temp);
return temp;
}
private Jump getBreakLabel(String name) {
return name == null ? breakTargets.peek() : namedBreakLabels.get(name);
}
private void setBreakLabel(String name, Jump label) {
if (name == null) {
assert breakTargets.isEmpty();
breakTargets.push(label);
} else {
namedBreakLabels.put(name, label);
}
}
private Jump getContinueLabel(String name) {
return name == null ? continueTargets.peek() : namedContinueLabels.get(name);
}
private void setContinueLabel(String name, Jump label) {
if (name == null) {
assert continueTargets.isEmpty();
continueTargets.push(label);
} else {
namedContinueLabels.put(name, label);
}
}
Jump returnLabel() {
Jump lbl = returnLabel;
if (lbl == null) {
assert parent != null : "return outside of function";
lbl = newTemp(parent.returnLabel());
returnLabel = lbl;
}
return lbl;
}
Jump breakLabel(String name) {
Jump label = getBreakLabel(name);
if (label == null) {
assert parent != null : "Label not found: " + name;
label = newTemp(parent.breakLabel(name));
setBreakLabel(name, label);
}
return label;
}
Jump continueLabel(String name) {
Jump label = getContinueLabel(name);
if (label == null) {
assert parent != null : "Label not found: " + name;
label = newTemp(parent.continueLabel(name));
setContinueLabel(name, label);
}
return label;
}
}
private static final int CONTEXT_SLOT = 0;
private static final int RESUME_SLOT = 1;
private static final int MIN_RECOVER_SLOT = 2;
static {
assert CONTEXT_SLOT < RESUME_SLOT;
assert RESUME_SLOT < MIN_RECOVER_SLOT;
}
private final CodeVisitor parent;
private final TopLevelNode<?> topLevelNode;
private final boolean strict;
private int classDefDepth = 0;
private Scope scope;
private Labels labels = new Labels(null, null);
// tail-call support
private boolean hasTailCalls = false;
private int wrapped = 0;
private Set<Expression> tailCallNodes = Collections.emptySet();
private Variable<ExecutionContext> executionContext;
private MutableValue<Object> completionValue;
private boolean hasCompletion;
protected CodeVisitor(MethodCode method, CodeVisitor parent) {
super(method);
this.parent = parent;
this.topLevelNode = parent.topLevelNode;
this.strict = parent.strict;
this.classDefDepth = parent.classDefDepth;
this.scope = parent.scope;
this.wrapped = parent.wrapped;
this.tailCallNodes = parent.tailCallNodes;
// no return in script/module code
this.labels.returnLabel = isFunction() ? new ReturnLabel() : null;
}
protected CodeVisitor(MethodCode method, TopLevelNode<?> topLevelNode) {
super(method);
this.parent = null;
this.topLevelNode = topLevelNode;
this.strict = isStrict(topLevelNode);
// no return in script/module code
this.labels.returnLabel = isFunction() ? new ReturnLabel() : null;
}
private static boolean isStrict(TopLevelNode<?> node) {
if (node instanceof Script) {
return IsStrict((Script) node);
}
if (node instanceof Module) {
return IsStrict((Module) node);
}
assert node instanceof FunctionNode;
return IsStrict((FunctionNode) node);
}
@Override
public void begin() {
super.begin();
this.executionContext = getParameter(CONTEXT_SLOT, ExecutionContext.class);
this.completionValue = createCompletionVariable();
this.hasCompletion = hasCompletionValue();
assert !(hasCompletion && completionValue == null);
}
@Override
public void end() {
if (parent != null) {
parent.hasTailCalls |= hasTailCalls;
}
super.end();
}
@Override
protected final Stack createStack(Variables variables) {
if (isGeneratorOrAsync()) {
return new StackImpl(variables);
}
return super.createStack(variables);
}
/**
* Returns the completion value variable or {@code null} if completion values are not saved.
*
* @return the completion value variable
*/
protected MutableValue<Object> createCompletionVariable() {
return null;
}
/**
* Returns {@code true} if the current method stores statement completion values.
*
* @return {@code true} if statement completion values are stored
*/
protected boolean hasCompletionValue() {
return false;
}
/**
* Returns the parent visitor or {@code null} if not present
*
* @return the parent visitor or {@code null}
*/
protected final CodeVisitor getParent() {
return parent;
}
/**
* Returns the {@link TopLevelNode} for this visitor.
*
* @return the top level node for this visitor
*/
final TopLevelNode<?> getTopLevelNode() {
return topLevelNode;
}
/**
* Returns the execution context.
*
* @return the execution context
*/
final Variable<ExecutionContext> executionContext() {
return executionContext;
}
/**
* ∅ → cx
*/
final void loadExecutionContext() {
load(executionContext);
}
/**
* Returns {@code true} if currently emitting strict-mode code.
*
* @return {@code true} if emitting strict-mode code
*/
final boolean isStrict() {
return strict || classDefDepth != 0;
}
/**
* Returns {@code true} if emitting global script or module code.
*
* @return {@code true} if in global script or module code
*/
final boolean isGlobalCode() {
if (topLevelNode instanceof Script) {
return ((Script) topLevelNode).isGlobalCode();
}
if (topLevelNode instanceof Module) {
return true;
}
assert topLevelNode instanceof FunctionNode;
return false;
}
/**
* Returns {@code true} if compiling function code.
*
* @return {@code true} if compiling function code
*/
final boolean isFunction() {
return topLevelNode instanceof FunctionNode;
}
/**
* Returns {@code true} if compiling async function code.
*
* @return {@code true} if compiling async function code
*/
final boolean isAsync() {
if (topLevelNode instanceof FunctionNode) {
return ((FunctionNode) topLevelNode).isAsync();
}
return false;
}
/**
* Returns {@code true} if compiling generator function code.
*
* @return {@code true} if compiling generator function code
*/
final boolean isGenerator() {
if (topLevelNode instanceof FunctionNode) {
return ((FunctionNode) topLevelNode).isGenerator();
}
return false;
}
/**
* Returns {@code true} if compiling generator or async function code.
*
* @return {@code true} if compiling generator or async function code
*/
final boolean isGeneratorOrAsync() {
if (topLevelNode instanceof FunctionNode) {
return ((FunctionNode) topLevelNode).isGenerator() || ((FunctionNode) topLevelNode).isAsync();
}
return false;
}
/**
* Enters a class definition.
*/
final void enterClassDefinition() {
++classDefDepth;
}
/**
* Exits a class definition.
*/
final void exitClassDefinition() {
--classDefDepth;
}
/**
* Returns <code>true</code> when currently within a wrapped block.
*
* @return <code>true</code> if currently wrapped
*/
private boolean isWrapped() {
return wrapped != 0;
}
/**
* Enters a try-catch-finally wrapped block.
*/
final void enterWrapped() {
++wrapped;
}
/**
* Exits a try-catch-finally wrapped block.
*/
final void exitWrapped() {
--wrapped;
}
/**
* Returns the current scope.
*
* @return the current scope
*/
final Scope getScope() {
return scope;
}
/**
* Convenience method for: {@code enterScope(node.getScope())}.
*
* @param node
* the scoped node
* @see #enterScope(Scope)
*/
final void enterScope(ScopedNode node) {
assert node.getScope().getParent() == this.scope;
this.scope = node.getScope();
}
/**
* Enters a new scope.
*
* @param scope
* the new scope
*/
final void enterScope(Scope scope) {
assert scope.getParent() == this.scope;
this.scope = scope;
}
/**
* Exits the current scope.
*/
final void exitScope() {
scope = scope.getParent();
}
/**
* Enters a function body code.
*
* @param node
* the function node
*/
final void enterFunction(FunctionNode node) {
assert this.scope == null;
this.scope = node.getScope().lexicalScope();
}
/**
* Exits a function body code.
*/
final void exitFunction() {
scope = null;
}
/**
* Enters a tail call position.
*
* @param expr
* the expression node
*/
final void enterTailCallPosition(Expression expr) {
assert isFunction();
if (isStrict() && !isWrapped() && !isGeneratorOrAsync()) {
// Tail calls are only enabled in strict-mode code [14.6.1 Tail Position, step 2]
this.tailCallNodes = TailCallNodes(expr);
}
}
/**
* Exits a tail call position.
*/
final void exitTailCallPosition() {
this.tailCallNodes = Collections.emptySet();
}
/**
* Returns {@code true} if the current method has tail-calls.
*
* @return {@code true} if tail-calls have been emitted
*/
final boolean hasTailCalls() {
return hasTailCalls;
}
/**
* Returns {@code true} if the expression is in a tail-call position.
*
* @param expr
* the expression node
* @return {@code true} if in tail-call position
*/
final boolean isTailCall(Expression expr) {
boolean isTaillCall = tailCallNodes.contains(expr);
hasTailCalls |= isTaillCall;
return isTaillCall;
}
/**
* Emit goto instruction to jump to {@code label}'s wrapped target.
*
* @param label
* the target instruction
* @param completion
* the variable which holds the current completion value
*/
final void goTo(TempLabel label, Value<Object> completion) {
if (label.isReturn()) {
// Specialize return label to emit return completion.
returnCompletion(completion);
} else {
goTo(label.getWrapped());
}
}
/**
* Emits a return instruction.
*/
final void returnCompletion(Value<? extends Object> returnValue) {
if (isAbruptRegion()) {
// If currently enclosed by a finally scoped block, store value in completion register
// and jump to (temporary) return label.
store(labels.completion, returnValue);
goTo(labels.returnLabel());
} else {
// Otherwise emit a return instruction.
load(returnValue);
returnForCompletion();
}
}
/**
* Pops the stack's top element and emits a return instruction.
*/
final void returnCompletion() {
if (isAbruptRegion()) {
// If currently enclosed by a finally scoped block, store value in completion register
// and jump to (temporary) return label.
store(labels.completion);
goTo(labels.returnLabel());
} else {
// Otherwise emit a return instruction.
returnForCompletion();
}
}
/**
* Extension point for subclasses.
* <p>
* stack: [value] {@literal ->} []
*/
protected void returnForCompletion() {
// Emit direct return instruction.
_return();
}
/**
* Returns {@code true} when currently within a finally scoped block.
*
* @return {@code true} if currently in a finally block
*/
private boolean isAbruptRegion() {
return labels.parent != null;
}
private MutableValue<Object> enterAbruptRegion(boolean statementCompletion) {
assert labels != null;
MutableValue<Object> completion;
if (hasCompletion() && !statementCompletion) {
// Re-use the existing completion variable if the statement doesn't track its completion state.
completion = completionValue();
} else {
completion = newVariable("completion", Object.class);
loadCompletionValue();
store(completion);
}
labels = new Labels(labels, completion);
return completion;
}
private List<TempLabel> exitAbruptRegion() {
assert isAbruptRegion();
ArrayList<TempLabel> tempLabels = labels.tempLabels;
labels = labels.parent;
return tempLabels != null ? tempLabels : Collections.<TempLabel> emptyList();
}
final MutableValue<Object> completionValue() {
assert hasCompletion();
if (isAbruptRegion()) {
return labels.completion;
}
return completionValue;
}
/**
* Returns {@code true} if the current method stores statement completion values.
*
* @return {@code true} if statement completion values are stored
*/
final boolean hasCompletion() {
return hasCompletion;
}
/**
* Pushes the completion value onto the stack.
*/
final void loadCompletionValue() {
if (hasCompletion()) {
load(completionValue());
} else {
aconst(null);
}
}
/**
* Pops the stack's top element and stores it into the completion value.
*
* @param type
* the value type of top stack value
*/
final void storeCompletionValue(ValType type) {
if (hasCompletion()) {
toBoxed(type);
store(completionValue());
} else {
pop(type);
}
}
/**
* Stores the variable's value as the new completion value.
*
* @param completionValue
* the new completion value
*/
final void storeCompletionValue(Value<Object> completionValue) {
if (hasCompletion()) {
load(completionValue);
store(completionValue());
}
}
/**
* Stores {@code undefined} as the new completion value.
*/
final void storeUndefinedAsCompletionValue() {
if (hasCompletion()) {
loadUndefined();
store(completionValue());
}
}
/**
* Enters iteration code.
*
* @return the temporary completion value variable or {@code null} if not applicable for the current method
*/
final MutableValue<Object> enterIteration() {
if (isGeneratorOrAsync()) {
return enterAbruptRegion(false);
}
return null;
}
/**
* Exits iteration code.
*
* @return the list of generated labels
*/
final List<TempLabel> exitIteration() {
if (isGeneratorOrAsync()) {
return exitAbruptRegion();
}
return Collections.emptyList();
}
/**
* Enters an iteration body block.
*
* @param <FORSTATEMENT>
* the for-statement node type
* @param node
* the iteration statement
* @return the temporary completion object variable
*/
final <FORSTATEMENT extends IterationStatement & ForIterationNode> MutableValue<Object> enterIterationBody(
FORSTATEMENT node) {
MutableValue<Object> completion = enterAbruptRegion(false);
if (node.hasContinue()) {
// Copy current continue labels from parent to new labelset, so we won't create
// temp-labels for own continue labels.
Labels labels = this.labels;
assert !labels.parent.continueTargets.isEmpty();
Jump lblContinue = labels.parent.continueTargets.peek();
assert lblContinue instanceof ContinueLabel;
labels.continueTargets.push(lblContinue);
for (String label : node.getLabelSet()) {
labels.namedContinueLabels.put(label, lblContinue);
}
}
return completion;
}
/**
* Exits an iteration body block.
*
* @param <FORSTATEMENT>
* the for-statement node type
* @param node
* the iteration statement
* @return the list of generated labels
*/
final <FORSTATEMENT extends IterationStatement & ForIterationNode> List<TempLabel> exitIterationBody(
FORSTATEMENT node) {
return exitAbruptRegion();
}
/**
* Enters a finally scoped block.
*
* @param node
* the try-statement
* @return the temporary completion object variable
*/
final MutableValue<Object> enterFinallyScoped(TryStatement node) {
return enterAbruptRegion(node.hasCompletionValue());
}
/**
* Exits a finally scoped block.
*
* @return the list of generated labels
*/
final List<TempLabel> exitFinallyScoped() {
return exitAbruptRegion();
}
/**
* Starts code generation for {@link IterationStatement} nodes.
*
* @param node
* the iteration statement
* @param lblBreak
* the break label for the statement
* @param lblContinue
* the continue label for the statement
*/
final void enterIteration(IterationStatement node, BreakLabel lblBreak, ContinueLabel lblContinue) {
boolean hasBreak = node.hasBreak();
boolean hasContinue = node.hasContinue();
if (!(hasBreak || hasContinue))
return;
Labels labels = this.labels;
if (hasBreak)
labels.breakTargets.push(lblBreak);
if (hasContinue)
labels.continueTargets.push(lblContinue);
for (String label : node.getLabelSet()) {
if (hasBreak)
labels.namedBreakLabels.put(label, lblBreak);
if (hasContinue)
labels.namedContinueLabels.put(label, lblContinue);
}
}
/**
* Stops code generation for {@link IterationStatement} nodes.
*
* @param node
* the iteration statement
*/
final void exitIteration(IterationStatement node) {
boolean hasBreak = node.hasBreak();
boolean hasContinue = node.hasContinue();
if (!(hasBreak || hasContinue))
return;
Labels labels = this.labels;
if (hasBreak)
labels.breakTargets.pop();
if (hasContinue)
labels.continueTargets.pop();
for (String label : node.getLabelSet()) {
if (hasBreak)
labels.namedBreakLabels.remove(label);
if (hasContinue)
labels.namedContinueLabels.remove(label);
}
}
/**
* Starts code generation for {@link BreakableStatement} nodes.
*
* @param node
* the breakable statement
* @param lblBreak
* the break label for the statement
*/
final void enterBreakable(BreakableStatement node, BreakLabel lblBreak) {
if (!node.hasBreak())
return;
Labels labels = this.labels;
labels.breakTargets.push(lblBreak);
for (String label : node.getLabelSet()) {
labels.namedBreakLabels.put(label, lblBreak);
}
}
/**
* Stops code generation for {@link BreakableStatement} nodes.
*
* @param node
* the breakable statement
*/
final void exitBreakable(BreakableStatement node) {
if (!node.hasBreak())
return;
Labels labels = this.labels;
labels.breakTargets.pop();
for (String label : node.getLabelSet()) {
labels.namedBreakLabels.remove(label);
}
}
/**
* Starts code generation for {@link LabelledStatement} nodes.
*
* @param node
* the labelled statement
* @param lblBreak
* the break label for the statement
*/
final void enterLabelled(LabelledStatement node, BreakLabel lblBreak) {
if (!node.hasBreak())
return;
Labels labels = this.labels;
for (String label : node.getLabelSet()) {
labels.namedBreakLabels.put(label, lblBreak);
}
}
/**
* Stops code generation for {@link LabelledStatement} nodes.
*
* @param node
* the labelled statement
*/
final void exitLabelled(LabelledStatement node) {
if (!node.hasBreak())
return;
Labels labels = this.labels;
for (String label : node.getLabelSet()) {
labels.namedBreakLabels.remove(label);
}
}
/**
* Returns the break-label for the given {@link BreakStatement}.
*
* @param node
* the break statement
* @return the label to the target instruction
*/
final Jump breakLabel(BreakStatement node) {
String name = node.getLabel();
return labels.breakLabel(name);
}
/**
* Returns the continue-label for the given {@link ContinueStatement}.
*
* @param node
* the continue statement
* @return the label to the target instruction
*/
final Jump continueLabel(ContinueStatement node) {
String name = node.getLabel();
return labels.continueLabel(name);
}
/**
* Current execution state (locals + stack + ip)
*/
private static final class ExecutionState {
private final VariablesSnapshot locals;
private final Type[] stack;
private final Jump instruction;
private final int offset;
private final int line;
private InlineArrayList<ExecutionState> shared;
ExecutionState(VariablesSnapshot locals, Type[] stack, Jump instruction, int offset, int line) {
this.locals = locals;
this.stack = stack;
this.instruction = instruction;
this.offset = offset;
this.line = line;
}
boolean isCompatible(VariablesSnapshot locals, Type[] stack) {
return this.locals.equals(locals) && Arrays.equals(this.stack, stack);
}
ExecutionState addShared(Jump instruction, int offset, int line) {
if (shared == null) {
shared = new InlineArrayList<>();
}
ExecutionState state = new ExecutionState(locals, stack, instruction, offset, line);
shared.add(state);
return state;
}
}
/**
* List of saved execution states
*/
private ArrayList<ExecutionState> states = null;
private int stateOffsetCounter = 0;
/**
* Creates a new object to save the current execution state.
*
* @param instruction
* the resume instruction
* @return the current execution state
*/
private ExecutionState newExecutionState(Jump instruction) {
if (states == null) {
states = new ArrayList<>();
}
assert hasParameter(CONTEXT_SLOT, ExecutionContext.class);
assert hasParameter(RESUME_SLOT, ResumptionPoint.class) || hasParameter(RESUME_SLOT, ResumptionPoint[].class);
VariablesSnapshot locals = getVariablesSnapshot(MIN_RECOVER_SLOT);
Type[] stack = getStack();
int offset = stateOffsetCounter++;
int line = getLastLineNumber();
for (ExecutionState state : states) {
if (state.isCompatible(locals, stack)) {
return state.addShared(instruction, offset, line);
}
}
ExecutionState state = new ExecutionState(locals, stack, instruction, offset, line);
states.add(state);
return state;
}
static final class GeneratorState {
private final Jump resumeSwitch = new Jump(), startBody = new Jump();
}
private static final class RuntimeBootstrap {
static final boolean ENABLED = true;
static final String STACK = "rt:stack";
static final String LOCALS = "rt:locals";
private static final Handle BOOTSTRAP;
static {
MethodType mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
MethodType.class);
BOOTSTRAP = MethodName.findStatic(ScriptRuntime.class, "runtimeBootstrap", mt).toHandle();
}
}
/**
* Returns the resumption point mutable value.
*
* @return the resumption point value
*/
protected MutableValue<ResumptionPoint> resumptionPoint() {
return getParameter(RESUME_SLOT, ResumptionPoint.class);
}
/**
* Generates prologue code for generator functions.
*
* @return the generator state
*/
final GeneratorState generatorPrologue() {
GeneratorState state = new GeneratorState();
load(resumptionPoint());
ifnonnull(state.resumeSwitch);
mark(state.startBody);
return state;
}
/**
* Generates epilogue code for generator functions.
*
* @param state
* the generator state
*/
final void generatorEpilogue(GeneratorState state) {
mark(state.resumeSwitch);
ArrayList<ExecutionState> states = this.states;
if (states == null) {
goTo(state.startBody);
} else {
MutableValue<ResumptionPoint> resume = resumptionPoint();
int count = stateOffsetCounter;
int uniqueCount = states.size();
assert count > 0 && uniqueCount > 0;
if (uniqueCount == 1) {
resume(resume, states.get(0));
} else {
Jump[] restore = new Jump[count];
for (ExecutionState execState : states) {
Jump target = new Jump();
restore[execState.offset] = target;
if (execState.shared != null) {
for (ExecutionState shared : execState.shared) {
restore[shared.offset] = target;
}
}
}
load(resume);
invoke(Methods.ResumptionPoint_getOffset);
tableswitch(0, count - 1, state.startBody, restore);
for (ExecutionState execState : states) {
mark(restore[execState.offset]);
resume(resume, execState);
}
}
}
}
/**
* Calls a runtime function.
*
* @param method
* the method name
* @param arguments
* the argument values
*/
final void call(MethodName method, Value<?>... arguments) {
loadExecutionContext();
for (Value<?> value : arguments) {
load(value);
}
invoke(method);
}
/**
* Calls a runtime function and stores the return value in {@code result}.
*
* @param method
* the method name
* @param result
* the result value
* @param arguments
* the argument values
*/
final void callWithResult(MethodName method, MutableValue<?> result, Value<?>... arguments) {
call(method, arguments);
store(result);
}
/**
* Calls a suspendable runtime function.
*
* @param method
* the method name
* @param arguments
* the argument values
*/
final void callWithSuspend(MethodName method, Value<?>... arguments) {
enterVariableScope();
Variable<ResumptionPoint[]> resumeRef = newVariable("resume", ResumptionPoint[].class);
anewarray(1, Types.ResumptionPoint);
store(resumeRef);
Variable<Object[]> completionRef = newVariable("completion", Object[].class);
anewarray(1, Types.Object);
store(completionRef);
Jump resumeLabel = new Jump();
mark(resumeLabel);
loadExecutionContext();
load(resumeRef);
load(completionRef);
for (Value<?> value : arguments) {
load(value);
}
invoke(method);
MutableValue<ResumptionPoint> resume = arrayElement(resumeRef, 0, ResumptionPoint.class);
Jump noSuspend = new Jump();
load(resume);
ifnull(noSuspend);
{
pop(method.descriptor.returnType());
suspend(resumeLabel, resume);
}
mark(noSuspend);
MutableValue<Object> completion = arrayElement(completionRef, 0, Object.class);
Jump noReturn = new Jump();
load(completion);
ifnull(noReturn);
{
pop(method.descriptor.returnType());
// Pop remaining stack entries before returning the completion value.
popStack();
returnCompletion(completion);
}
mark(noReturn);
exitVariableScope();
}
/**
* Calls a suspendable runtime function.
*
* @param method
* the method name
* @param result
* the result value
* @param arguments
* the argument values
*/
final void callWithSuspendInt(MethodName method, MutableValue<Integer> result, Value<?>... arguments) {
enterVariableScope();
Variable<ResumptionPoint[]> resumeRef = newVariable("resume", ResumptionPoint[].class);
anewarray(1, Types.ResumptionPoint);
store(resumeRef);
Jump resumeLabel = new Jump();
mark(resumeLabel);
loadExecutionContext();
load(resumeRef);
for (Value<?> value : arguments) {
load(value);
}
invoke(method);
store(result);
MutableValue<ResumptionPoint> resume = arrayElement(resumeRef, 0, ResumptionPoint.class);
Jump noSuspend = new Jump();
load(result);
ifge(noSuspend);
{
suspend(resumeLabel, resume);
}
mark(noSuspend);
exitVariableScope();
}
/**
* Suspend: Saves the current stack and locals and emits a return instruction.
*
* @param instruction
* the resume instruction
* @param next
* the next resumption point
*/
private void suspend(Jump instruction, Value<ResumptionPoint> next) {
assert hasStack();
assert instruction.isResolved();
assert isEqualStack(instruction, getStack());
assert next != null;
// Create a new resumption point.
ExecutionState state = newExecutionState(instruction);
createResumptionPoint(state, next);
returnForSuspend();
}
/**
* Suspend: Saves the current stack and locals and emits a return instruction.
*/
final void suspend() {
assert hasStack();
// Create a new resumption point.
ExecutionState state = newExecutionState(new Jump());
createResumptionPoint(state, null);
returnForSuspend();
// Manually restore stack type information after return.
restoreStack(state.stack);
// Mark the resumption point for further method execution.
mark(state.instruction);
// Emit line info for debugging.
lineInfo(state.line);
}
/**
* Extension point for subclasses.
* <p>
* stack: [resumptionPoint] {@literal ->} []
*/
protected void returnForSuspend() {
// Emit direct return instruction.
_return();
}
/**
* Create a new resumption point at the current instruction offset.
* <p>
* stack: [] -> [resumptionPoint]
*
* @param state
* the current execution state
* @param next
* the next resumption point or {@code null}
*/
private void createResumptionPoint(ExecutionState state, Value<ResumptionPoint> next) {
// stack: [...] -> [<stack>]
saveStack(state);
assert getStackSize() == 1;
assert getStack()[0].equals(Types.Object_) : Arrays.toString(getStack());
// stack: [<stack>] -> [<stack>, <locals>]
saveLocals(state);
// stack: [<stack>, <locals>] -> [r]
iconst(state.offset);
if (next == null) {
invoke(Methods.ResumptionPoint_create);
} else {
load(next);
invoke(Methods.ResumptionPoint_createWithNext);
}
}
/**
* stack: [...] -> [{@literal <stack>}]
*
* @param state
* the current execution state
*/
private void saveStack(ExecutionState state) {
Type[] stack = state.stack;
if (RuntimeBootstrap.ENABLED) {
invokedynamic(RuntimeBootstrap.STACK, Type.methodType(Types.Object_, stack), RuntimeBootstrap.BOOTSTRAP);
} else {
anewarray(stack.length, Types.Object);
for (int sp = stack.length - 1; sp >= 0; --sp) {
Type t = stack[sp];
// stack: [?, array] -> [array, array, ?]
if (t.getSize() == 1) {
dupX1();
swap();
} else {
dup();
swap2();
}
// stack: [array, array, ?] -> [array, array, boxed(?)]
toBoxed(t);
// stack: [array, array, boxed(?)] -> [array, array, index, boxed(?)]
iconst(sp);
swap();
// stack: [array, array, index, boxed(?)] -> [array]
astore(Types.Object);
}
}
}
/**
* stack: [] -> [{@literal <locals>}]
*
* @param state
* the current execution state
*/
private void saveLocals(ExecutionState state) {
VariablesSnapshot locals = state.locals;
int numLocals = locals.getSize();
if (numLocals > 0) {
if (RuntimeBootstrap.ENABLED) {
int i = 0;
Type[] llocals = new Type[numLocals];
for (Variable<?> v : locals) {
llocals[i++] = v.getType();
load(v);
}
invokedynamic(RuntimeBootstrap.LOCALS, Type.methodType(Types.Object_, llocals),
RuntimeBootstrap.BOOTSTRAP);
} else {
int i = 0;
anewarray(numLocals, Types.Object);
for (Variable<?> v : locals) {
// stack: [array] -> [array, index, v]
dup();
iconst(i++);
load(v);
toBoxed(v.getType());
astore(Types.Object);
}
}
} else {
anull();
}
}
/**
* Resume: Restores the locals and stack and jumps to the resumption point.
*
* @param resume
* the variable which holds the resumption point
* @param state
* the execution state
*/
private void resume(MutableValue<ResumptionPoint> resume, ExecutionState state) {
assert hasStack();
assert getStackSize() == 0;
VariablesSnapshot variables = state.locals;
boolean hasLocals = variables.getSize() > 0;
// Emit line info for debugging.
lineInfo(state.line);
// (1) Restore locals
if (hasLocals) {
// stack: [] -> [<locals>]
load(resume);
invoke(Methods.ResumptionPoint_getLocals);
// manually restore locals type information
restoreVariables(variables);
int index = 0;
for (Variable<?> v : variables) {
// stack: [<locals>] -> [<locals>, value]
dup();
loadFromArray(index++, v.getType());
// stack: [<locals>, value] -> [<locals>]
store(v);
}
// stack: [<locals>] -> []
pop();
}
// (2) Restore stack
// stack: [] -> [<stack>]
assert getStackSize() == 0;
restoreStack(resume, state);
assert Arrays.equals(state.stack, getStack()) : String.format("%s != %s", Arrays.toString(state.stack),
Arrays.toString(getStack()));
// stack: [...] -> [..., offset]
boolean hasShared = state.shared != null;
if (hasShared) {
load(resume);
invoke(Methods.ResumptionPoint_getOffset);
}
// Clear resume parameter to avoid leaking saved state.
store(resume, anullValue());
if (hasShared) {
// stack: [..., offset] -> [...]
InlineArrayList<ExecutionState> sharedStates = state.shared;
int sharedSize = sharedStates.size();
assert sharedSize > 0;
if (sharedSize == 1) {
iconst(state.offset);
ificmpeq(state.instruction);
goTo(sharedStates.get(0).instruction);
} else {
Jump[] restore = new Jump[sharedSize];
for (int i = 0; i < sharedSize; ++i) {
restore[i] = sharedStates.get(i).instruction;
}
int firstOffset = sharedStates.get(0).offset;
int lastOffset = sharedStates.get(sharedSize - 1).offset;
boolean consecutiveOffsets = (lastOffset - firstOffset) == (sharedSize - 1);
if (consecutiveOffsets) {
tableswitch(firstOffset, lastOffset, state.instruction, restore);
} else {
int[] keys = new int[sharedSize];
for (int i = 0; i < sharedSize; ++i) {
keys[i] = sharedStates.get(i).offset;
}
lookupswitch(state.instruction, keys, restore);
}
}
} else {
goTo(state.instruction);
}
}
private void restoreStack(Value<ResumptionPoint> resume, ExecutionState state) {
Type[] stack = state.stack;
if (stack.length == 0) {
// Nothing to do.
} else if (stack.length == 1) {
// stack: [] -> [v]
load(resume);
invoke(Methods.ResumptionPoint_getStack);
loadFromArray(0, stack[0]);
} else {
assert stack.length > 1;
enterVariableScope();
Variable<Object[]> stackContent = newVariable("stack", Object[].class);
load(resume);
invoke(Methods.ResumptionPoint_getStack);
store(stackContent);
for (int sp = 0, len = stack.length; sp < len; ++sp) {
// stack: [] -> [<stack>]
load(stackContent);
// stack: [<stack>] -> [value]
loadFromArray(sp, stack[sp]);
}
// Set 'stackContent' variable to null to avoid leaking the saved stack
aconst(null);
store(stackContent);
exitVariableScope();
}
}
/**
* stack: [array] {@literal ->} [value]
*
* @param index
* array index
* @param type
* array element type
*/
private void loadFromArray(int index, Type type) {
aload(index, Types.Object);
if (type.isPrimitive()) {
checkcast(wrapper(type));
toUnboxed(type);
} else if (!Types.Object.equals(type)) {
checkcast(type);
}
}
private enum LabelKind {
Break, Continue, Return
}
static final class LabelState {
final Completion completion;
private final ArrayList<Map.Entry<LabelKind, String>> labels;
LabelState(Completion completion, ArrayList<Map.Entry<LabelKind, String>> labels) {
this.completion = completion;
this.labels = labels;
}
int size() {
return labels.size();
}
Map.Entry<LabelKind, String> get(int index) {
return labels.get(index);
}
boolean hasReturn() {
return size() > 0 && get(0).getKey() == LabelKind.Return;
}
boolean hasTargetInstruction() {
return size() > 1 || (size() == 1 && !completion.isAbrupt());
}
}
/**
* Generates prologue code for label functions.
*/
final void labelPrologue() {
assert parent != null;
assert labels.parent == null;
labels = new Labels(parent.labels, completionValue);
}
/**
* Generates epilogue code for label functions.
*
* @param target
* the target array variable
* @param completion
* the completion value
* @return the label state
*/
final LabelState labelEpilogue(Completion completion) {
assert parent != null;
assert labels.parent != null;
ArrayList<Map.Entry<LabelKind, String>> usedLabels = new ArrayList<>();
if (labels.returnLabel != null) {
labelReturn(labels.returnLabel, usedLabels, LabelKind.Return, null);
}
if (!labels.breakTargets.isEmpty()) {
labelReturn(labels.breakTargets.peek(), usedLabels, LabelKind.Break, null);
}
if (!labels.continueTargets.isEmpty()) {
labelReturn(labels.continueTargets.peek(), usedLabels, LabelKind.Continue, null);
}
for (Map.Entry<String, Jump> named : labels.namedBreakLabels.entrySet()) {
labelReturn(named.getValue(), usedLabels, LabelKind.Break, named.getKey());
}
for (Map.Entry<String, Jump> named : labels.namedContinueLabels.entrySet()) {
labelReturn(named.getValue(), usedLabels, LabelKind.Continue, named.getKey());
}
assert usedLabels.isEmpty()
|| (labels.tempLabels != null && usedLabels.size() <= labels.tempLabels.size()) : String
.format("%s != %s", usedLabels, labels.tempLabels);
return new LabelState(completion, usedLabels);
}
private void labelReturn(Jump label, ArrayList<Map.Entry<LabelKind, String>> labels, LabelKind kind, String name) {
if (label.isTarget()) {
labels.add(new SimpleImmutableEntry<>(kind, name));
mark(label);
iconst(labels.size());
_return();
}
}
/**
* Generates the label switch code.
*
* @param labelState
* the label state
* @param target
* the target array variable
* @param completion
* the completion value
* @param expression
* if {@code true} emits the label switch at expression level
*/
final void labelSwitch(LabelState labelState, Value<Integer> target, Value<Object> completion, boolean expression) {
assert hasStack();
if (expression) {
labelSwitchExpr(labelState, target, completion);
} else {
labelSwitchStmt(labelState, target, completion);
}
}
private void labelSwitchStmt(LabelState labelState, Value<Integer> target, Value<Object> returnValue) {
if (labelState.size() == 0) {
if (labelState.completion.isAbrupt()) {
// Unreachable code, create 'throw' completion for bytecode verifier.
unreachable();
}
} else if (labelState.size() == 1) {
Map.Entry<LabelKind, String> entry = labelState.get(0);
if (entry.getKey() == LabelKind.Return) {
if (labelState.completion.isAbrupt()) {
returnCompletion(returnValue);
} else {
Jump noReturn = new Jump();
load(target);
ifeq(noReturn);
{
returnCompletion(returnValue);
}
mark(noReturn);
}
} else {
Jump targetInstr = targetInstruction(entry);
if (labelState.completion.isAbrupt()) {
goTo(targetInstr);
} else {
load(target);
ifne(targetInstr);
}
}
} else if (labelState.size() > 1) {
emitLabelSwitch(labelState, target, returnValue);
if (labelState.completion.isAbrupt()) {
// Unreachable code, create 'throw' completion for bytecode verifier.
unreachable();
}
}
}
private void labelSwitchExpr(LabelState labelState, Value<Integer> target, Value<Object> returnValue) {
assert hasStack();
if (labelState.size() == 1) {
popStack(target, () -> {
Map.Entry<LabelKind, String> entry = labelState.get(0);
if (entry.getKey() == LabelKind.Return) {
returnCompletion(returnValue);
} else {
Jump targetInstr = targetInstruction(entry);
goTo(targetInstr);
}
});
} else if (labelState.size() > 1) {
popStack(target, () -> {
emitLabelSwitch(labelState, target, returnValue);
// Unreachable code, create 'throw' completion for bytecode verifier.
unreachable();
});
}
}
private Jump targetInstruction(Map.Entry<LabelKind, String> entry) {
assert entry.getKey() != LabelKind.Return;
if (entry.getKey() == LabelKind.Break) {
return labels.breakLabel(entry.getValue());
}
return labels.continueLabel(entry.getValue());
}
private void emitLabelSwitch(LabelState labelState, Value<Integer> target, Value<Object> returnValue) {
Jump defaultInstr = new Jump();
Jump returnInstr = null;
Jump[] targetInstrs = new Jump[labelState.size()];
for (int i = 0; i < labelState.size(); ++i) {
Map.Entry<LabelKind, String> entry = labelState.get(i);
if (entry.getKey() == LabelKind.Return) {
assert returnInstr == null;
targetInstrs[i] = returnInstr = new Jump();
} else {
targetInstrs[i] = targetInstruction(entry);
}
}
load(target);
tableswitch(1, labelState.size(), defaultInstr, targetInstrs);
if (returnInstr != null) {
mark(returnInstr);
returnCompletion(returnValue);
}
mark(defaultInstr);
}
private void unreachable() {
anull();
athrow();
}
@FunctionalInterface
private interface Action {
void perform();
}
private void popStack(Value<Integer> target, Action then) {
Jump noAbrupt = new Jump();
load(target);
ifeq(noAbrupt);
{
popStack();
then.perform();
}
mark(noAbrupt);
}
}