/**
* 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.semantics.StaticSemantics.*;
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import com.github.anba.es6draft.ast.*;
import com.github.anba.es6draft.ast.scope.FunctionScope;
import com.github.anba.es6draft.ast.scope.Name;
import com.github.anba.es6draft.compiler.CodeGenerator.FunctionName;
import com.github.anba.es6draft.compiler.assembler.Code.MethodCode;
import com.github.anba.es6draft.compiler.assembler.MethodName;
import com.github.anba.es6draft.compiler.assembler.Type;
import com.github.anba.es6draft.compiler.assembler.Variable;
import com.github.anba.es6draft.runtime.DeclarativeEnvironmentRecord;
import com.github.anba.es6draft.runtime.ExecutionContext;
import com.github.anba.es6draft.runtime.FunctionEnvironmentRecord;
import com.github.anba.es6draft.runtime.LexicalEnvironment;
import com.github.anba.es6draft.runtime.internal.CompatibilityOption;
import com.github.anba.es6draft.runtime.types.Undefined;
import com.github.anba.es6draft.runtime.types.builtins.ArgumentsObject;
import com.github.anba.es6draft.runtime.types.builtins.FunctionObject;
import com.github.anba.es6draft.runtime.types.builtins.LegacyConstructorFunction;
import com.github.anba.es6draft.runtime.types.builtins.OrdinaryAsyncFunction;
import com.github.anba.es6draft.runtime.types.builtins.OrdinaryAsyncGenerator;
import com.github.anba.es6draft.runtime.types.builtins.OrdinaryConstructorFunction;
import com.github.anba.es6draft.runtime.types.builtins.OrdinaryConstructorGenerator;
import com.github.anba.es6draft.runtime.types.builtins.OrdinaryFunction;
import com.github.anba.es6draft.runtime.types.builtins.OrdinaryGenerator;
/**
* <h1>9 Ordinary and Exotic Objects Behaviours</h1><br>
* <h2>9.2 ECMAScript Function Objects</h2>
* <ul>
* <li>9.2.13 FunctionDeclarationInstantiation(func, argumentsList)
* </ul>
*/
final class FunctionDeclarationInstantiationGenerator extends
DeclarationBindingInstantiationGenerator {
private static final class Methods {
// class: Arrays
static final MethodName Arrays_asList = MethodName.findStatic(Types.Arrays, "asList",
Type.methodType(Types.List, Types.Object_));
// class: ExecutionContext
static final MethodName ExecutionContext_setLexicalEnvironment = MethodName.findVirtual(Types.ExecutionContext,
"setLexicalEnvironment", Type.methodType(Type.VOID_TYPE, Types.LexicalEnvironment));
static final MethodName ExecutionContext_setVariableEnvironment = MethodName.findVirtual(Types.ExecutionContext,
"setVariableEnvironment", Type.methodType(Type.VOID_TYPE, Types.LexicalEnvironment));
// class: ArgumentsObject
static final MethodName ArgumentsObject_CreateMappedArgumentsObject_Empty = MethodName.findStatic(
Types.ArgumentsObject, "CreateMappedArgumentsObject",
Type.methodType(Types.ArgumentsObject, Types.ExecutionContext, Types.FunctionObject, Types.Object_));
static final MethodName ArgumentsObject_CreateMappedArgumentsObject = MethodName
.findStatic(Types.ArgumentsObject, "CreateMappedArgumentsObject", Type.methodType(Types.ArgumentsObject,
Types.ExecutionContext, Types.FunctionObject, Types.Object_, Types.LexicalEnvironment));
static final MethodName ArgumentsObject_CreateUnmappedArgumentsObject = MethodName.findStatic(
Types.ArgumentsObject, "CreateUnmappedArgumentsObject",
Type.methodType(Types.ArgumentsObject, Types.ExecutionContext, Types.Object_));
// class: LexicalEnvironment
static final MethodName LexicalEnvironment_newDeclarativeEnvironment = MethodName.findStatic(
Types.LexicalEnvironment, "newDeclarativeEnvironment",
Type.methodType(Types.LexicalEnvironment, Types.LexicalEnvironment));
// class: List
static final MethodName List_iterator = MethodName.findInterface(Types.List, "iterator",
Type.methodType(Types.Iterator));
}
private static final int EXECUTION_CONTEXT = 0;
private static final int FUNCTION = 1;
private static final int ARGUMENTS = 2;
private static final class FunctionDeclInitMethodGenerator extends CodeVisitor {
private final String name;
private final Type type;
FunctionDeclInitMethodGenerator(MethodCode method, FunctionNode node, Type type) {
super(method, node);
this.name = targetName(node);
this.type = type;
}
@Override
public void begin() {
super.begin();
setParameterName("cx", EXECUTION_CONTEXT, Types.ExecutionContext);
setParameterName(name, FUNCTION, type);
setParameterName("arguments", ARGUMENTS, Types.Object_);
}
}
FunctionDeclarationInstantiationGenerator(CodeGenerator codegen) {
super(codegen);
}
void generate(FunctionNode function) {
MethodCode method = codegen.newMethod(function, FunctionName.Init);
CodeVisitor mv = new FunctionDeclInitMethodGenerator(method, function, targetType(function));
mv.lineInfo(function);
mv.begin();
mv.enterScope(function);
generate(function, mv);
mv.exitScope();
mv.end();
}
private Type targetType(FunctionNode node) {
if (node.isAsync() && node.isGenerator()) {
return Types.OrdinaryAsyncGenerator;
} else if (node.isGenerator()) {
if (node.isConstructor()) {
return Types.OrdinaryConstructorGenerator;
}
return Types.OrdinaryGenerator;
} else if (node.isAsync()) {
return Types.OrdinaryAsyncFunction;
} else if (isLegacy(node)) {
return Types.LegacyConstructorFunction;
} else if (node.isConstructor() || isCallConstructor(node)) {
return Types.OrdinaryConstructorFunction;
} else {
return Types.OrdinaryFunction;
}
}
private Class<? extends FunctionObject> targetClass(FunctionNode node) {
if (node.isAsync() && node.isGenerator()) {
return OrdinaryAsyncGenerator.class;
} else if (node.isGenerator()) {
if (node.isConstructor()) {
return OrdinaryConstructorGenerator.class;
}
return OrdinaryGenerator.class;
} else if (node.isAsync()) {
return OrdinaryAsyncFunction.class;
} else if (isLegacy(node)) {
return LegacyConstructorFunction.class;
} else if (node.isConstructor() || isCallConstructor(node)) {
return OrdinaryConstructorFunction.class;
} else {
return OrdinaryFunction.class;
}
}
private static String targetName(FunctionNode node) {
if (node.isGenerator()) {
return "generator";
} else {
return "function";
}
}
private void generate(FunctionNode function, CodeVisitor mv) {
Variable<ExecutionContext> context = mv.getParameter(EXECUTION_CONTEXT, ExecutionContext.class);
Variable<LexicalEnvironment<FunctionEnvironmentRecord>> env = mv.newVariable("env", LexicalEnvironment.class)
.uncheckedCast();
Variable<FunctionEnvironmentRecord> envRec = mv.newVariable("envRec", FunctionEnvironmentRecord.class);
Variable<FunctionObject> fo = null;
Variable<Undefined> undefined = mv.newVariable("undef", Undefined.class);
mv.loadUndefined();
mv.store(undefined);
FunctionScope fscope = function.getScope();
boolean hasParameters = !function.getParameters().getFormals().isEmpty();
Variable<Iterator<?>> iterator = null;
if (hasParameters) {
iterator = mv.newVariable("iterator", Iterator.class).uncheckedCast();
mv.loadParameter(ARGUMENTS, Object[].class);
mv.invoke(Methods.Arrays_asList);
mv.invoke(Methods.List_iterator);
mv.store(iterator);
}
/* step 1 (omitted) */
/* step 2 */
getLexicalEnvironment(context, env, mv);
/* step 3 */
getEnvironmentRecord(env, envRec, mv);
/* step 4 */
// RuntimeInfo.Function code = func.getCode();
/* step 5 */
boolean strict = IsStrict(function);
/* step 6 */
FormalParameterList formals = function.getParameters();
/* step 7 */
List<Name> parameterNames = BoundNames(formals);
HashSet<Name> parameterNamesSet = new HashSet<>(parameterNames);
/* step 8 */
boolean hasDuplicates = parameterNames.size() != parameterNamesSet.size();
/* step 9 */
boolean simpleParameterList = IsSimpleParameterList(formals);
/* step 10 */
boolean hasParameterExpressions = ContainsExpression(formals);
// invariant: hasDuplicates => simpleParameterList
assert !hasDuplicates || simpleParameterList;
// invariant: hasParameterExpressions => !simpleParameterList
assert !hasParameterExpressions || !simpleParameterList;
/* step 11 */
Set<Name> varNames = VarDeclaredNames(function);
/* step 12 */
List<StatementListItem> varDeclarations = VarScopedDeclarations(function);
/* step 13 */
Set<Name> lexicalNames = LexicallyDeclaredNames(function);
/* step 14 */
HashSet<Name> functionNames = new HashSet<>();
/* step 15 */
ArrayDeque<HoistableDeclaration> functionsToInitialize = new ArrayDeque<>();
/* step 16 */
for (StatementListItem item : reverse(varDeclarations)) {
if (item instanceof HoistableDeclaration) {
HoistableDeclaration d = (HoistableDeclaration) item;
Name fn = BoundName(d);
if (functionNames.add(fn)) {
functionsToInitialize.addFirst(d);
}
}
}
if (!functionsToInitialize.isEmpty()) {
fo = mv.newVariable("fo", FunctionObject.class);
}
/* step 17 */
// Optimization: Skip 'arguments' allocation if it's not referenced within the function.
boolean argumentsObjectNeeded = function.getScope().needsArguments();
Name arguments = function.getScope().arguments();
argumentsObjectNeeded &= arguments != null;
/* step 18 */
if (function.getThisMode() == FunctionNode.ThisMode.Lexical) {
argumentsObjectNeeded = false;
}
/* step 19 */
else if (parameterNamesSet.contains(arguments)) {
argumentsObjectNeeded = false;
}
/* step 20 */
else if (!hasParameterExpressions) {
if (functionNames.contains(arguments) || lexicalNames.contains(arguments)) {
argumentsObjectNeeded = false;
}
}
/* step 21 */
for (Name paramName : function.getScope().parameterNames()) {
BindingOp<FunctionEnvironmentRecord> op = BindingOp.of(envRec, paramName);
op.createMutableBinding(envRec, paramName, false, mv);
if (hasDuplicates) {
op.initializeBinding(envRec, paramName, undefined, mv);
}
}
/* step 22 */
if (argumentsObjectNeeded) {
assert arguments != null;
Variable<ArgumentsObject> argumentsObj = mv.newVariable("argumentsObj", ArgumentsObject.class);
if (strict || !simpleParameterList) {
CreateUnmappedArgumentsObject(mv);
} else if (formals.getFormals().isEmpty()) {
CreateMappedArgumentsObject(mv);
} else {
CreateMappedArgumentsObject(env, formals, mv);
}
mv.store(argumentsObj);
BindingOp<FunctionEnvironmentRecord> op = BindingOp.of(envRec, arguments);
if (strict) {
op.createImmutableBinding(envRec, arguments, false, mv);
} else {
op.createMutableBinding(envRec, arguments, false, mv);
}
op.initializeBinding(envRec, arguments, argumentsObj, mv);
parameterNames.add(arguments);
parameterNamesSet.add(arguments);
}
/* step 23 (not applicable) */
/* steps 24-26 */
if (hasParameters) {
if (hasDuplicates) {
/* step 24 */
BindingInitialization(codegen, function, env, iterator, mv);
} else {
/* step 25 */
BindingInitialization(codegen, function, env, envRec, iterator, mv);
}
}
/* steps 27-28 */
HashSet<Name> instantiatedVarNames;
Variable<? extends LexicalEnvironment<?>> varEnv;
Variable<? extends DeclarativeEnvironmentRecord> varEnvRec;
if (!hasParameterExpressions) {
assert fscope == fscope.variableScope();
/* step 27.a (note) */
/* step 27.b */
instantiatedVarNames = new HashSet<>(parameterNames);
/* step 27.c */
for (Name varName : varNames) {
if (instantiatedVarNames.add(varName)) {
BindingOp<FunctionEnvironmentRecord> op = BindingOp.of(envRec, varName);
op.createMutableBinding(envRec, varName, false, mv);
op.initializeBinding(envRec, varName, undefined, mv);
}
}
/* steps 27.d-27.e */
varEnv = env;
varEnvRec = envRec;
} else {
assert fscope != fscope.variableScope();
mv.enterScope(fscope.variableScope());
/* step 28.a (note) */
/* step 28.b */
varEnv = mv.newVariable("varEnv", LexicalEnvironment.class).uncheckedCast();
newDeclarativeEnvironment(env, mv);
mv.store(varEnv);
/* step 28.c */
varEnvRec = mv.newVariable("varEnvRec", DeclarativeEnvironmentRecord.class);
getEnvironmentRecord(varEnv, varEnvRec, mv);
/* step 28.d */
setVariableEnvironment(varEnv, mv);
/* step 28.e */
instantiatedVarNames = new HashSet<>();
/* step 28.f */
Variable<Object> tempValue = null;
for (Name varName : varNames) {
if (instantiatedVarNames.add(varName)) {
BindingOp<DeclarativeEnvironmentRecord> op = BindingOp.of(varEnvRec, varName);
op.createMutableBinding(varEnvRec, varName, false, mv);
if (!parameterNamesSet.contains(varName) || functionNames.contains(varName)) {
op.initializeBinding(varEnvRec, varName, undefined, mv);
} else {
BindingOp.of(envRec, varName).getBindingValue(envRec, varName, strict, mv);
if (tempValue == null) {
tempValue = mv.newVariable("tempValue", Object.class);
}
mv.store(tempValue);
op.initializeBinding(varEnvRec, varName, tempValue, mv);
}
}
}
}
/* step 29 (B.3.3 Block-Level Function Declarations Web Legacy Compatibility Semantics) */
for (Name fname : function.getScope().blockFunctionNames()) {
if (instantiatedVarNames.add(fname)) {
BindingOp<DeclarativeEnvironmentRecord> op = BindingOp.of(varEnvRec, fname);
op.createMutableBinding(varEnvRec, fname, false, mv);
op.initializeBinding(varEnvRec, fname, undefined, mv);
}
}
/* steps 30-32 */
Variable<? extends LexicalEnvironment<?>> lexEnv;
Variable<? extends DeclarativeEnvironmentRecord> lexEnvRec;
assert strict || fscope.variableScope() != fscope.lexicalScope();
if (!strict || fscope.variableScope() != fscope.lexicalScope()) {
// NB: Scopes are unmodifiable once constructed, that means we need to emit the extra
// scope for functions with deferred strict-ness, even if this scope is not present in
// the specification.
mv.enterScope(fscope.lexicalScope());
if (!lexicalNames.isEmpty()) {
/* step 30 */
lexEnv = mv.newVariable("lexEnv", LexicalEnvironment.class).uncheckedCast();
newDeclarativeEnvironment(varEnv, mv);
mv.store(lexEnv);
/* step 32 */
lexEnvRec = mv.newVariable("lexEnvRec", DeclarativeEnvironmentRecord.class);
getEnvironmentRecord(lexEnv, lexEnvRec, mv);
} else {
// Optimization: Skip environment allocation if no lexical names are defined.
/* step 30 */
lexEnv = varEnv;
/* step 32 */
lexEnvRec = varEnvRec;
}
} else {
/* step 30 */
lexEnv = varEnv;
/* step 32 */
lexEnvRec = varEnvRec;
}
/* step 33 */
if (lexEnv != env) {
setLexicalEnvironment(lexEnv, mv);
}
/* step 34 */
List<Declaration> lexDeclarations = LexicallyScopedDeclarations(function);
/* step 35 */
for (Declaration d : lexDeclarations) {
assert !(d instanceof HoistableDeclaration);
for (Name dn : BoundNames(d)) {
BindingOp<DeclarativeEnvironmentRecord> op = BindingOp.of(lexEnvRec, dn);
if (d.isConstDeclaration()) {
op.createImmutableBinding(lexEnvRec, dn, true, mv);
} else {
op.createMutableBinding(lexEnvRec, dn, false, mv);
}
}
}
/* step 36 */
for (HoistableDeclaration f : functionsToInitialize) {
Name fn = BoundName(f);
// stack: [] -> [fo]
InstantiateFunctionObject(context, lexEnv, f, mv);
mv.store(fo);
// stack: [fo] -> []
// Resolve the actual binding name: function(a){ function a(){} }
// TODO: Can be removed when StaticIdResolution handles this case.
Name name = fscope.variableScope().resolveName(fn, false);
BindingOp<DeclarativeEnvironmentRecord> op = BindingOp.of(varEnvRec, name);
op.setMutableBinding(varEnvRec, name, fo, false, mv);
}
/* step 37 */
mv._return();
}
private void newDeclarativeEnvironment(Variable<? extends LexicalEnvironment<?>> env, CodeVisitor mv) {
// stack: [] -> [env]
mv.load(env);
mv.invoke(Methods.LexicalEnvironment_newDeclarativeEnvironment);
}
private void setVariableEnvironment(Variable<? extends LexicalEnvironment<?>> env, CodeVisitor mv) {
// stack: [] -> []
mv.loadExecutionContext();
mv.load(env);
mv.invoke(Methods.ExecutionContext_setVariableEnvironment);
}
private void setLexicalEnvironment(Variable<? extends LexicalEnvironment<?>> env, CodeVisitor mv) {
// stack: [] -> []
mv.loadExecutionContext();
mv.load(env);
mv.invoke(Methods.ExecutionContext_setLexicalEnvironment);
}
private void CreateMappedArgumentsObject(CodeVisitor mv) {
// stack: [] -> [argsObj]
mv.loadExecutionContext();
mv.loadParameter(FUNCTION, targetClass((FunctionNode) mv.getTopLevelNode()));
mv.loadParameter(ARGUMENTS, Object[].class);
mv.invoke(Methods.ArgumentsObject_CreateMappedArgumentsObject_Empty);
}
private void CreateMappedArgumentsObject(Variable<LexicalEnvironment<FunctionEnvironmentRecord>> env,
FormalParameterList formals, CodeVisitor mv) {
// stack: [] -> [argsObj]
mv.loadExecutionContext();
mv.loadParameter(FUNCTION, targetClass((FunctionNode) mv.getTopLevelNode()));
mv.loadParameter(ARGUMENTS, Object[].class);
mv.load(env);
mv.invoke(Methods.ArgumentsObject_CreateMappedArgumentsObject);
}
private void CreateUnmappedArgumentsObject(CodeVisitor mv) {
// stack: [] -> [argsObj]
mv.loadExecutionContext();
mv.loadParameter(ARGUMENTS, Object[].class);
mv.invoke(Methods.ArgumentsObject_CreateUnmappedArgumentsObject);
}
private boolean isLegacy(FunctionNode node) {
if (IsStrict(node)) {
return false;
}
if (!(node instanceof FunctionDeclaration || node instanceof FunctionExpression)) {
return false;
}
return codegen.isEnabled(CompatibilityOption.FunctionArguments)
|| codegen.isEnabled(CompatibilityOption.FunctionCaller);
}
private boolean isCallConstructor(FunctionNode node) {
if (node instanceof MethodDefinition) {
return ((MethodDefinition) node).isCallConstructor();
}
return false;
}
}