/**
* 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.compiler.BindingInitializationGenerator.BindingInitialization;
import static com.github.anba.es6draft.compiler.BindingInitializationGenerator.InitializeBoundName;
import static com.github.anba.es6draft.compiler.BindingInitializationGenerator.InitializeBoundNameWithInitializer;
import static com.github.anba.es6draft.compiler.BindingInitializationGenerator.InitializeBoundNameWithUndefined;
import static com.github.anba.es6draft.compiler.DestructuringAssignmentGenerator.DestructuringAssignment;
import static com.github.anba.es6draft.semantics.StaticSemantics.BoundNames;
import static com.github.anba.es6draft.semantics.StaticSemantics.IsAnonymousFunctionDefinition;
import static com.github.anba.es6draft.semantics.StaticSemantics.IsConstantDeclaration;
import java.util.List;
import java.util.Map.Entry;
import com.github.anba.es6draft.ast.*;
import com.github.anba.es6draft.ast.scope.BlockScope;
import com.github.anba.es6draft.ast.scope.FunctionScope;
import com.github.anba.es6draft.ast.scope.Name;
import com.github.anba.es6draft.ast.scope.ScriptScope;
import com.github.anba.es6draft.ast.scope.TopLevelScope;
import com.github.anba.es6draft.ast.synthetic.StatementListMethod;
import com.github.anba.es6draft.compiler.CodeVisitor.LabelState;
import com.github.anba.es6draft.compiler.Labels.BreakLabel;
import com.github.anba.es6draft.compiler.Labels.ContinueLabel;
import com.github.anba.es6draft.compiler.Labels.TempLabel;
import com.github.anba.es6draft.compiler.assembler.InstructionAssembler;
import com.github.anba.es6draft.compiler.assembler.Jump;
import com.github.anba.es6draft.compiler.assembler.MethodName;
import com.github.anba.es6draft.compiler.assembler.MutableValue;
import com.github.anba.es6draft.compiler.assembler.TryCatchLabel;
import com.github.anba.es6draft.compiler.assembler.Type;
import com.github.anba.es6draft.compiler.assembler.Value;
import com.github.anba.es6draft.compiler.assembler.Variable;
import com.github.anba.es6draft.runtime.DeclarativeEnvironmentRecord;
import com.github.anba.es6draft.runtime.EnvironmentRecord;
import com.github.anba.es6draft.runtime.LexicalEnvironment;
import com.github.anba.es6draft.runtime.internal.CompatibilityOption;
import com.github.anba.es6draft.runtime.internal.ScriptException;
import com.github.anba.es6draft.runtime.internal.ScriptIterator;
import com.github.anba.es6draft.runtime.types.ScriptObject;
import com.github.anba.es6draft.runtime.types.builtins.FunctionObject;
/**
*
*/
final class StatementGenerator extends DefaultCodeGenerator<StatementGenerator.Completion> {
/**
* 6.2.2 The Completion Record Specification Type
*/
enum Completion {
Empty, Normal, Return, Throw, Break, Continue, Abrupt;
/**
* Returns {@code true} if this completion type is not {@link #Normal}.
*
* @return {@code true} if not the normal completion type
*/
boolean isAbrupt() {
return !(this == Normal || this == Empty);
}
/**
* <pre>
* {@code
* then :: Completion -> Completion -> Completion
* then a b = case (a, b) of
* (Normal, Empty) -> a
* (Normal, _) -> b
* (Empty, _) -> b
* _ -> a
* }
* </pre>
*
* @param next
* the next completion
* @return the statically computed completion type
*/
Completion then(Completion next) {
if (!(this == Normal || this == Empty) || (this == Normal && next == Empty)) {
return this;
}
return next;
}
/**
* <pre>
* {@code
* select :: Completion -> Completion -> Completion
* select a b = case (a, b) of
* (Empty, _) -> Normal
* (_, Empty) -> Normal
* (Normal, _) -> Normal
* (_, Normal) -> Normal
* _ | a == b -> a
* _ -> Abrupt
* }
* </pre>
*
* @param other
* the other completion
* @return the statically computed completion type
*/
Completion select(Completion other) {
if (this == Normal || this == Empty || other == Normal || other == Empty) {
return Normal;
}
return this == other ? this : Abrupt;
}
/**
* <pre>
* {@code
* normal :: Completion -> Bool -> Completion
* normal a b = case (a, b) of
* (_, True) -> Normal
* (Empty, _) -> Normal
* _ -> a
* }
* </pre>
*
* @param useNormal
* the flag to select the normal completion type
* @return the statically computed completion type
*/
Completion normal(boolean useNormal) {
return (useNormal || this == Empty) ? Normal : this;
}
/**
* <pre>
* {@code
* normal :: Completion -> Bool -> Completion
* normal a b = case (a, b) of
* (_, True) -> Normal
* _ -> a
* }
* </pre>
*
* @param useNormal
* the flag to select the normal completion type
* @return the statically computed completion type
*/
Completion normalOrEmpty(boolean useNormal) {
return useNormal ? Normal : this;
}
/**
* <pre>
* {@code
* nonEmpty :: Completion -> Completion
* nonEmpty a = case (a) of
* (Empty) -> Normal
* _ -> a
* }
* </pre>
*
* @return the statically computed completion type
*/
Completion nonEmpty() {
return this == Empty ? Normal : this;
}
}
private enum Bool {
True, False, Any;
private static Bool from(boolean b) {
return b ? True : False;
}
static Bool evaluate(Expression expr) {
if (expr instanceof Literal) {
if (expr instanceof BooleanLiteral) {
return from(((BooleanLiteral) expr).getValue());
}
if (expr instanceof NumericLiteral) {
double num = ((NumericLiteral) expr).getValue();
return from(num != 0 && !Double.isNaN(num));
}
if (expr instanceof StringLiteral) {
String s = ((StringLiteral) expr).getValue();
return from(s.length() != 0);
}
assert expr instanceof NullLiteral;
return False;
}
return Any;
}
}
private static final class Methods {
// class: GeneratorObject
static final MethodName GeneratorObject_isLegacyGenerator = MethodName.findVirtual(
Types.GeneratorObject, "isLegacyGenerator", Type.methodType(Type.BOOLEAN_TYPE));
// class: Iterator
static final MethodName Iterator_hasNext = MethodName.findInterface(Types.Iterator,
"hasNext", Type.methodType(Type.BOOLEAN_TYPE));
static final MethodName Iterator_next = MethodName.findInterface(Types.Iterator, "next",
Type.methodType(Types.Object));
// class: ScriptException
static final MethodName ScriptException_create = MethodName.findStatic(
Types.ScriptException, "create",
Type.methodType(Types.ScriptException, Types.Object));
static final MethodName ScriptException_getValue = MethodName.findVirtual(
Types.ScriptException, "getValue", Type.methodType(Types.Object));
// class: ScriptRuntime
static final MethodName ScriptRuntime_debugger = MethodName.findStatic(Types.ScriptRuntime,
"debugger", Type.methodType(Type.VOID_TYPE));
static final MethodName ScriptRuntime_enumerate = MethodName.findStatic(
Types.ScriptRuntime, "enumerate",
Type.methodType(Types.ScriptIterator, Types.Object, Types.ExecutionContext));
static final MethodName ScriptRuntime_enumerateValues = MethodName.findStatic(
Types.ScriptRuntime, "enumerateValues",
Type.methodType(Types.ScriptIterator, Types.Object, Types.ExecutionContext));
static final MethodName ScriptRuntime_stackOverflowError = MethodName.findStatic(
Types.ScriptRuntime, "stackOverflowError",
Type.methodType(Types.StackOverflowError, Types.Error));
static final MethodName ScriptRuntime_iterate = MethodName.findStatic(Types.ScriptRuntime,
"iterate",
Type.methodType(Types.ScriptIterator, Types.Object, Types.ExecutionContext));
static final MethodName ScriptRuntime_asyncIterate = MethodName.findStatic(Types.ScriptRuntime, "asyncIterate",
Type.methodType(Types.ScriptObject, Types.Object, Types.ExecutionContext));
static final MethodName ScriptRuntime_toInternalError = MethodName.findStatic(
Types.ScriptRuntime, "toInternalError", Type.methodType(Types.ScriptException,
Types.StackOverflowError, Types.ExecutionContext));
static final MethodName ScriptRuntime_isLegacyBlockFunction = MethodName.findStatic(Types.ScriptRuntime,
"isLegacyBlockFunction", Type.methodType(Type.BOOLEAN_TYPE, Types.ExecutionContext, Type.INT_TYPE));
}
public StatementGenerator(CodeGenerator codegen) {
super(codegen);
}
/* ----------------------------------------------------------------------------------------- */
@Override
protected Completion visit(Node node, CodeVisitor mv) {
throw new IllegalStateException(String.format("node-class: %s", node.getClass()));
}
/**
* stack: [value] {@literal ->} []
*
* @param name
* the binding name
* @param clazz
* the variable type
* @param mv
* the code visitor
*/
private <T> void InitializeBoundNameWithValue(Name name, Class<T> clazz, CodeVisitor mv) {
mv.enterVariableScope();
Variable<T> value = mv.newVariable("value", clazz);
mv.store(value);
Class<? extends EnvironmentRecord> envRecClass = getEnvironmentRecordClass(mv);
Variable<? extends EnvironmentRecord> envRec = mv.newVariable("envRec", envRecClass);
getLexicalEnvironmentRecord(envRec, mv);
InitializeBoundName(envRec, name, value, mv);
mv.exitVariableScope();
}
/**
* Extension: Async Function Definitions
*/
@Override
public Completion visit(AsyncFunctionDeclaration node, CodeVisitor mv) {
/* step 1 */
return Completion.Empty;
}
/**
* Extension: Async Generator Function Definitions
*/
@Override
public Completion visit(AsyncGeneratorDeclaration node, CodeVisitor mv) {
/* step 1 */
return Completion.Empty;
}
/**
* 13.2 Block
* <p>
* 13.2.13 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(BlockStatement node, CodeVisitor mv) {
if (node.getStatements().isEmpty()) {
// Block : { }
// -> Return NormalCompletion(empty)
return Completion.Empty;
}
/* steps 1-4 */
BlockScope scope = node.getScope();
if (scope.isPresent()) {
newDeclarativeEnvironment(scope, mv);
codegen.blockInit(node, mv);
pushLexicalEnvironment(mv);
}
/* step 5 */
mv.enterScope(node);
Completion result = Completion.Empty;
{
// 13.2.13 Runtime Semantics: Evaluation
// StatementList : StatementList StatementListItem
/* steps 1-4 */
for (StatementListItem statement : node.getStatements()) {
if ((result = result.then(statement.accept(this, mv))).isAbrupt()) {
break;
}
}
}
mv.exitScope();
/* step 6 */
if (scope.isPresent() && !result.isAbrupt()) {
popLexicalEnvironment(mv);
}
/* step 7 */
return result;
}
/**
* 13.9 The break Statement
* <p>
* 13.9.3 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(BreakStatement node, CodeVisitor mv) {
assert mv.getStackSize() == 0;
/* steps 1-2 */
mv.goTo(mv.breakLabel(node));
return Completion.Break;
}
/**
* 14.5 Class Definitions
* <p>
* 14.5.16 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(ClassDeclaration node, CodeVisitor mv) {
/* steps 1-2 */
BindingClassDeclarationEvaluation(node, mv);
/* step 3 */
return Completion.Empty;
}
/**
* 14.5.15 Runtime Semantics: BindingClassDeclarationEvaluation
*
* @param node
* the class declaration node
* @param mv
* the code visitor
*/
private void BindingClassDeclarationEvaluation(ClassDeclaration node, CodeVisitor mv) {
if (node.getIdentifier() != null) {
/* step 1 */
Name className = node.getIdentifier().getName();
/* steps 2-3 */
ClassDefinitionEvaluation(node, className, mv);
/* steps 4-6 */
SetFunctionName(node, className, mv);
/* steps 7-9 */
// stack: [value] -> []
InitializeBoundNameWithValue(className, FunctionObject.class, mv);
/* step 10 (return) */
} else {
// stack: [] -> [value]
ClassDefinitionEvaluation(node, null, mv);
}
}
/**
* 13.8 The continue Statement
* <p>
* 13.8.3 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(ContinueStatement node, CodeVisitor mv) {
assert mv.getStackSize() == 0;
/* steps 1-2 */
mv.goTo(mv.continueLabel(node));
return Completion.Continue;
}
/**
* 13.16 The debugger statement
* <p>
* 13.16.1 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(DebuggerStatement node, CodeVisitor mv) {
/* steps 1-3 */
mv.lineInfo(node);
mv.invoke(Methods.ScriptRuntime_debugger);
return Completion.Empty;
}
/**
* 13.7.2 The do-while Statement
* <p>
* 13.1.8 Runtime Semantics: Evaluation<br>
* 13.1.7 Runtime Semantics: LabelledEvaluation<br>
* 13.7.2.6 Runtime Semantics: LabelledEvaluation
*/
@Override
public Completion visit(DoWhileStatement node, CodeVisitor mv) {
assert mv.getStackSize() == 0;
Jump lblNext = new Jump();
ContinueLabel lblContinue = new ContinueLabel();
BreakLabel lblBreak = new BreakLabel();
Bool btest = Bool.evaluate(node.getTest());
mv.enterVariableScope();
Variable<LexicalEnvironment<?>> savedEnv = saveEnvironment(node, mv);
/* step 1 */
if (node.hasCompletionValue()) {
mv.storeUndefinedAsCompletionValue();
}
/* step 2 (repeat loop) */
mv.mark(lblNext);
/* steps 2.a-c */
Completion result;
{
mv.enterIteration(node, lblBreak, lblContinue);
result = node.getStatement().accept(this, mv);
mv.exitIteration(node);
}
/* step 2.b (abrupt completion - continue) */
if (lblContinue.isTarget()) {
mv.mark(lblContinue);
restoreEnvironment(savedEnv, mv);
}
/* steps 2.d-g */
if (!result.isAbrupt() || lblContinue.isTarget()) {
if (btest == Bool.Any) {
ValType type = expression(node.getTest(), mv);
ToBoolean(type, mv);
mv.ifne(lblNext);
} else if (btest == Bool.True) {
mv.goTo(lblNext);
}
}
/* step 2.b (abrupt completion - break) */
if (lblBreak.isTarget()) {
mv.mark(lblBreak);
restoreEnvironment(savedEnv, mv);
}
mv.exitVariableScope();
/* steps 2.b, 2.g */
if (btest == Bool.True) {
if (!result.isAbrupt() && !lblBreak.isTarget()) {
return Completion.Abrupt; // infinite loop
}
return result.normal(lblBreak.isTarget());
}
return result.normal(lblContinue.isTarget() || lblBreak.isTarget());
}
/**
* 13.4 Empty Statement
* <p>
* 13.4.1 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(EmptyStatement node, CodeVisitor mv) {
/* step 1 */
return Completion.Empty;
}
/**
* 15.2.3 Exports
* <p>
* 15.2.3.11 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(ExportDeclaration node, CodeVisitor mv) {
switch (node.getType()) {
case All:
case External:
case Local:
return Completion.Empty;
case Variable:
return node.getVariableStatement().accept(this, mv);
case Declaration:
return node.getDeclaration().accept(this, mv);
case DefaultHoistableDeclaration:
return node.getHoistableDeclaration().accept(this, mv);
case DefaultClassDeclaration: {
ClassDeclaration decl = node.getClassDeclaration();
/* steps 1-2 */
BindingClassDeclarationEvaluation(decl, mv);
/* steps 3-4 */
if (decl.getIdentifier() == null) {
/* steps 4.a-c */
SetFunctionName(decl, "default", mv);
/* steps 4.d-f */
InitializeBoundNameWithValue(decl.getName(), FunctionObject.class, mv);
}
/* step 5 */
return Completion.Empty;
}
case DefaultExpression:
return node.getExpression().accept(this, mv);
default:
throw new AssertionError();
}
}
/**
* 15.2.3 Exports
* <p>
* 15.2.3.11 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(ExportDefaultExpression node, CodeVisitor mv) {
Expression expr = node.getExpression();
/* steps 1-3 */
expressionBoxed(expr, mv);
/* step 4 */
if (IsAnonymousFunctionDefinition(expr)) {
SetFunctionName(expr, "default", mv);
}
/* steps 5-6 */
InitializeBoundNameWithValue(node.getBinding().getName(), Object.class, mv);
/* step 7 */
return Completion.Empty;
}
/**
* 13.5 Expression Statement
* <p>
* 13.5.1 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(ExpressionStatement node, CodeVisitor mv) {
boolean hasCompletion = mv.hasCompletion() && node.hasCompletionValue();
Expression expr = node.getExpression();
/* steps 1-2 */
if (hasCompletion) {
ValType type = expression(expr, mv);
mv.storeCompletionValue(type);
} else {
ValType type = expression(expr.emptyCompletion(), mv);
mv.pop(type);
}
return Completion.Normal;
}
private enum IterationKind {
AsyncIterate, Enumerate, EnumerateValues, Iterate
}
/**
* Extension: 'for-await' statement
*/
@Override
public Completion visit(ForAwaitStatement node, CodeVisitor mv) {
return visitForInOfLoop(node, IterationKind.AsyncIterate, mv);
}
/**
* Extension: 'for-each' statement
*/
@Override
public Completion visit(ForEachStatement node, CodeVisitor mv) {
return visitForInOfLoop(node, IterationKind.EnumerateValues, mv);
}
/**
* 13.7.5 The for-in and for-of Statements
* <p>
* 13.1.8 Runtime Semantics: Evaluation<br>
* 13.1.7 Runtime Semantics: LabelledEvaluation<br>
* 13.7.5.11 Runtime Semantics: LabelledEvaluation
*/
@Override
public Completion visit(ForInStatement node, CodeVisitor mv) {
return visitForInOfLoop(node, IterationKind.Enumerate, mv);
}
/**
* 13.7.5 The for-in and for-of Statements
* <p>
* 13.1.8 Runtime Semantics: Evaluation<br>
* 13.1.7 Runtime Semantics: LabelledEvaluation<br>
* 13.7.5.11 Runtime Semantics: LabelledEvaluation
*/
@Override
public Completion visit(ForOfStatement node, CodeVisitor mv) {
return visitForInOfLoop(node, IterationKind.Iterate, mv);
}
/**
* 13.7.5.11 Runtime Semantics: LabelledEvaluation
*
* @param <FORSTATEMENT>
* the for-statement node type
* @param node
* the for-statement node
* @param expr
* the expression node
* @param lhs
* the left-hand side node
* @param stmt
* the statement node
* @param iterationKind
* the for-statement's iteration kind
* @param mv
* the code visitor
* @return the completion value
*/
private <FORSTATEMENT extends IterationStatement & ForIterationNode> Completion visitForInOfLoop(FORSTATEMENT node,
IterationKind iterationKind, CodeVisitor mv) {
assert mv.getStackSize() == 0;
Jump lblFail = new Jump();
/* steps 1-2 */
ValType type = ForInOfHeadEvaluation(node, iterationKind, lblFail, mv);
/* step 3 */
Completion result;
if (iterationKind != IterationKind.AsyncIterate) {
result = ForInOfBodyEvaluation(node, mv);
} else {
result = AsyncForInOfBodyEvaluation(node, mv);
}
if (type != ValType.Object) {
mv.mark(lblFail);
}
return result;
}
/**
* 13.7.5.12 Runtime Semantics: ForIn/OfHeadEvaluation (TDZnames, expr, iterationKind, labelSet)
* <p>
* stack: [] {@literal ->} [Iterator]
*
* @param <FORSTATEMENT>
* the for-statement node type
* @param node
* the for-statement node
* @param iterationKind
* the for-statement's iteration kind
* @param lblFail
* the target instruction if the expression node does not produce an object type
* @param mv
* the code visitor
* @return the value type of the expression
*/
private <FORSTATEMENT extends IterationStatement & ForIterationNode> ValType ForInOfHeadEvaluation(
FORSTATEMENT node, IterationKind iterationKind, Jump lblFail, CodeVisitor mv) {
/* steps 1-2 */
BlockScope scope = node.getScope();
Node lhs = node.getHead();
List<Name> tdzNames = null;
if (lhs instanceof LexicalDeclaration) {
tdzNames = BoundNames(forDeclarationBinding((LexicalDeclaration) lhs));
assert scope.isPresent() == !tdzNames.isEmpty();
if (scope.isPresent()) {
// stack: [] -> [TDZ]
newDeclarativeEnvironment(scope, mv);
mv.enterVariableScope();
Variable<DeclarativeEnvironmentRecord> envRec = mv.newVariable("envRec",
DeclarativeEnvironmentRecord.class);
getEnvRec(envRec, mv);
// stack: [TDZ] -> [TDZ]
for (Name name : tdzNames) {
BindingOp<DeclarativeEnvironmentRecord> op = BindingOp.of(envRec, name);
op.createMutableBinding(envRec, name, false, mv);
}
mv.exitVariableScope();
// stack: [TDZ] -> []
pushLexicalEnvironment(mv);
}
mv.enterScope(node);
}
/* steps 3, 5-6 */
Expression expr = node.getExpression();
ValType type = expressionBoxed(expr, mv);
/* step 4 */
if (tdzNames != null) {
mv.exitScope();
if (scope.isPresent()) {
popLexicalEnvironment(mv);
}
}
/* steps 7-8 */
if (iterationKind == IterationKind.Enumerate
|| iterationKind == IterationKind.EnumerateValues) {
/* step 7.a */
if (type != ValType.Object) {
Jump loopstart = new Jump();
mv.dup();
isUndefinedOrNull(mv);
mv.ifeq(loopstart);
mv.pop();
mv.goTo(lblFail);
mv.mark(loopstart);
}
/* steps 7.b-c */
if (codegen.isEnabled(CompatibilityOption.LegacyGenerator)) {
// legacy generator mode, both, for-in and for-each, perform Iterate on generators
Jump l0 = new Jump(), l1 = new Jump();
mv.dup();
mv.instanceOf(Types.GeneratorObject);
mv.ifeq(l0);
mv.dup();
mv.checkcast(Types.GeneratorObject);
mv.invoke(Methods.GeneratorObject_isLegacyGenerator);
mv.ifeq(l0);
mv.loadExecutionContext();
mv.lineInfo(expr);
mv.invoke(Methods.ScriptRuntime_iterate);
mv.goTo(l1);
mv.mark(l0);
mv.loadExecutionContext();
if (iterationKind == IterationKind.Enumerate) {
mv.lineInfo(expr);
mv.invoke(Methods.ScriptRuntime_enumerate);
} else {
mv.lineInfo(expr);
mv.invoke(Methods.ScriptRuntime_enumerateValues);
}
mv.mark(l1);
} else if (iterationKind == IterationKind.Enumerate) {
mv.loadExecutionContext();
mv.lineInfo(expr);
mv.invoke(Methods.ScriptRuntime_enumerate);
} else {
mv.loadExecutionContext();
mv.lineInfo(expr);
mv.invoke(Methods.ScriptRuntime_enumerateValues);
}
} else if (iterationKind == IterationKind.AsyncIterate) {
mv.loadExecutionContext();
mv.lineInfo(expr);
mv.invoke(Methods.ScriptRuntime_asyncIterate);
} else {
/* step 8 */
assert iterationKind == IterationKind.Iterate;
mv.loadExecutionContext();
mv.lineInfo(expr);
mv.invoke(Methods.ScriptRuntime_iterate);
}
return type;
}
/**
* 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation (lhs, stmt, iterator, lhsKind, labelSet)
* <p>
* stack: [Iterator] {@literal ->} []
*
* @param <FORSTATEMENT>
* the for-statement node type
* @param node
* the for-statement node
* @param mv
* the code visitor
* @return the completion value
*/
private <FORSTATEMENT extends IterationStatement & ForIterationNode> Completion ForInOfBodyEvaluation(
FORSTATEMENT node, CodeVisitor mv) {
assert mv.getStackSize() == 1;
ContinueLabel lblContinue = new ContinueLabel();
BreakLabel lblBreak = new BreakLabel();
Jump enter = new Jump(), test = new Jump();
mv.enterVariableScope();
Variable<ScriptIterator<?>> iterator = mv.newVariable("iter", ScriptIterator.class).uncheckedCast();
// stack: [Iterator] -> []
mv.store(iterator);
Variable<Object> nextValue = mv.newVariable("nextValue", Object.class);
Variable<LexicalEnvironment<?>> savedEnv = saveEnvironment(node, mv);
/* step 1 (not applicable) */
/* step 2 */
if (node.hasCompletionValue()) {
mv.storeUndefinedAsCompletionValue();
}
/* steps 3-4 (not applicable) */
/* step 5 (repeat loop) */
mv.nonDestructiveGoTo(test);
/* steps 5.d-e */
mv.mark(enter);
mv.load(iterator);
mv.lineInfo(node);
mv.invoke(Methods.Iterator_next);
mv.store(nextValue);
/* steps 5.f-l */
{
mv.enterIteration(node, lblBreak, lblContinue);
mv.enterWrapped();
new IterationGenerator<FORSTATEMENT>(codegen) {
@Override
protected Completion iterationBody(FORSTATEMENT node, Variable<ScriptIterator<?>> iterator,
CodeVisitor mv) {
return ForInOfBodyEvaluationInner(node, nextValue, mv);
}
@Override
protected MutableValue<Object> enterIteration(FORSTATEMENT node, CodeVisitor mv) {
return mv.enterIterationBody(node);
}
@Override
protected List<TempLabel> exitIteration(FORSTATEMENT node, CodeVisitor mv) {
return mv.exitIterationBody(node);
}
}.generate(node, iterator, test, mv);
mv.exitWrapped();
mv.exitIteration(node);
}
/* steps 5.m-n */
if (lblContinue.isTarget()) {
mv.mark(lblContinue);
restoreEnvironment(savedEnv, mv);
}
/* steps 5.a-c */
mv.mark(test);
mv.load(iterator);
mv.lineInfo(node);
mv.invoke(Methods.Iterator_hasNext);
mv.ifne(enter);
/* steps 5.m-n */
if (lblBreak.isTarget()) {
mv.mark(lblBreak);
restoreEnvironment(savedEnv, mv);
}
mv.exitVariableScope();
return Completion.Normal;
}
/**
* 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation (lhs, stmt, iterator, lhsKind, labelSet)
* <p>
* stack: [Iterator] {@literal ->} []
*
* @param <FORSTATEMENT>
* the for-statement node type
* @param node
* the for-statement node
* @param mv
* the code visitor
* @return the completion value
*/
private <FORSTATEMENT extends IterationStatement & ForIterationNode> Completion AsyncForInOfBodyEvaluation(
FORSTATEMENT node, CodeVisitor mv) {
assert mv.getStackSize() == 1;
ContinueLabel lblContinue = new ContinueLabel();
BreakLabel lblBreak = new BreakLabel();
Jump enter = new Jump(), test = new Jump();
mv.enterVariableScope();
Variable<ScriptObject> iterator = mv.newVariable("iter", ScriptObject.class);
// stack: [Iterator] -> []
mv.store(iterator);
Variable<ScriptObject> nextResult = mv.newVariable("nextResult", ScriptObject.class);
mv.anull();
mv.store(nextResult);
Variable<Object> nextValue = mv.newVariable("nextValue", Object.class);
mv.anull();
mv.store(nextValue);
Variable<LexicalEnvironment<?>> savedEnv = saveEnvironment(node, mv);
/* step 1 (not applicable) */
/* step 2 */
if (node.hasCompletionValue()) {
mv.storeUndefinedAsCompletionValue();
}
/* steps 3-4 (not applicable) */
/* step 5 (repeat loop) */
mv.nonDestructiveGoTo(test);
/* steps 5.d-e */
mv.mark(enter);
IteratorValue(node, nextResult, mv);
await(node, mv);
mv.store(nextValue);
/* steps 5.f-l */
{
mv.enterIteration(node, lblBreak, lblContinue);
mv.enterWrapped();
new AbstractIterationGenerator<FORSTATEMENT, ScriptObject>(codegen) {
@Override
protected Completion iterationBody(FORSTATEMENT node, Variable<ScriptObject> iterator, CodeVisitor mv) {
return ForInOfBodyEvaluationInner(node, nextValue, mv);
}
@Override
protected MutableValue<Object> enterIteration(FORSTATEMENT node, CodeVisitor mv) {
return mv.enterIterationBody(node);
}
@Override
protected List<TempLabel> exitIteration(FORSTATEMENT node, CodeVisitor mv) {
return mv.exitIterationBody(node);
}
@Override
protected void IteratorClose(FORSTATEMENT node, Variable<ScriptObject> iterator,
Variable<? extends Throwable> throwable, CodeVisitor mv) {
asyncIteratorClose(node, iterator, throwable, mv);
}
@Override
protected void IteratorClose(FORSTATEMENT node, Variable<ScriptObject> iterator, CodeVisitor mv) {
asyncIteratorClose(node, iterator, mv);
}
}.generate(node, iterator, test, mv);
mv.exitWrapped();
mv.exitIteration(node);
}
/* steps 5.m-n */
if (lblContinue.isTarget()) {
mv.mark(lblContinue);
restoreEnvironment(savedEnv, mv);
}
/* steps 5.a-c */
mv.mark(test);
IteratorNext(node, iterator, mv);
await(node, mv);
// FIXME: spec bug - missing type check after await
requireObjectResult(node, "next", mv);
mv.store(nextResult);
IteratorComplete(node, nextResult, mv);
mv.ifeq(enter);
/* steps 5.m-n */
if (lblBreak.isTarget()) {
mv.mark(lblBreak);
restoreEnvironment(savedEnv, mv);
}
mv.exitVariableScope();
return Completion.Normal;
}
private <FORSTATEMENT extends IterationStatement & ForIterationNode> Completion ForInOfBodyEvaluationInner(
FORSTATEMENT node, Variable<Object> nextValue, CodeVisitor mv) {
BlockScope scope = node.getScope();
Node lhs = node.getHead();
/* steps 5.f-j */
if (lhs instanceof Expression) {
/* steps 5.f, 5.h-j */
LeftHandSideExpression lhsExpr = (LeftHandSideExpression) lhs;
if (!(lhsExpr instanceof AssignmentPattern)) {
ReferenceOp<LeftHandSideExpression> op = ReferenceOp.of(lhsExpr);
/* step 5.f.i.1 */
// stack: [] -> [<ref>]
ValType ref = op.reference(lhsExpr, mv, codegen);
/* steps 5.h.i, 5.h.iii */
// stack: [<ref>] -> []
mv.load(nextValue);
op.putValue(lhsExpr, ref, ValType.Any, mv);
} else {
/* step 5.i.i */
mv.load(nextValue);
DestructuringAssignment(codegen, (AssignmentPattern) lhs, mv);
}
} else if (lhs instanceof VariableStatement) {
/* steps 5.f, 5.h-j */
Binding binding = forVarDeclarationBinding((VariableStatement) lhs);
if (binding instanceof BindingIdentifier) {
BindingIdentifier bindingId = (BindingIdentifier) binding;
/* step 5.f.i.1 */
// 13.7.5.14 Runtime Semantics: Evaluation
IdReferenceOp op = IdReferenceOp.of(bindingId);
op.resolveBinding(bindingId, mv);
/* steps 5.h.i, 5.h.iii */
// stack: [<ref>] -> []
mv.load(nextValue);
op.putValue(bindingId, ValType.Any, mv);
} else {
/* step 5.i.ii */
mv.load(nextValue);
BindingInitialization(codegen, (BindingPattern) binding, mv);
}
} else {
/* step 5.g-j */
Binding binding = forDeclarationBinding((LexicalDeclaration) lhs);
Variable<DeclarativeEnvironmentRecord> envRec = null;
if (scope.isPresent()) {
mv.enterVariableScope();
envRec = mv.newVariable("envRec", DeclarativeEnvironmentRecord.class);
newDeclarativeEnvironment(scope, mv);
getEnvRec(envRec, mv);
BindingInstantiation(envRec, (LexicalDeclaration) lhs, mv);
pushLexicalEnvironment(mv);
}
mv.enterScope(node);
/* step 5.h */
if (binding instanceof BindingIdentifier) {
/* step 5.h.ii */
BindingIdentifier bindingId = (BindingIdentifier) binding;
Name name = bindingId.getName();
BindingOp<DeclarativeEnvironmentRecord> op = BindingOp.of(envRec, name);
op.initializeBinding(envRec, name, nextValue, mv);
} else {
/* step 5.i.iii */
// 13.7.5.9 Runtime Semantics: BindingInitialization
BindingInitialization(codegen, envRec, (BindingPattern) binding, nextValue, mv);
}
if (scope.isPresent()) {
mv.exitVariableScope();
}
}
/* step 5.k */
Completion result = node.getStatement().accept(this, mv);
/* step 5.l */
if (lhs instanceof LexicalDeclaration) {
mv.exitScope();
if (scope.isPresent() && !result.isAbrupt()) {
popLexicalEnvironment(mv);
}
}
return result;
}
private static Binding forVarDeclarationBinding(VariableStatement lhs) {
assert ((VariableStatement) lhs).getElements().size() == 1;
return ((VariableStatement) lhs).getElements().get(0).getBinding();
}
private static Binding forDeclarationBinding(LexicalDeclaration lhs) {
assert ((LexicalDeclaration) lhs).getElements().size() == 1;
return ((LexicalDeclaration) lhs).getElements().get(0).getBinding();
}
/**
* 13.7.5.10 Runtime Semantics: BindingInstantiation
*/
private void BindingInstantiation(Variable<DeclarativeEnvironmentRecord> envRec, LexicalDeclaration declaration,
CodeVisitor mv) {
boolean isConst = IsConstantDeclaration(declaration);
for (Name name : BoundNames(forDeclarationBinding(declaration))) {
BindingOp<DeclarativeEnvironmentRecord> op = BindingOp.of(envRec, name);
if (isConst) {
op.createImmutableBinding(envRec, name, true, mv);
} else {
op.createMutableBinding(envRec, name, false, mv);
}
}
}
/**
* 13.7.4 The for Statement
* <p>
* 13.1.8 Runtime Semantics: Evaluation<br>
* 13.1.7 Runtime Semantics: LabelledEvaluation<br>
* 13.7.4.7 Runtime Semantics: LabelledEvaluation
*/
@Override
public Completion visit(ForStatement node, CodeVisitor mv) {
assert mv.getStackSize() == 0;
boolean perIterationsLets = false;
BlockScope scope = node.getScope();
Node head = node.getHead();
if (head == null) {
// empty
} else if (head instanceof Expression) {
ValType type = expression(((Expression) head).emptyCompletion(), mv);
mv.pop(type);
} else if (head instanceof VariableStatement) {
head.accept(this, mv);
} else {
assert head instanceof LexicalDeclaration;
LexicalDeclaration lexDecl = (LexicalDeclaration) head;
List<Name> boundNames = BoundNames(lexDecl);
boolean isConst = IsConstantDeclaration(lexDecl);
perIterationsLets = !isConst && !boundNames.isEmpty();
if (scope.isPresent()) {
// stack: [] -> [loopEnv]
newDeclarativeEnvironment(scope, mv);
// stack: [loopEnv] -> [loopEnv]
mv.enterVariableScope();
Variable<DeclarativeEnvironmentRecord> envRec = mv.newVariable("envRec",
DeclarativeEnvironmentRecord.class);
getEnvRec(envRec, mv);
// stack: [loopEnv] -> [loopEnv]
for (Name dn : boundNames) {
BindingOp<DeclarativeEnvironmentRecord> op = BindingOp.of(envRec, dn);
if (isConst) {
op.createImmutableBinding(envRec, dn, true, mv);
} else {
op.createMutableBinding(envRec, dn, false, mv);
}
}
mv.exitVariableScope();
// stack: [loopEnv] -> []
pushLexicalEnvironment(mv);
}
mv.enterScope(node);
lexDecl.accept(this, mv);
}
Completion result = ForBodyEvaluation(node, perIterationsLets, mv);
if (head instanceof LexicalDeclaration) {
mv.exitScope();
if (scope.isPresent() && !result.isAbrupt()) {
popLexicalEnvironment(mv);
}
}
return result;
}
/**
* 13.7.4.8 Runtime Semantics: ForBodyEvaluation(test, increment, stmt, perIterationBindings,
* labelSet)
*/
private Completion ForBodyEvaluation(ForStatement node, boolean perIterationsLets, CodeVisitor mv) {
assert mv.getStackSize() == 0;
mv.enterVariableScope();
/* step 1 */
if (node.hasCompletionValue()) {
mv.storeUndefinedAsCompletionValue();
}
/* steps 2-3 */
Variable<LexicalEnvironment<?>> savedEnv;
if (perIterationsLets) {
savedEnv = mv.newVariable("savedEnv", LexicalEnvironment.class).uncheckedCast();
CreatePerIterationEnvironment(savedEnv, mv);
} else {
savedEnv = saveEnvironment(node, mv);
}
Jump lblTest = new Jump(), lblStmt = new Jump();
ContinueLabel lblContinue = new ContinueLabel();
BreakLabel lblBreak = new BreakLabel();
Bool btest = node.getTest() != null ? Bool.evaluate(node.getTest()) : Bool.True;
/* steps 4.b-d */
Completion result;
if (btest != Bool.True) {
mv.nonDestructiveGoTo(lblTest);
}
mv.mark(lblStmt);
{
mv.enterIteration(node, lblBreak, lblContinue);
result = node.getStatement().accept(this, mv);
mv.exitIteration(node);
}
/* step 4.c (abrupt completion - continue) */
if (lblContinue.isTarget()) {
mv.mark(lblContinue);
restoreEnvironment(savedEnv, mv);
}
/* steps 4.e-f */
if (perIterationsLets && (!result.isAbrupt() || lblContinue.isTarget())) {
CreatePerIterationEnvironment(savedEnv, mv);
}
/* step 4.g */
if (node.getStep() != null && (!result.isAbrupt() || lblContinue.isTarget())) {
ValType type = expression(node.getStep().emptyCompletion(), mv);
mv.pop(type);
}
/* step 4.a */
if (btest != Bool.True) {
mv.mark(lblTest);
ValType type = expression(node.getTest(), mv);
ToBoolean(type, mv);
mv.ifne(lblStmt);
} else {
mv.goTo(lblStmt);
}
/* step 4.c (abrupt completion - break) */
if (lblBreak.isTarget()) {
mv.mark(lblBreak);
restoreEnvironment(savedEnv, mv);
}
mv.exitVariableScope();
if (btest == Bool.True) {
if (!result.isAbrupt() && !lblBreak.isTarget()) {
return Completion.Abrupt; // infinite loop
}
return result.normal(lblBreak.isTarget());
}
return Completion.Normal;
}
/**
* 13.7.4.9 Runtime Semantics: CreatePerIterationEnvironment( perIterationBindings )
*
* @param savedEnv
* the variable which holds the saved environment
* @param mv
* the code visitor
*/
private void CreatePerIterationEnvironment(Variable<LexicalEnvironment<?>> savedEnv, CodeVisitor mv) {
// NB: Non-fallible operation as long as do-expressions in for-init cannot target current for-loop.
/* steps 1.a-e */
cloneDeclarativeEnvironment(mv);
mv.store(savedEnv);
/* step 1.f */
replaceLexicalEnvironment(savedEnv, mv);
/* step 2 (not applicable) */
}
/**
* 14.1.20 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(FunctionDeclaration node, CodeVisitor mv) {
/* B.3.3 Block-Level Function Declarations Web Legacy Compatibility Semantics */
if (node.isLegacyBlockScoped()) {
Name name = node.getIdentifier().getName();
TopLevelScope top = mv.getScope().getTop();
if (mv.isFunction()) {
assert top instanceof FunctionScope;
Name varName = ((FunctionScope) top).variableScope().resolveName(name, false);
assert varName != null && name != varName;
/* step 1.a.ii.3.1 */
Value<DeclarativeEnvironmentRecord> fenv = getVariableEnvironmentRecord(
Types.DeclarativeEnvironmentRecord, mv);
/* steps 1.a.ii.3.5-6 */
BindingOp.of(fenv, varName).setMutableBinding(fenv, varName, asm -> {
/* step 1.a.ii.3.2 */
Value<DeclarativeEnvironmentRecord> benv = getLexicalEnvironmentRecord(
Types.DeclarativeEnvironmentRecord, mv);
/* steps 1.a.ii.3.3-4 */
BindingOp.of(benv, name).getBindingValue(benv, name, false, mv);
}, false, mv);
} else {
assert top instanceof ScriptScope;
Name varName = name;
int functionId = node.getLegacyBlockScopeId();
Jump isLegacyScoped = null;
if (functionId > 0) {
isLegacyScoped = new Jump();
mv.loadExecutionContext();
mv.iconst(functionId);
mv.invoke(Methods.ScriptRuntime_isLegacyBlockFunction);
mv.ifeq(isLegacyScoped);
}
// The variable environment record is either:
// 1. The global environment record for global (eval) scripts.
// 2. Or a (function) declarative environment record for eval in functions.
// 3. Or a script-context environment record for eval in JSR-223 scripting.
Value<EnvironmentRecord> genv = getVariableEnvironmentRecord(Types.EnvironmentRecord, mv);
BindingOp.of(genv, varName).setMutableBinding(genv, varName, asm -> {
Value<DeclarativeEnvironmentRecord> benv = getLexicalEnvironmentRecord(
Types.DeclarativeEnvironmentRecord, mv);
BindingOp.of(benv, name).getBindingValue(benv, name, false, mv);
}, false, mv);
if (isLegacyScoped != null) {
mv.mark(isLegacyScoped);
}
}
}
/* step 1 */
return Completion.Empty;
}
/**
* 13.1.8 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(GeneratorDeclaration node, CodeVisitor mv) {
/* step 1 */
return Completion.Empty;
}
/**
* 13.6 The if Statement
* <p>
* 13.6.7 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(IfStatement node, CodeVisitor mv) {
Bool btest = Bool.evaluate(node.getTest());
if (btest != Bool.Any) {
if (node.hasCompletionValue()) {
mv.storeUndefinedAsCompletionValue();
}
if (btest == Bool.True) {
Completion resultThen = node.getThen().accept(this, mv);
return resultThen.nonEmpty();
}
if (node.getOtherwise() != null) {
Completion resultOtherwise = node.getOtherwise().accept(this, mv);
return resultOtherwise.nonEmpty();
}
return Completion.Normal;
}
/* steps 1-3 */
ValType type = expression(node.getTest(), mv);
ToBoolean(type, mv);
if (node.getOtherwise() != null) {
// IfStatement : if ( Expression ) Statement else Statement
Jump l0 = new Jump(), l1 = new Jump();
/* step 4 */
mv.ifeq(l0);
if (node.hasCompletionValue()) {
// TODO: Emit only when necessary, i.e. 'then' branch does not return a completion value.
mv.storeUndefinedAsCompletionValue();
}
Completion resultThen = node.getThen().accept(this, mv);
if (!resultThen.isAbrupt()) {
mv.goTo(l1);
}
/* step 5 */
mv.mark(l0);
if (node.hasCompletionValue()) {
// TODO: Emit only when necessary, i.e. 'otherwise' branch does not return a completion value.
mv.storeUndefinedAsCompletionValue();
}
Completion resultOtherwise = node.getOtherwise().accept(this, mv);
if (!resultThen.isAbrupt()) {
mv.mark(l1);
}
/* steps 6-8 */
return resultThen.select(resultOtherwise);
} else {
// IfStatement : if ( Expression ) Statement
Jump l0 = new Jump();
/* step 5 */
mv.ifeq(l0);
if (node.hasCompletionValue()) {
mv.storeUndefinedAsCompletionValue();
}
Completion resultThen = node.getThen().accept(this, mv);
if (node.hasCompletionValue() && mv.hasCompletion()) {
if (!resultThen.isAbrupt()) {
Jump l1 = new Jump();
mv.goTo(l1);
mv.mark(l0);
mv.storeUndefinedAsCompletionValue();
mv.mark(l1);
} else {
mv.mark(l0);
mv.storeUndefinedAsCompletionValue();
}
} else {
mv.mark(l0);
}
/* steps 4-5 */
return resultThen.select(Completion.Normal);
}
}
/**
* 15.2.1.20 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(ImportDeclaration node, CodeVisitor mv) {
return Completion.Empty;
}
/**
* 13.13 Labelled Statements
* <p>
* 13.13.15 Runtime Semantics: Evaluation<br>
* 13.13.14 Runtime Semantics: LabelledEvaluation
*/
@Override
public Completion visit(LabelledStatement node, CodeVisitor mv) {
assert mv.getStackSize() == 0;
mv.enterVariableScope();
Variable<LexicalEnvironment<?>> savedEnv = saveEnvironment(node, mv);
/* steps 1-3 */
BreakLabel label = new BreakLabel();
mv.enterLabelled(node, label);
Completion result = node.getStatement().accept(this, mv);
mv.exitLabelled(node);
/* step 4 */
if (label.isTarget()) {
mv.mark(label);
restoreEnvironment(savedEnv, mv);
}
mv.exitVariableScope();
/* steps 4-5 */
return result.normalOrEmpty(label.isTarget());
}
/**
* 13.13 Labelled Statements
* <p>
* 13.13.14 Runtime Semantics: LabelledEvaluation<br>
*
* <code>LabelledItem: FunctionDeclaration</code>
*/
@Override
public Completion visit(LabelledFunctionStatement node, CodeVisitor mv) {
return node.getFunction().accept(this, mv);
}
/**
* 13.3.1 Let and Const Declarations
* <p>
* 13.3.1.4 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(LexicalDeclaration node, CodeVisitor mv) {
mv.enterVariableScope();
Class<? extends EnvironmentRecord> envRecClass = getEnvironmentRecordClass(mv);
Variable<? extends EnvironmentRecord> envRec = mv.newVariable("envRec", envRecClass);
getLexicalEnvironmentRecord(envRec, mv);
/* steps 1-2 */
for (LexicalBinding lexical : node.getElements()) {
Binding binding = lexical.getBinding();
Expression initializer = lexical.getInitializer();
if (initializer == null) {
// LexicalBinding : BindingIdentifier
assert binding instanceof BindingIdentifier;
Name name = ((BindingIdentifier) binding).getName();
/* steps 1-2 */
assert mv.getScope().isDeclared(name);
InitializeBoundNameWithUndefined(envRec, name, mv);
} else if (binding instanceof BindingIdentifier) {
// LexicalBinding : BindingIdentifier Initializer
Name name = ((BindingIdentifier) binding).getName();
/* steps 1-7 */
InitializeBoundNameWithInitializer(codegen, envRec, name, initializer, mv);
} else {
// LexicalBinding : BindingPattern Initializer
assert binding instanceof BindingPattern;
/* steps 1-3 */
expressionBoxed(initializer, mv);
/* steps 4-5 */
BindingInitialization(codegen, envRec, (BindingPattern) binding, mv);
}
}
mv.exitVariableScope();
/* step 3 */
return Completion.Empty;
}
/**
* Extension: 'let' statement
*/
@Override
public Completion visit(LetStatement node, CodeVisitor mv) {
BlockScope scope = node.getScope();
if (scope.isPresent()) {
mv.enterVariableScope();
Variable<LexicalEnvironment<DeclarativeEnvironmentRecord>> env = mv.newVariable("env",
LexicalEnvironment.class).uncheckedCast();
Variable<DeclarativeEnvironmentRecord> envRec = mv.newVariable("envRec",
DeclarativeEnvironmentRecord.class);
newDeclarativeEnvironment(scope, mv);
mv.store(env);
getEnvRec(env, envRec, mv);
for (LexicalBinding lexical : node.getBindings()) {
Binding binding = lexical.getBinding();
Expression initializer = lexical.getInitializer();
for (Name name : BoundNames(binding)) {
BindingOp<DeclarativeEnvironmentRecord> op = BindingOp.of(envRec, name);
op.createMutableBinding(envRec, name, false, mv);
}
if (initializer == null) {
// LexicalBinding : BindingIdentifier
assert binding instanceof BindingIdentifier;
Name name = ((BindingIdentifier) binding).getName();
/* steps 1-2 */
InitializeBoundNameWithUndefined(envRec, name, mv);
} else if (binding instanceof BindingIdentifier) {
// LexicalBinding : BindingIdentifier Initializer
Name name = ((BindingIdentifier) binding).getName();
/* steps 1-7 */
InitializeBoundNameWithInitializer(codegen, envRec, name, initializer, mv);
} else {
// LexicalBinding : BindingPattern Initializer
assert binding instanceof BindingPattern;
/* steps 1-3 */
expressionBoxed(initializer, mv);
/* steps 4-5 */
BindingInitialization(codegen, envRec, (BindingPattern) binding, mv);
}
}
mv.load(env);
pushLexicalEnvironment(mv);
mv.exitVariableScope();
}
mv.enterScope(node);
Completion result = node.getStatement().accept(this, mv);
mv.exitScope();
if (scope.isPresent() && !result.isAbrupt()) {
popLexicalEnvironment(mv);
}
return result;
}
/**
* 13.10 The return Statement
* <p>
* 13.10.1 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(ReturnStatement node, CodeVisitor mv) {
Expression expr = node.getExpression();
if (expr == null) {
// ReturnStatement : return ;
/* step 1 */
mv.returnCompletion(mv.undefinedValue());
} else {
// ReturnStatement : return Expression;
/* steps 1-3 */
mv.enterTailCallPosition(expr);
expressionBoxed(expr, mv);
mv.exitTailCallPosition();
/* step 4 */
mv.returnCompletion();
}
return Completion.Return;
}
@Override
public Completion visit(StatementListMethod node, CodeVisitor mv) {
Entry<MethodName, LabelState> entry = codegen.compile(node, mv);
MethodName method = entry.getKey();
LabelState labelState = entry.getValue();
boolean hasCompletion = labelState.hasReturn() || (mv.hasCompletion() && node.hasCompletionValue());
boolean hasResume = node.hasResumePoint();
boolean hasTarget = hasResume || labelState.hasTargetInstruction();
mv.enterVariableScope();
Value<Object[]> completion;
if (hasCompletion) {
Variable<Object[]> completionVar = mv.newVariable("completion", Object[].class);
mv.anewarray(1, Types.Object);
mv.store(completionVar);
if (mv.hasCompletion()) {
mv.astore(completionVar, 0, mv.completionValue());
}
completion = completionVar;
} else {
completion = mv.anullValue();
}
MutableValue<Integer> target = hasTarget ? mv.newVariable("target", int.class) : new PopStoreValue<>();
// stack: [] -> []
mv.lineInfo(0); // 0 = hint for stacktraces to omit this frame
if (hasResume) {
mv.callWithSuspendInt(method, target, completion);
} else {
mv.callWithResult(method, target, completion);
}
Value<Object> completionValue = mv.arrayElement(completion, 0, Object.class);
if (node.hasCompletionValue()) {
mv.storeCompletionValue(completionValue);
}
mv.labelSwitch(labelState, target, completionValue, false);
mv.exitVariableScope();
return labelState.completion;
}
private static final class PopStoreValue<V> implements MutableValue<V> {
@Override
public void load(InstructionAssembler assembler) {
throw new AssertionError();
}
@Override
public void store(InstructionAssembler assembler) {
assembler.pop();
}
}
/**
* 13.12 The switch Statement
* <p>
* 13.12.11 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(SwitchStatement node, CodeVisitor mv) {
assert mv.getStackSize() == 0;
if (node.hasCompletionValue()) {
mv.storeUndefinedAsCompletionValue();
}
return node.accept(new SwitchStatementGenerator(codegen), mv);
}
/**
* 13.14 The throw Statement
* <p>
* 13.14.1 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(ThrowStatement node, CodeVisitor mv) {
/* steps 1-3 */
expressionBoxed(node.getExpression(), mv);
mv.lineInfo(node);
mv.invoke(Methods.ScriptException_create);
/* step 4 */
mv.athrow();
return Completion.Throw;
}
/**
* 13.15 The try Statement
* <p>
* 13.15.8 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(TryStatement node, CodeVisitor mv) {
assert mv.getStackSize() == 0;
if (node.hasCompletionValue()) {
mv.storeUndefinedAsCompletionValue();
}
boolean hasCatch = node.getCatchNode() != null || !node.getGuardedCatchNodes().isEmpty();
if (hasCatch && node.getFinallyBlock() != null) {
return visitTryCatchFinally(node, mv);
} else if (hasCatch) {
return visitTryCatch(node, mv);
} else {
return visitTryFinally(node, mv);
}
}
/**
* 13.15.8 Runtime Semantics: Evaluation<br>
*
* <code>try-catch-finally</code>
*
* @param node
* the try-statement
* @param mv
* the code visitor
* @return the completion value
*/
private Completion visitTryCatchFinally(TryStatement node, CodeVisitor mv) {
TryCatchLabel startCatchFinally = new TryCatchLabel();
TryCatchLabel endCatch = new TryCatchLabel(), handlerCatch = new TryCatchLabel();
TryCatchLabel endFinally = new TryCatchLabel(), handlerFinally = new TryCatchLabel();
TryCatchLabel handlerCatchStackOverflow = new TryCatchLabel();
TryCatchLabel handlerFinallyStackOverflow = new TryCatchLabel();
Jump noException = new Jump();
mv.enterVariableScope();
Variable<LexicalEnvironment<?>> savedEnv = saveEnvironment(mv);
MutableValue<Object> completion = mv.enterFinallyScoped(node);
/* step 1 */
// Emit try-block
mv.mark(startCatchFinally);
Completion tryResult = emitTryBlock(node, noException, mv);
mv.mark(endCatch);
/* steps 2-3 */
// Emit catch-block
Completion catchResult = emitCatchBlock(node, savedEnv, handlerCatch,
handlerCatchStackOverflow, mv);
if (!catchResult.isAbrupt()) {
mv.goTo(noException);
}
mv.mark(endFinally);
// Restore temporary abrupt targets
List<TempLabel> tempLabels = mv.exitFinallyScoped();
/* step 4 */
// Emit finally-block
Completion finallyResult = emitFinallyBlock(node, savedEnv, completion, tryResult, catchResult, handlerFinally,
handlerFinallyStackOverflow, noException, tempLabels, mv);
mv.exitVariableScope();
mv.tryCatch(startCatchFinally, endCatch, handlerCatch, Types.ScriptException);
mv.tryCatch(startCatchFinally, endCatch, handlerCatchStackOverflow, Types.Error);
mv.tryCatch(startCatchFinally, endFinally, handlerFinally, Types.ScriptException);
mv.tryCatch(startCatchFinally, endFinally, handlerFinallyStackOverflow, Types.Error);
/* steps 5-8 */
return finallyResult.then(tryResult.select(catchResult));
}
/**
* 13.15.8 Runtime Semantics: Evaluation<br>
*
* <code>try-catch</code>
*
* @param node
* the try-statement
* @param mv
* the code visitor
* @return the completion value
*/
private Completion visitTryCatch(TryStatement node, CodeVisitor mv) {
TryCatchLabel startCatch = new TryCatchLabel(), endCatch = new TryCatchLabel();
TryCatchLabel handlerCatch = new TryCatchLabel();
TryCatchLabel handlerCatchStackOverflow = new TryCatchLabel();
Jump exceptionHandled = new Jump();
mv.enterVariableScope();
Variable<LexicalEnvironment<?>> savedEnv = saveEnvironment(mv);
/* step 1 */
// Emit try-block
mv.mark(startCatch);
Completion tryResult = emitTryBlock(node, exceptionHandled, mv);
mv.mark(endCatch);
/* step 2 */
// Emit catch-block
Completion catchResult = emitCatchBlock(node, savedEnv, handlerCatch,
handlerCatchStackOverflow, mv);
/* step 3 */
if (!tryResult.isAbrupt()) {
mv.mark(exceptionHandled);
}
mv.exitVariableScope();
mv.tryCatch(startCatch, endCatch, handlerCatch, Types.ScriptException);
mv.tryCatch(startCatch, endCatch, handlerCatchStackOverflow, Types.Error);
/* steps 4-6 */
return tryResult.select(catchResult);
}
/**
* 13.15.8 Runtime Semantics: Evaluation<br>
*
* <code>try-finally</code>
*
* @param node
* the try-statement
* @param mv
* the code visitor
* @return the completion value
*/
private Completion visitTryFinally(TryStatement node, CodeVisitor mv) {
TryCatchLabel startFinally = new TryCatchLabel(), endFinally = new TryCatchLabel();
TryCatchLabel handlerFinally = new TryCatchLabel();
TryCatchLabel handlerFinallyStackOverflow = new TryCatchLabel();
Jump noException = new Jump();
mv.enterVariableScope();
Variable<LexicalEnvironment<?>> savedEnv = saveEnvironment(mv);
MutableValue<Object> completion = mv.enterFinallyScoped(node);
/* step 1 */
// Emit try-block
mv.mark(startFinally);
Completion tryResult = emitTryBlock(node, noException, mv);
mv.mark(endFinally);
// Restore temporary abrupt targets
List<TempLabel> tempLabels = mv.exitFinallyScoped();
/* step 2 */
// Emit finally-block
Completion finallyResult = emitFinallyBlock(node, savedEnv, completion, tryResult, Completion.Abrupt,
handlerFinally, handlerFinallyStackOverflow, noException, tempLabels, mv);
mv.exitVariableScope();
mv.tryCatch(startFinally, endFinally, handlerFinally, Types.ScriptException);
mv.tryCatch(startFinally, endFinally, handlerFinallyStackOverflow, Types.Error);
/* steps 3-6 */
return finallyResult.then(tryResult);
}
private Completion emitTryBlock(TryStatement node, Jump noException, CodeVisitor mv) {
mv.enterWrapped();
Completion tryResult = node.getTryBlock().accept(this, mv);
mv.exitWrapped();
if (!tryResult.isAbrupt()) {
mv.goTo(noException);
}
return tryResult.nonEmpty();
}
private Completion emitCatchBlock(TryStatement node, Variable<LexicalEnvironment<?>> savedEnv,
TryCatchLabel handlerCatch, TryCatchLabel handlerCatchStackOverflow, CodeVisitor mv) {
boolean hasFinally = node.getFinallyBlock() != null;
CatchNode catchNode = node.getCatchNode();
List<GuardedCatchNode> guardedCatchNodes = node.getGuardedCatchNodes();
assert catchNode != null || !guardedCatchNodes.isEmpty();
// StackOverflowError -> ScriptException
mv.catchHandler(handlerCatchStackOverflow, Types.Error);
mv.invoke(Methods.ScriptRuntime_stackOverflowError);
mv.loadExecutionContext();
mv.invoke(Methods.ScriptRuntime_toInternalError);
mv.catchHandler(handlerCatch, Types.ScriptException);
restoreEnvironment(savedEnv, mv);
if (hasFinally) {
mv.enterWrapped();
}
Completion catchResult;
if (!guardedCatchNodes.isEmpty()) {
mv.enterVariableScope();
Variable<ScriptException> exception = mv.newVariable("exception", ScriptException.class);
Jump catchWithGuardedLabel = new Jump();
mv.store(exception);
Completion result = null;
for (GuardedCatchNode guardedCatchNode : guardedCatchNodes) {
mv.load(exception);
Completion guardedResult = CatchClauseEvaluation(guardedCatchNode, catchWithGuardedLabel, mv);
result = result != null ? result.select(guardedResult) : guardedResult;
}
assert result != null;
if (catchNode != null) {
mv.load(exception);
catchResult = CatchClauseEvaluation(catchNode, mv);
} else {
mv.load(exception);
mv.athrow();
catchResult = Completion.Throw;
}
if (!result.isAbrupt()) {
mv.mark(catchWithGuardedLabel);
}
mv.exitVariableScope();
catchResult = catchResult.select(result);
} else {
catchResult = CatchClauseEvaluation(catchNode, mv);
}
if (hasFinally) {
mv.exitWrapped();
}
return catchResult.nonEmpty();
}
private Completion emitFinallyBlock(TryStatement node, Variable<LexicalEnvironment<?>> savedEnv,
Value<Object> completion, Completion tryResult, Completion catchResult, TryCatchLabel handlerFinally,
TryCatchLabel handlerFinallyStackOverflow, Jump noException, List<TempLabel> tempLabels, CodeVisitor mv) {
BlockStatement finallyBlock = node.getFinallyBlock();
assert finallyBlock != null;
// various finally blocks (1 - 4)
// (1) finally block for abrupt throw completions within 'try-catch'
mv.enterVariableScope();
Variable<Throwable> throwable = mv.newVariable("throwable", Throwable.class);
mv.catchHandler(handlerFinallyStackOverflow, Types.Error);
mv.invoke(Methods.ScriptRuntime_stackOverflowError);
mv.catchHandler(handlerFinally, Types.ScriptException);
mv.store(throwable);
restoreEnvironment(savedEnv, mv);
Completion finallyResult = finallyBlock.accept(this, mv);
if (!finallyResult.isAbrupt()) {
mv.load(throwable);
mv.athrow();
}
mv.exitVariableScope();
// (2) finally block if 'try' did not complete abruptly
// (3) finally block if 'catch' did not complete abruptly
Jump exceptionHandled = null;
if (!tryResult.isAbrupt() || !catchResult.isAbrupt()) {
mv.mark(noException);
finallyBlock.accept(this, mv);
if (!finallyResult.isAbrupt()) {
if (node.hasCompletionValue()) {
mv.storeCompletionValue(completion);
}
if (!tempLabels.isEmpty()) {
exceptionHandled = new Jump();
mv.goTo(exceptionHandled);
}
}
}
// (4) finally blocks for other abrupt completion (return, break, continue)
for (TempLabel temp : tempLabels) {
if (temp.isTarget()) {
mv.mark(temp);
restoreEnvironment(savedEnv, mv);
finallyBlock.accept(this, mv);
if (!finallyResult.isAbrupt()) {
if (node.hasCompletionValue()) {
mv.storeCompletionValue(completion);
}
mv.goTo(temp, completion);
}
}
}
if (exceptionHandled != null) {
mv.mark(exceptionHandled);
}
return finallyResult.nonEmpty();
}
/**
* 13.15.7 Runtime Semantics: CatchClauseEvaluation
*/
private Completion CatchClauseEvaluation(CatchNode node, CodeVisitor mv) {
/* steps 1-6 */
enterCatchScope(node, mv);
/* step 7 */
Completion result = node.getCatchBlock().accept(this, mv);
/* step 8 */
exitCatchScope(node, result, mv);
/* step 9 */
return result;
}
/**
* Extension: 'catch-if' statement
*/
private Completion CatchClauseEvaluation(GuardedCatchNode node, Jump catchWithGuardedLabel, CodeVisitor mv) {
/* steps 1-6 */
enterCatchScope(node, mv);
/* step 7 */
Jump l0 = new Jump();
Completion result;
ToBoolean(expression(node.getGuard(), mv), mv);
mv.ifeq(l0);
{
result = node.getCatchBlock().accept(this, mv);
if (!result.isAbrupt()) {
if (node.getScope().isPresent()) {
popLexicalEnvironment(mv);
}
mv.goTo(catchWithGuardedLabel);
}
}
mv.mark(l0);
/* step 8 */
exitCatchScope(node, Completion.Normal, mv);
/* step 9 */
return result;
}
private void enterCatchScope(CatchClause node, CodeVisitor mv) {
BlockScope scope = node.getScope();
Binding catchParameter = node.getCatchParameter();
/* steps 1-6 */
// stack: [e] -> []
mv.enterVariableScope();
{
Variable<Object> exception = mv.newVariable("exception", Object.class);
mv.invoke(Methods.ScriptException_getValue);
mv.store(exception);
/* step 1 (not applicable) */
/* steps 2-4 */
Variable<DeclarativeEnvironmentRecord> envRec = null;
if (scope.isPresent()) {
/* step 2 */
// stack: [] -> [catchEnv]
newCatchEnvironment(catchParameter, scope, mv);
envRec = mv.newVariable("envRec", DeclarativeEnvironmentRecord.class);
getEnvRec(envRec, mv);
/* step 3 */
for (Name name : BoundNames(catchParameter)) {
BindingOp<DeclarativeEnvironmentRecord> op = BindingOp.of(envRec, name);
op.createMutableBinding(envRec, name, false, mv);
}
/* step 4 */
// stack: [catchEnv] -> []
pushLexicalEnvironment(mv);
}
mv.enterScope(node);
/* steps 5-6 */
// stack: [ex] -> []
BindingInitialization(codegen, envRec, catchParameter, exception, mv);
}
mv.exitVariableScope();
}
private void exitCatchScope(CatchClause node, Completion result, CodeVisitor mv) {
mv.exitScope();
if (node.getScope().isPresent() && !result.isAbrupt()) {
popLexicalEnvironment(mv);
}
}
private void newCatchEnvironment(Binding catchParameter, BlockScope scope, CodeVisitor mv) {
if (codegen.isEnabled(CompatibilityOption.CatchVarPattern)) {
if (catchParameter instanceof BindingPattern) {
newDeclarativeEnvironment(scope, mv);
return;
}
}
if (codegen.isEnabled(CompatibilityOption.CatchVarStatement)) {
newCatchDeclarativeEnvironment(scope, mv);
return;
}
newDeclarativeEnvironment(scope, mv);
}
/**
* 13.3.2 Variable Statement
* <p>
* 13.3.2.4 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(VariableDeclaration node, CodeVisitor mv) {
Binding binding = node.getBinding();
Expression initializer = node.getInitializer();
if (initializer == null) {
// VariableDeclaration : BindingIdentifier
assert binding instanceof BindingIdentifier;
/* step 1 (return) */
} else if (binding instanceof BindingIdentifier) {
// VariableDeclaration : BindingIdentifier Initializer
/* step 1 */
BindingIdentifier bindingId = (BindingIdentifier) binding;
/* steps 2-3 */
IdReferenceOp op = IdReferenceOp.of(bindingId);
op.resolveBinding(bindingId, mv);
/* steps 4-6 */
ValType type = expression(initializer, mv);
/* step 7 */
if (IsAnonymousFunctionDefinition(initializer)) {
SetFunctionName(initializer, bindingId.getName(), mv);
}
/* step 8 */
op.putValue(bindingId, type, mv);
} else {
// VariableDeclaration : BindingPattern Initializer
assert binding instanceof BindingPattern;
/* steps 1-3 */
expressionBoxed(initializer, mv);
/* step 4 */
BindingInitialization(codegen, (BindingPattern) binding, mv);
}
return Completion.Empty;
}
/**
* 13.3.2 Variable Statement
* <p>
* 13.3.2.4 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(VariableStatement node, CodeVisitor mv) {
/* steps 1-2 */
for (VariableDeclaration decl : node.getElements()) {
decl.accept(this, mv);
}
/* step 3 */
return Completion.Empty;
}
/**
* 13.7.3 The while Statement
* <p>
* 13.1.8 Runtime Semantics: Evaluation<br>
* 13.1.7 Runtime Semantics: LabelledEvaluation<br>
* 13.7.3.6 Runtime Semantics: LabelledEvaluation
*/
@Override
public Completion visit(WhileStatement node, CodeVisitor mv) {
assert mv.getStackSize() == 0;
Jump lblNext = new Jump(), lblTest = new Jump();
ContinueLabel lblContinue = new ContinueLabel();
BreakLabel lblBreak = new BreakLabel();
Bool btest = Bool.evaluate(node.getTest());
mv.enterVariableScope();
Variable<LexicalEnvironment<?>> savedEnv = saveEnvironment(node, mv);
/* step 1 */
if (node.hasCompletionValue()) {
mv.storeUndefinedAsCompletionValue();
}
/* step 2 (repeat loop) */
if (btest != Bool.True) {
mv.nonDestructiveGoTo(lblTest);
}
mv.mark(lblNext);
/* steps 2.e-g */
Completion result;
{
mv.enterIteration(node, lblBreak, lblContinue);
result = node.getStatement().accept(this, mv);
mv.exitIteration(node);
}
/* step 2.f (abrupt completion - continue) */
if (lblContinue.isTarget()) {
mv.mark(lblContinue);
restoreEnvironment(savedEnv, mv);
}
/* steps 2.a-d */
if (btest != Bool.True) {
mv.mark(lblTest);
ValType type = expression(node.getTest(), mv);
ToBoolean(type, mv);
mv.ifne(lblNext);
} else if (!result.isAbrupt() || lblContinue.isTarget()) {
mv.goTo(lblNext);
}
/* step 2.f (abrupt completion - break) */
if (lblBreak.isTarget()) {
mv.mark(lblBreak);
restoreEnvironment(savedEnv, mv);
}
mv.exitVariableScope();
/* steps 2.d, 2.f */
if (btest == Bool.True) {
if (!result.isAbrupt() && !lblBreak.isTarget()) {
return Completion.Abrupt; // infinite loop
}
return result.normal(lblBreak.isTarget());
}
return Completion.Normal;
}
/**
* 13.11 The with Statement
* <p>
* 13.11.7 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(WithStatement node, CodeVisitor mv) {
/* step 1 */
ValType type = expression(node.getExpression(), mv);
/* steps 2-3 */
ToObject(node, type, mv);
/* steps 4-7 */
newObjectEnvironment(mv, true);
pushLexicalEnvironment(mv);
/* step 8 */
mv.enterScope(node);
if (node.hasCompletionValue()) {
mv.storeUndefinedAsCompletionValue();
}
Completion result = node.getStatement().accept(this, mv);
mv.exitScope();
/* step 9 */
if (!result.isAbrupt()) {
popLexicalEnvironment(mv);
}
/* steps 10-11 */
return result.nonEmpty();
}
}