/**
* 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.*;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.HashSet;
import java.util.List;
import java.util.function.BiConsumer;
import com.github.anba.es6draft.ast.*;
import com.github.anba.es6draft.ast.scope.Name;
import com.github.anba.es6draft.compiler.CodeGenerator.FunctionName;
import com.github.anba.es6draft.compiler.CodeGenerator.ModuleName;
import com.github.anba.es6draft.compiler.CodeGenerator.ScriptName;
import com.github.anba.es6draft.compiler.assembler.Code.MethodCode;
import com.github.anba.es6draft.compiler.assembler.Handle;
import com.github.anba.es6draft.compiler.assembler.InstructionAssembler;
import com.github.anba.es6draft.compiler.assembler.MethodName;
import com.github.anba.es6draft.compiler.assembler.Type;
import com.github.anba.es6draft.parser.Parser;
import com.github.anba.es6draft.runtime.internal.CompatibilityOption;
import com.github.anba.es6draft.runtime.internal.RuntimeInfo;
import com.github.anba.es6draft.runtime.internal.RuntimeInfo.FunctionFlags;
/**
*
*/
final class RuntimeInfoGenerator {
private static final class Methods {
// class: DebugInfo
static final MethodName DebugInfo_init = MethodName.findConstructor(Types.DebugInfo,
Type.methodType(Type.VOID_TYPE));
static final MethodName DebugInfo_addMethod = MethodName.findVirtual(Types.DebugInfo, "addMethod",
Type.methodType(Type.VOID_TYPE, Types.MethodHandle));
// class: RuntimeInfo
static final MethodName RTI_newScriptBody = MethodName.findStatic(Types.RuntimeInfo, "newScriptBody",
Type.methodType(Types.RuntimeInfo$ScriptBody, Types.String, Types.String, Types.MethodHandle));
static final MethodName RTI_newScriptBodyDebug = MethodName.findStatic(Types.RuntimeInfo, "newScriptBody",
Type.methodType(Types.RuntimeInfo$ScriptBody, Types.String, Types.String, Types.MethodHandle,
Types.MethodHandle));
static final MethodName RTI_newModuleBody = MethodName.findStatic(Types.RuntimeInfo, "newModuleBody",
Type.methodType(Types.RuntimeInfo$ModuleBody, Types.String, Types.String, Types.MethodHandle,
Types.MethodHandle));
static final MethodName RTI_newModuleBodyDebug = MethodName.findStatic(Types.RuntimeInfo, "newModuleBody",
Type.methodType(Types.RuntimeInfo$ModuleBody, Types.String, Types.String, Types.MethodHandle,
Types.MethodHandle, Types.MethodHandle));
static final MethodName RTI_newFunction = MethodName.findStatic(Types.RuntimeInfo, "newFunction",
Type.methodType(Types.RuntimeInfo$Function, Types.Object, Types.String, Type.INT_TYPE, Type.INT_TYPE,
Types.String_, Types.String, Type.INT_TYPE, Types.MethodHandle, Types.MethodHandle,
Types.MethodHandle));
static final MethodName RTI_newFunctionDebug = MethodName.findStatic(Types.RuntimeInfo, "newFunction",
Type.methodType(Types.RuntimeInfo$Function, Types.Object, Types.String, Type.INT_TYPE, Type.INT_TYPE,
Types.String_, Types.String, Type.INT_TYPE, Types.MethodHandle, Types.MethodHandle,
Types.MethodHandle, Types.MethodHandle));
}
private final CodeGenerator codegen;
RuntimeInfoGenerator(CodeGenerator codegen) {
this.codegen = codegen;
}
private int functionFlags(FunctionNode node, boolean tailCall, boolean tailConstruct) {
boolean strict = IsStrict(node);
int functionFlags = 0;
if (strict) {
functionFlags |= FunctionFlags.Strict.getValue();
}
if (strict && node.getStrictMode() == FunctionNode.StrictMode.ImplicitStrict) {
functionFlags |= FunctionFlags.ImplicitStrict.getValue();
}
if (node.isGenerator()) {
functionFlags |= FunctionFlags.Generator.getValue();
}
if (node.isAsync()) {
functionFlags |= FunctionFlags.Async.getValue();
}
if (node.getThisMode() == FunctionNode.ThisMode.Lexical) {
functionFlags |= FunctionFlags.Arrow.getValue();
}
if (node instanceof Declaration) {
functionFlags |= FunctionFlags.Declaration.getValue();
}
if (node instanceof Expression) {
functionFlags |= FunctionFlags.Expression.getValue();
}
if (hasConciseBody(node)) {
functionFlags |= FunctionFlags.ConciseBody.getValue();
}
if (node instanceof MethodDefinition) {
MethodDefinition method = (MethodDefinition) node;
if (method.isClassConstructor()) {
functionFlags |= FunctionFlags.Class.getValue();
} else {
functionFlags |= FunctionFlags.Method.getValue();
if (method.isStatic()) {
functionFlags |= FunctionFlags.Static.getValue();
}
}
}
if (isLegacyGenerator(node)) {
functionFlags |= FunctionFlags.LegacyGenerator.getValue();
}
if (!IsStrict(node) && isLegacy(node)) {
functionFlags |= FunctionFlags.Legacy.getValue();
}
if (hasScopedName(node)) {
functionFlags |= FunctionFlags.ScopedName.getValue();
}
if (node.getScope().hasSuperReference()) {
functionFlags |= FunctionFlags.Super.getValue();
}
if (tailCall) {
assert !node.isGenerator() && !node.isAsync() && strict;
functionFlags |= FunctionFlags.TailCall.getValue();
}
if (tailConstruct) {
assert !node.isGenerator() && !node.isAsync() && strict;
functionFlags |= FunctionFlags.TailConstruct.getValue();
}
if (codegen.isEnabled(Parser.Option.NativeFunction)) {
functionFlags |= FunctionFlags.Native.getValue();
}
if (node.getScope().hasEval()) {
functionFlags |= FunctionFlags.Eval.getValue();
}
if (hasMappedOrLegacyArguments(node)) {
functionFlags |= FunctionFlags.MappedArguments.getValue();
}
return functionFlags;
}
private boolean hasConciseBody(FunctionNode node) {
if (node instanceof ArrowFunction) {
return ((ArrowFunction) node).getExpression() != null;
}
if (node instanceof AsyncArrowFunction) {
return ((AsyncArrowFunction) node).getExpression() != null;
}
return false;
}
private boolean isLegacy(FunctionNode node) {
if (!(node instanceof FunctionDeclaration || node instanceof FunctionExpression)) {
return false;
}
return codegen.isEnabled(CompatibilityOption.FunctionArguments)
|| codegen.isEnabled(CompatibilityOption.FunctionCaller);
}
private boolean isLegacyGenerator(FunctionNode node) {
if (node instanceof LegacyGeneratorDeclaration || node instanceof LegacyGeneratorExpression) {
return true;
}
if (node instanceof GeneratorComprehension) {
return ((GeneratorComprehension) node).getComprehension() instanceof LegacyComprehension;
}
return false;
}
private static boolean hasScopedName(FunctionNode node) {
return node instanceof Expression && node.getIdentifier() != null;
}
private boolean hasLegacyArguments(FunctionNode node) {
if (!(node instanceof FunctionDeclaration || node instanceof FunctionExpression)) {
return false;
}
return codegen.isEnabled(CompatibilityOption.FunctionArguments);
}
private boolean hasMappedOrLegacyArguments(FunctionNode node) {
// Strict or arrow functions never have mapped arguments.
if (IsStrict(node) || node.getThisMode() == FunctionNode.ThisMode.Lexical) {
return false;
}
// Functions with non-simple parameters (or no parameters at all) also never have mapped arguments.
FormalParameterList formals = node.getParameters();
if (formals.getFormals().isEmpty() || !IsSimpleParameterList(formals)) {
return false;
}
// Legacy functions always need the argument name mapping.
if (hasLegacyArguments(node)) {
return true;
}
// No mapping needed when 'arguments' is never accessed.
boolean argumentsObjectNeeded = node.getScope().needsArguments();
Name arguments = node.getScope().arguments();
if (!argumentsObjectNeeded || arguments == null) {
return false;
}
// Or a parameter named 'arguments' is present.
if (BoundNames(formals).contains(arguments)) {
return false;
}
// Or a lexical variable named 'arguments' is present.
if (LexicallyDeclaredNames(node).contains(arguments)) {
return false;
}
// Or a function named 'arguments' is present.
for (StatementListItem item : VarScopedDeclarations(node)) {
if (item instanceof HoistableDeclaration) {
HoistableDeclaration d = (HoistableDeclaration) item;
if (arguments.equals(BoundName(d))) {
return false;
}
}
}
return true;
}
private static final Handle RUNTIME_INFO_BOOTSTRAP = MethodName
.findStatic(RuntimeInfo.class, "bootstrap",
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class))
.toHandle();
void runtimeInfo(ClassDefinition node, boolean tailCall, boolean tailConstruct, String source) {
MethodDefinition constructor = node.getConstructor();
// Cf. CodeGenerator#getSource(ClassDefinition)
int split = "constructor{}".length() + constructor.getHeaderSource().length()
+ constructor.getBodySource().length();
runtimeInfo(node, constructor, tailCall, tailConstruct, source, split, this::debugInfo);
}
void runtimeInfo(FunctionNode node, boolean tailCall, String source) {
runtimeInfo(node, node, tailCall, tailCall, source, node.getHeaderSource().length(), this::debugInfo);
}
private <T extends Node> void runtimeInfo(T def, FunctionNode node, boolean tailCall, boolean tailConstruct,
String source, int sourceSplit, BiConsumer<T, FunctionName> debugInfo) {
FunctionName constructName = tailConstruct ? FunctionName.ConstructTailCall : FunctionName.Construct;
InstructionAssembler asm = new InstructionAssembler(codegen.newMethod(node, FunctionName.RTI));
asm.begin();
asm.invokedynamic("methodInfo", Type.methodType(Types.Object), RUNTIME_INFO_BOOTSTRAP);
asm.aconst(node.getFunctionName());
asm.iconst(functionFlags(node, tailCall, tailConstruct));
asm.iconst(ExpectedArgumentCount(node.getParameters()));
if (hasMappedOrLegacyArguments(node)) {
// TODO: Make this a compact string (to save bytecode and memory size)?
newStringArray(asm, mappedNames(node.getParameters()));
} else {
asm.anull();
}
asm.aconst(source);
asm.iconst(sourceSplit);
if (node.isAsync() || node.isGenerator()) {
asm.handle(codegen.methodDesc(node, FunctionName.Code));
} else {
asm.anull();
}
asm.handle(codegen.methodDesc(node, FunctionName.Call));
if (node.isConstructor()) {
asm.handle(codegen.methodDesc(node, constructName));
} else {
asm.anull();
}
if (codegen.isEnabled(Compiler.Option.DebugInfo)) {
debugInfo.accept(def, constructName);
asm.handle(codegen.methodDesc(node, FunctionName.DebugInfo));
asm.invoke(Methods.RTI_newFunctionDebug);
} else {
asm.invoke(Methods.RTI_newFunction);
}
asm._return();
asm.end();
}
void runtimeInfo(Script node) {
InstructionAssembler asm = new InstructionAssembler(codegen.newMethod(node, ScriptName.RTI));
asm.begin();
asm.aconst(node.getSource().getName());
asm.aconst(node.getSource().getFileString());
asm.handle(codegen.methodDesc(node, ScriptName.Eval));
if (codegen.isEnabled(Compiler.Option.DebugInfo)) {
debugInfo(node);
asm.handle(codegen.methodDesc(node, ScriptName.DebugInfo));
asm.invoke(Methods.RTI_newScriptBodyDebug);
} else {
asm.invoke(Methods.RTI_newScriptBody);
}
asm._return();
asm.end();
}
void runtimeInfo(Module node) {
InstructionAssembler asm = new InstructionAssembler(codegen.newMethod(node, ModuleName.RTI));
asm.begin();
asm.aconst(node.getSource().getName());
asm.aconst(node.getSource().getFileString());
asm.handle(codegen.methodDesc(node, ModuleName.Init));
asm.handle(codegen.methodDesc(node, ModuleName.Code));
if (codegen.isEnabled(Compiler.Option.DebugInfo)) {
debugInfo(node);
asm.handle(codegen.methodDesc(node, ModuleName.DebugInfo));
asm.invoke(Methods.RTI_newModuleBodyDebug);
} else {
asm.invoke(Methods.RTI_newModuleBody);
}
asm._return();
asm.end();
}
private String[] mappedNames(FormalParameterList formals) {
List<FormalParameter> list = formals.getFormals();
int numberOfParameters = list.size();
HashSet<String> mappedNames = new HashSet<>();
String[] names = new String[numberOfParameters];
for (int index = numberOfParameters - 1; index >= 0; --index) {
BindingElementItem element = list.get(index).getElement();
assert element instanceof BindingElement : element.getClass().toString();
Binding binding = ((BindingElement) element).getBinding();
assert binding instanceof BindingIdentifier : binding.getClass().toString();
String name = ((BindingIdentifier) binding).getName().getIdentifier();
if (mappedNames.add(name)) {
names[index] = name;
}
}
return names;
}
private void newStringArray(InstructionAssembler mv, String[] strings) {
mv.anewarray(strings.length, Types.String);
int index = 0;
for (String string : strings) {
mv.astore(index++, string);
}
}
private void debugInfo(FunctionNode node, FunctionName constructName) {
if (node.isConstructor()) {
debugInfo(codegen.newMethod(node, FunctionName.DebugInfo),
codegen.methodDesc(node, FunctionName.RTI),
codegen.methodDesc(node, FunctionName.Call),
codegen.methodDesc(node, constructName),
codegen.methodDesc(node, FunctionName.Init),
codegen.methodDesc(node, FunctionName.Code));
} else {
debugInfo(codegen.newMethod(node, FunctionName.DebugInfo),
codegen.methodDesc(node, FunctionName.RTI),
codegen.methodDesc(node, FunctionName.Call),
codegen.methodDesc(node, FunctionName.Init),
codegen.methodDesc(node, FunctionName.Code));
}
}
private void debugInfo(ClassDefinition node, FunctionName constructName) {
MethodDefinition constructor = node.getConstructor();
MethodDefinition callConstructor = node.getCallConstructor();
if (callConstructor == null) {
debugInfo(codegen.newMethod(constructor, FunctionName.DebugInfo),
codegen.methodDesc(constructor, FunctionName.RTI),
codegen.methodDesc(constructor, FunctionName.Call),
codegen.methodDesc(constructor, constructName),
codegen.methodDesc(constructor, FunctionName.Init),
codegen.methodDesc(constructor, FunctionName.Code));
} else {
debugInfo(codegen.newMethod(constructor, FunctionName.DebugInfo),
codegen.methodDesc(constructor, FunctionName.RTI),
codegen.methodDesc(constructor, FunctionName.Call),
codegen.methodDesc(callConstructor, FunctionName.Init),
codegen.methodDesc(callConstructor, FunctionName.Code),
codegen.methodDesc(constructor, constructName),
codegen.methodDesc(constructor, FunctionName.Init),
codegen.methodDesc(constructor, FunctionName.Code));
}
}
private void debugInfo(Script node) {
debugInfo(codegen.newMethod(node, ScriptName.DebugInfo),
codegen.methodDesc(node, ScriptName.RTI),
codegen.methodDesc(node, ScriptName.Eval),
codegen.methodDesc(node, ScriptName.Init),
codegen.methodDesc(node, ScriptName.Code));
}
private void debugInfo(Module node) {
debugInfo(codegen.newMethod(node, ModuleName.DebugInfo),
codegen.methodDesc(node, ModuleName.RTI),
codegen.methodDesc(node, ModuleName.Init),
codegen.methodDesc(node, ModuleName.Code));
}
private void debugInfo(MethodCode code, MethodName... names) {
InstructionAssembler asm = new InstructionAssembler(code);
asm.begin();
asm.anew(Types.DebugInfo, Methods.DebugInfo_init);
for (MethodName name : names) {
asm.dup();
asm.handle(name);
asm.invoke(Methods.DebugInfo_addMethod);
}
asm._return();
asm.end();
}
}