/* * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.nashorn.internal.codegen; import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.PRIVATE; import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.STATIC; import static jdk.nashorn.internal.codegen.CompilerConstants.GET_MAP; import static jdk.nashorn.internal.codegen.CompilerConstants.GET_STRING; import static jdk.nashorn.internal.codegen.CompilerConstants.LEAF; import static jdk.nashorn.internal.codegen.CompilerConstants.QUICK_PREFIX; import static jdk.nashorn.internal.codegen.CompilerConstants.REGEX_PREFIX; import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_ARRAY_ARG; import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_PREFIX; import static jdk.nashorn.internal.codegen.CompilerConstants.interfaceCallNoLookup; import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor; import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; import static jdk.nashorn.internal.codegen.CompilerConstants.staticField; import static jdk.nashorn.internal.codegen.CompilerConstants.typeDescriptor; import static jdk.nashorn.internal.ir.Symbol.IS_INTERNAL; import static jdk.nashorn.internal.ir.Symbol.IS_TEMP; import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_FAST_SCOPE; import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_FUNCTION_DECLARATION; import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_SCOPE; import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_STRICT; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import jdk.nashorn.internal.codegen.ClassEmitter.Flag; import jdk.nashorn.internal.codegen.CompilerConstants.Call; import jdk.nashorn.internal.codegen.MethodEmitter.Condition; import jdk.nashorn.internal.codegen.MethodEmitter.Label; import jdk.nashorn.internal.codegen.RuntimeCallSite.SpecializedRuntimeNode; import jdk.nashorn.internal.codegen.objects.FieldObjectCreator; import jdk.nashorn.internal.codegen.objects.FunctionObjectCreator; import jdk.nashorn.internal.codegen.objects.MapCreator; import jdk.nashorn.internal.codegen.objects.ObjectMapCreator; import jdk.nashorn.internal.codegen.types.ArrayType; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.AccessNode; import jdk.nashorn.internal.ir.BaseNode; import jdk.nashorn.internal.ir.BinaryNode; import jdk.nashorn.internal.ir.Block; import jdk.nashorn.internal.ir.BreakNode; import jdk.nashorn.internal.ir.CallNode; import jdk.nashorn.internal.ir.CaseNode; import jdk.nashorn.internal.ir.CatchNode; import jdk.nashorn.internal.ir.ContinueNode; import jdk.nashorn.internal.ir.DoWhileNode; import jdk.nashorn.internal.ir.EmptyNode; import jdk.nashorn.internal.ir.ExecuteNode; import jdk.nashorn.internal.ir.ForNode; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.IdentNode; import jdk.nashorn.internal.ir.IfNode; import jdk.nashorn.internal.ir.IndexNode; import jdk.nashorn.internal.ir.LineNumberNode; import jdk.nashorn.internal.ir.LiteralNode; import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode; import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit; import jdk.nashorn.internal.ir.Node; import jdk.nashorn.internal.ir.ObjectNode; import jdk.nashorn.internal.ir.PropertyNode; import jdk.nashorn.internal.ir.ReferenceNode; import jdk.nashorn.internal.ir.ReturnNode; import jdk.nashorn.internal.ir.RuntimeNode; import jdk.nashorn.internal.ir.RuntimeNode.Request; import jdk.nashorn.internal.ir.SplitNode; import jdk.nashorn.internal.ir.SwitchNode; import jdk.nashorn.internal.ir.Symbol; import jdk.nashorn.internal.ir.TernaryNode; import jdk.nashorn.internal.ir.ThrowNode; import jdk.nashorn.internal.ir.TryNode; import jdk.nashorn.internal.ir.UnaryNode; import jdk.nashorn.internal.ir.VarNode; import jdk.nashorn.internal.ir.WhileNode; import jdk.nashorn.internal.ir.WithNode; import jdk.nashorn.internal.ir.debug.ASTWriter; import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.parser.Lexer.RegexToken; import jdk.nashorn.internal.parser.TokenType; import jdk.nashorn.internal.runtime.Context; import jdk.nashorn.internal.runtime.ECMAException; import jdk.nashorn.internal.runtime.PropertyMap; import jdk.nashorn.internal.runtime.Scope; import jdk.nashorn.internal.runtime.ScriptFunction; import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.ScriptRuntime; import jdk.nashorn.internal.runtime.Source; import jdk.nashorn.internal.runtime.Undefined; import jdk.nashorn.internal.runtime.linker.LinkerCallSite; /** * This is the lowest tier of the code generator. It takes lowered ASTs emitted * from Lower and emits Java byte code. The byte code emission logic is broken * out into MethodEmitter. MethodEmitter works internally with a type stack, and * keeps track of the contents of the byte code stack. This way we avoid a large * number of special cases on the form *
 * if (type == INT) {
 *     visitInsn(ILOAD, slot);
 * } else if (type == DOUBLE) {
 *     visitInsn(DOUBLE, slot);
 * }
 * 
* This quickly became apparent when the code generator was generalized to work * with all types, and not just numbers or objects. *

* The CodeGenerator visits nodes only once, tags them as resolved and emits * bytecode for them. */ public final class CodeGenerator extends NodeOperatorVisitor { /** Current compiler */ private final Compiler compiler; /** Compiler context */ private final Context context; /** Call site flags given to the code generator to be used for all generated call sites */ private final int callSiteFlags; /** How many regexp fields have been emitted */ private int regexFieldCount; /** Map of shared scope call sites */ private final Map scopeCalls = new HashMap<>(); /** When should we stop caching regexp expressions in fields to limit bytecode size? */ private static final int MAX_REGEX_FIELDS = 2 * 1024; /** * Constructor. * * @param compiler */ CodeGenerator(final Compiler compiler) { this.compiler = compiler; this.context = compiler.getContext(); this.callSiteFlags = context._callsite_flags; } /** * Get the compiler * * @return the compiler used */ public Compiler getCompiler() { return compiler; } /** * Gets the call site flags, adding the strict flag if the current function * being generated is in strict mode * * @return the correct flags for a call site in the current function */ public int getCallSiteFlags() { return getCurrentFunctionNode().isStrictMode() ? callSiteFlags | CALLSITE_STRICT : callSiteFlags; } /** * Load an identity node * * @param identNode an identity node to load * @return the method generator used */ private MethodEmitter loadIdent(final IdentNode identNode) { final Symbol symbol = identNode.getSymbol(); if (!symbol.isScope()) { assert symbol.hasSlot() && symbol.getSlot() != 0 || symbol.isThis(); return method.load(symbol); } final String name = symbol.getName(); if (CompilerConstants.__FILE__.name().equals(name)) { return method.load(identNode.getSource().getName()); } else if (CompilerConstants.__DIR__.name().equals(name)) { return method.load(identNode.getSource().getBase()); } else if (CompilerConstants.__LINE__.name().equals(name)) { return method.load(identNode.getSource().getLine(identNode.position())).convert(Type.OBJECT); } else { assert identNode.getSymbol().isScope() : identNode + " is not in scope!"; final int flags = CALLSITE_SCOPE | getCallSiteFlags(); method.loadScope(); if (symbol.isFastScope(getCurrentFunctionNode())) { // Only generate shared scope getter for fast-scope symbols so we know we can dial in correct scope. if (symbol.getUseCount() > SharedScopeCall.FAST_SCOPE_GET_THRESHOLD) { return loadSharedScopeVar(identNode.getType(), symbol, flags); } return loadFastScopeVar(identNode.getType(), symbol, flags, identNode.isFunction()); } return method.dynamicGet(identNode.getType(), identNode.getName(), flags, identNode.isFunction()); } } private MethodEmitter loadSharedScopeVar(final Type valueType, final Symbol symbol, final int flags) { method.load(symbol.isFastScope(getCurrentFunctionNode()) ? getScopeProtoDepth(getCurrentBlock(), symbol) : -1); final SharedScopeCall scopeCall = getScopeGet(valueType, symbol, flags | CALLSITE_FAST_SCOPE); scopeCall.generateInvoke(method); return method; } private MethodEmitter loadFastScopeVar(final Type valueType, final Symbol symbol, final int flags, final boolean isMethod) { loadFastScopeProto(symbol, false); method.dynamicGet(valueType, symbol.getName(), flags | CALLSITE_FAST_SCOPE, isMethod); return method; } private MethodEmitter storeFastScopeVar(final Type valueType, final Symbol symbol, final int flags) { loadFastScopeProto(symbol, true); method.dynamicSet(valueType, symbol.getName(), flags | CALLSITE_FAST_SCOPE); return method; } private static int getScopeProtoDepth(final Block currentBlock, final Symbol symbol) { if (currentBlock == symbol.getBlock()) { return 0; } final int delta = currentBlock.needsScope() ? 1 : 0; final Block parentBlock = currentBlock.getParent(); if (parentBlock != null) { final int result = getScopeProtoDepth(parentBlock, symbol); if (result != -1) { return delta + result; } } if (currentBlock instanceof FunctionNode) { for (final Block lookupBlock : ((FunctionNode)currentBlock).getReferencingParentBlocks()) { final int result = getScopeProtoDepth(lookupBlock, symbol); if (result != -1) { return delta + result; } } } return -1; } private void loadFastScopeProto(final Symbol symbol, final boolean swap) { final int depth = getScopeProtoDepth(getCurrentBlock(), symbol); assert depth != -1; if(depth > 0) { if (swap) { method.swap(); } for (int i = 0; i < depth; i++) { method.invoke(ScriptObject.GET_PROTO); } if (swap) { method.swap(); } } } /** * Generate code that loads this node to the stack. This method is only * public to be accessible from the maps sub package. Do not call externally * * @param node node to load * * @return the method emitter used */ public MethodEmitter load(final Node node) { return load(node, false); } private MethodEmitter load(final Node node, final boolean baseAlreadyOnStack) { final Symbol symbol = node.getSymbol(); // If we lack symbols, we just generate what we see. if (symbol == null) { node.accept(this); return method; } /* * The load may be of type IdentNode, e.g. "x", AccessNode, e.g. "x.y" * or IndexNode e.g. "x[y]". Both AccessNodes and IndexNodes are * BaseNodes and the logic for loading the base object is reused */ final CodeGenerator codegen = this; node.accept(new NodeVisitor(compileUnit, method) { @Override public Node enter(final IdentNode identNode) { loadIdent(identNode); return null; } @Override public Node enter(final AccessNode accessNode) { if (!baseAlreadyOnStack) { load(accessNode.getBase()).convert(Type.OBJECT); } assert method.peekType().isObject(); method.dynamicGet(node.getType(), accessNode.getProperty().getName(), getCallSiteFlags(), accessNode.isFunction()); return null; } @Override public Node enter(final IndexNode indexNode) { if (!baseAlreadyOnStack) { load(indexNode.getBase()).convert(Type.OBJECT); load(indexNode.getIndex()); } method.dynamicGetIndex(node.getType(), getCallSiteFlags(), indexNode.isFunction()); return null; } @Override public Node enterDefault(final Node otherNode) { otherNode.accept(codegen); // generate code for whatever we are looking at. method.load(symbol); // load the final symbol to the stack (or nop if no slot, then result is already there) return null; } }); return method; } @Override public Node enter(final AccessNode accessNode) { if (accessNode.testResolved()) { return null; } load(accessNode); return null; } /** * Initialize a specific set of vars to undefined. This has to be done at * the start of each method for local variables that aren't passed as * parameters. * * @param symbols list of symbols. */ private void initSymbols(final Iterable symbols) { final LinkedList numbers = new LinkedList<>(); final LinkedList objects = new LinkedList<>(); for (final Symbol symbol : symbols) { /* * The following symbols are guaranteed to be defined and thus safe * from having unsigned written to them: parameters internals this * * Otherwise we must, unless we perform control/escape analysis, * assign them undefined. */ final boolean isInternal = symbol.isParam() || symbol.isInternal() || symbol.isThis() || !symbol.canBeUndefined(); if (symbol.hasSlot() && !isInternal) { assert symbol.getSymbolType().isNumber() || symbol.getSymbolType().isObject() : "no potentially undefined narrower local vars than doubles are allowed: " + symbol + " in " + getCurrentFunctionNode(); if (symbol.getSymbolType().isNumber()) { numbers.add(symbol); } else if (symbol.getSymbolType().isObject()) { objects.add(symbol); } } } initSymbols(numbers, Type.NUMBER); initSymbols(objects, Type.OBJECT); } private void initSymbols(final LinkedList symbols, final Type type) { if (symbols.isEmpty()) { return; } method.loadUndefined(type); while (!symbols.isEmpty()) { final Symbol symbol = symbols.removeFirst(); if (!symbols.isEmpty()) { method.dup(); } method.store(symbol); } } /** * Create symbol debug information. * * @param block block containing symbols. */ private void symbolInfo(final Block block) { for (final Symbol symbol : block.getFrame().getSymbols()) { method.localVariable(symbol, block.getEntryLabel(), block.getBreakLabel()); } } @Override public Node enter(final Block block) { if (block.testResolved()) { return null; } method.label(block.getEntryLabel()); initLocals(block); return block; } @Override public Node leave(final Block block) { method.label(block.getBreakLabel()); symbolInfo(block); if (block.needsScope()) { popBlockScope(block); } return block; } private void popBlockScope(final Block block) { final Label exitLabel = new Label("block_exit"); final Label recoveryLabel = new Label("block_catch"); final Label skipLabel = new Label("skip_catch"); /* pop scope a la try-finally */ method.loadScope(); method.invoke(ScriptObject.GET_PROTO); method.storeScope(); method._goto(skipLabel); method.label(exitLabel); method._catch(recoveryLabel); method.loadScope(); method.invoke(ScriptObject.GET_PROTO); method.storeScope(); method.athrow(); method.label(skipLabel); method._try(block.getEntryLabel(), exitLabel, recoveryLabel, Throwable.class); } @Override public Node enter(final BreakNode breakNode) { if (breakNode.testResolved()) { return null; } for (int i = 0; i < breakNode.getScopeNestingLevel(); i++) { closeWith(); } method.splitAwareGoto(breakNode.getTargetLabel()); return null; } private MethodEmitter loadArgs(final List args) { return loadArgs(args, null, false, args.size()); } private MethodEmitter loadArgs(final List args, final String signature, final boolean isVarArg, final int argCount) { // arg have already been converted to objects here. if (isVarArg || argCount > LinkerCallSite.ARGLIMIT) { loadArgsArray(args); return method; } // pad with undefined if size is too short. argCount is the real number of args int n = 0; final Type[] params = signature == null ? null : Type.getMethodArguments(signature); for (final Node arg : args) { assert arg != null; load(arg); if (n >= argCount) { method.pop(); // we had to load the arg for its side effects } else if (params != null) { method.convert(params[n]); } n++; } while (n < argCount) { method.loadUndefined(Type.OBJECT); n++; } return method; } /** * Create a new function object, including generating its stub code. * * @param functionNode FunctionNode to utilize. */ private void newFunctionObject(final FunctionNode functionNode) { // Turn thisProperties into keys and symbols for the FunctionAnalyzer final Map thisProperties = functionNode.getThisProperties(); final List keys = new ArrayList<>(); final List symbols = new ArrayList<>(); /* TODO - Predefine known properties. for (final Entry entry : thisProperties.entrySet()) { keys.add(entry.getKey()); symbols.add(entry.getValue().getSymbol()); } */ new FunctionObjectCreator(this, functionNode, keys, symbols).makeObject(method); } @Override public Node enter(final CallNode callNode) { if (callNode.testResolved()) { return null; } final List args = callNode.getArgs(); final Node function = callNode.getFunction(); final FunctionNode currentFunction = getCurrentFunctionNode(); final Block currentBlock = getCurrentBlock(); function.accept(new NodeVisitor(compileUnit, method) { private void sharedScopeCall(final IdentNode identNode, final int flags) { final Symbol symbol = identNode.getSymbol(); int scopeCallFlags = flags; method.loadScope(); if (symbol.isFastScope(currentFunction)) { method.load(getScopeProtoDepth(currentBlock, symbol)); scopeCallFlags |= CALLSITE_FAST_SCOPE; } else { method.load(-1); // Bypass fast-scope code in shared callsite } loadArgs(args); final Type[] paramTypes = method.getTypesFromStack(args.size()); final SharedScopeCall scopeCall = getScopeCall(symbol, identNode.getType(), callNode.getType(), paramTypes, scopeCallFlags); scopeCall.generateInvoke(method); } private void scopeCall(final IdentNode node, final int flags) { load(node); method.convert(Type.OBJECT); // foo() makes no sense if foo == 3 // ScriptFunction will see CALLSITE_SCOPE and will bind scope accordingly. method.loadNull(); loadArgs(args); method.dynamicCall(callNode.getType(), args.size(), flags); } private void evalCall(final IdentNode node, final int flags) { load(node); method.convert(Type.OBJECT); // foo() makes no sense if foo == 3 final Label not_eval = new Label("not_eval"); final Label eval_done = new Label("eval_done"); // check if this is the real built-in eval method.dup(); globalIsEval(); method.ifeq(not_eval); // We don't need ScriptFunction object for 'eval' method.pop(); method.loadScope(); // Load up self (scope). final CallNode.EvalArgs evalArgs = callNode.getEvalArgs(); // load evaluated code load(evalArgs.code); method.convert(Type.OBJECT); // special/extra 'eval' arguments load(evalArgs.evalThis); method.load(evalArgs.location); method.load(evalArgs.strictMode); method.convert(Type.OBJECT); // direct call to Global.directEval globalDirectEval(); method.convert(callNode.getType()); method._goto(eval_done); method.label(not_eval); // This is some scope 'eval' or global eval replaced by user // but not the built-in ECMAScript 'eval' function call method.loadNull(); loadArgs(args); method.dynamicCall(callNode.getType(), args.size(), flags); method.label(eval_done); } @Override public Node enter(final IdentNode node) { final Symbol symbol = node.getSymbol(); if (symbol.isScope()) { final int flags = getCallSiteFlags() | CALLSITE_SCOPE; final int useCount = symbol.getUseCount(); // Threshold for generating shared scope callsite is lower for fast scope symbols because we know // we can dial in the correct scope. However, we als need to enable it for non-fast scopes to // support huge scripts like mandreel.js. if (callNode.isEval()) { evalCall(node, flags); } else if (useCount <= SharedScopeCall.FAST_SCOPE_CALL_THRESHOLD || (!symbol.isFastScope(currentFunction) && useCount <= SharedScopeCall.SLOW_SCOPE_CALL_THRESHOLD) || callNode.inWithBlock()) { scopeCall(node, flags); } else { sharedScopeCall(node, flags); } assert method.peekType().equals(callNode.getType()); } else { enterDefault(node); } return null; } @Override public Node enter(final AccessNode node) { load(node.getBase()); method.convert(Type.OBJECT); method.dup(); method.dynamicGet(node.getType(), node.getProperty().getName(), getCallSiteFlags(), true); method.swap(); loadArgs(args); method.dynamicCall(callNode.getType(), args.size(), getCallSiteFlags()); assert method.peekType().equals(callNode.getType()); return null; } @Override public Node enter(final ReferenceNode node) { final FunctionNode callee = node.getReference(); final boolean isVarArg = callee.isVarArg(); final int argCount = isVarArg ? -1 : callee.getParameters().size(); final String signature = new FunctionSignature(true, callee.needsCallee(), callee.getReturnType(), isVarArg ? null : callee.getParameters()).toString(); if (callee.isStrictMode()) { // self is undefined method.loadUndefined(Type.OBJECT); } else { // get global from scope (which is the self) globalInstance(); } if (callee.needsCallee()) { // TODO: always true newFunctionObject(callee); // TODO: if callee not needed, function object is used only to pass scope (could be optimized). if neither the scope nor the function object is needed by the callee, we can pass null instead. } loadArgs(args, signature, isVarArg, argCount); method.invokeStatic(callee.getCompileUnit().getUnitClassName(), callee.getName(), signature); assert method.peekType().equals(callee.getReturnType()) : method.peekType() + " != " + callee.getReturnType(); return null; } @Override public Node enter(final IndexNode node) { load(node.getBase()); method.convert(Type.OBJECT); method.dup(); load(node.getIndex()); final Type indexType = node.getIndex().getType(); if (indexType.isObject() || indexType.isBoolean()) { method.convert(Type.OBJECT); //TODO } method.dynamicGetIndex(node.getType(), getCallSiteFlags(), true); method.swap(); loadArgs(args); method.dynamicCall(callNode.getType(), args.size(), getCallSiteFlags()); assert method.peekType().equals(callNode.getType()); return null; } @Override protected Node enterDefault(final Node node) { // Load up function. load(function); method.convert(Type.OBJECT); //TODO, e.g. booleans can be used as functions method.loadNull(); // ScriptFunction will figure out the correct this when it sees CALLSITE_SCOPE loadArgs(args); method.dynamicCall(callNode.getType(), args.size(), getCallSiteFlags() | CALLSITE_SCOPE); assert method.peekType().equals(callNode.getType()); return null; } }); method.store(callNode.getSymbol()); return null; } @Override public Node enter(final ContinueNode continueNode) { if (continueNode.testResolved()) { return null; } for (int i = 0; i < continueNode.getScopeNestingLevel(); i++) { closeWith(); } method.splitAwareGoto(continueNode.getTargetLabel()); return null; } @Override public Node enter(final DoWhileNode doWhileNode) { return enter((WhileNode)doWhileNode); } @Override public Node enter(final EmptyNode emptyNode) { return null; } @Override public Node enter(final ExecuteNode executeNode) { if (executeNode.testResolved()) { return null; } final Node expression = executeNode.getExpression(); expression.accept(this); return null; } @Override public Node enter(final ForNode forNode) { if (forNode.testResolved()) { return null; } final Node test = forNode.getTest(); final Block body = forNode.getBody(); final Node modify = forNode.getModify(); final Label breakLabel = forNode.getBreakLabel(); final Label continueLabel = forNode.getContinueLabel(); final Label loopLabel = new Label("loop"); Node init = forNode.getInit(); if (forNode.isForIn()) { final Symbol iter = forNode.getIterator(); // We have to evaluate the optional initializer expression // of the iterator variable of the for-in statement. if (init instanceof VarNode) { init.accept(this); init = ((VarNode)init).getName(); } load(modify); assert modify.getType().isObject(); method.invoke(forNode.isForEach() ? ScriptRuntime.TO_VALUE_ITERATOR : ScriptRuntime.TO_PROPERTY_ITERATOR); method.store(iter); method._goto(continueLabel); method.label(loopLabel); new Store(init) { @Override protected void evaluate() { method.load(iter); method.invoke(interfaceCallNoLookup(Iterator.class, "next", Object.class)); } }.store(); body.accept(this); method.label(continueLabel); method.load(iter); method.invoke(interfaceCallNoLookup(Iterator.class, "hasNext", boolean.class)); method.ifne(loopLabel); method.label(breakLabel); } else { if (init != null) { init.accept(this); } final Label testLabel = new Label("test"); method._goto(testLabel); method.label(loopLabel); body.accept(this); method.label(continueLabel); if (!body.isTerminal() && modify != null) { load(modify); } method.label(testLabel); if (test != null) { new BranchOptimizer(this, method).execute(test, loopLabel, true); } else { method._goto(loopLabel); } method.label(breakLabel); } return null; } /** * Initialize the slots in a frame to undefined. * * @param block block with local vars. */ private void initLocals(final Block block) { final FunctionNode function = block.getFunction(); final boolean isFunctionNode = block == function; /* * Get the symbols from the frame and realign the frame so that all * slots get correct numbers. The slot numbering is not fixed until * after initLocals has been run */ final Frame frame = block.getFrame(); final List symbols = frame.getSymbols(); /* Fix the predefined slots so they have numbers >= 0, like varargs. */ frame.realign(); /* * Determine if block needs scope, if not, just do initSymbols for this block. */ if (block.needsScope()) { /* * Determine if function is varargs and consequently variables have to * be in the scope. */ final boolean isVarArg = function.isVarArg(); final boolean varsInScope = function.varsInScope(); // TODO for LET we can do better: if *block* does not contain any eval/with, we don't need its vars in scope. final List nameList = new ArrayList<>(); final List locals = new ArrayList<>(); // If there are variable arguments, we need to load them (functions only). if (isFunctionNode && isVarArg) { method.loadVarArgs(); method.loadCallee(); method.load(function.getParameters().size()); globalAllocateArguments(); method.storeArguments(); } // Initalize symbols and values final List newSymbols = new ArrayList<>(); final List values = new ArrayList<>(); for (final Symbol symbol : symbols) { if (symbol.isInternal() || symbol.isThis()) { continue; } if (symbol.isVar() && (varsInScope || symbol.isScope())) { nameList.add(symbol.getName()); newSymbols.add(symbol); values.add(null); assert symbol.isScope() : "scope for " + symbol + " should have been set in Lower already " + function.getName(); assert !symbol.hasSlot() : "slot for " + symbol + " should have been removed in Lower already" + function.getName(); } else if (symbol.isVar()) { assert symbol.hasSlot() : symbol + " should have a slot only, no scope"; locals.add(symbol); } else if (symbol.isParam() && (varsInScope || isVarArg || symbol.isScope())) { nameList.add(symbol.getName()); newSymbols.add(symbol); values.add(isVarArg ? null : symbol); assert symbol.isScope() : "scope for " + symbol + " should have been set in Lower already " + function.getName() + " varsInScope="+varsInScope+" isVarArg="+isVarArg+" symbol.isScope()=" + symbol.isScope(); assert !(isVarArg && symbol.hasSlot()) : "slot for " + symbol + " should have been removed in Lower already " + function.getName(); } } /* Correct slot numbering again */ frame.realign(); // we may have locals that need to be initialized initSymbols(locals); if (isFunctionNode) { initScope(); } /* * Create a new object based on the symbols and values, generate * bootstrap code for object */ final FieldObjectCreator foc = new FieldObjectCreator(this, nameList, newSymbols, values, true, isVarArg) { @Override protected Type getValueType(final Symbol value) { return value.getSymbolType(); } @Override protected void loadValue(final Symbol value) { method.load(value); } }; foc.makeObject(method); // runScript(): merge scope into global if (isFunctionNode && function.isScript()) { method.invoke(ScriptRuntime.MERGE_SCOPE); } method.storeScope(); } else { initSymbols(symbols); if (isFunctionNode) { initScope(); } } // Debugging: print symbols? @see --print-symbols flag printSymbols(block, (isFunctionNode ? "Function " : "Block in ") + (function.getIdent() == null ? "" : function.getIdent().getName())); } private void initScope() { method.loadCallee(); method.invoke(ScriptFunction.GET_SCOPE); method.storeScope(); } @Override public Node enter(final FunctionNode functionNode) { if (functionNode.testResolved()) { return null; } compileUnit = functionNode.getCompileUnit(); assert compileUnit != null; method = compileUnit.getClassEmitter().method(functionNode); functionNode.setMethodEmitter(method); // Mark end for variable tables. method.begin(); method.label(functionNode.getEntryLabel()); initLocals(functionNode); return functionNode; } @Override public Node leave(final FunctionNode functionNode) { // Mark end for variable tables. method.label(functionNode.getBreakLabel()); if (!functionNode.needsScope()) { method.markerVariable(LEAF.tag(), functionNode.getEntryLabel(), functionNode.getBreakLabel()); } symbolInfo(functionNode); try { method.end(); // wrap up this method } catch (final Throwable t) { Context.printStackTrace(t); final VerifyError e = new VerifyError("Code generation bug in \"" + functionNode.getName() + "\": likely stack misaligned: " + t + " " + functionNode.getSource().getName()); e.initCause(t); throw e; } return functionNode; } @Override public Node enter(final IdentNode identNode) { return null; } @Override public Node enter(final IfNode ifNode) { if (ifNode.testResolved()) { return null; } final Node test = ifNode.getTest(); final Block pass = ifNode.getPass(); final Block fail = ifNode.getFail(); final Label failLabel = new Label("if_fail"); final Label afterLabel = fail == null ? failLabel : new Label("if_done"); new BranchOptimizer(this, method).execute(test, failLabel, false); boolean passTerminal = false; boolean failTerminal = false; pass.accept(this); if (!pass.hasTerminalFlags()) { method._goto(afterLabel); //don't fallthru to fail block } else { passTerminal = pass.isTerminal(); } if (fail != null) { method.label(failLabel); fail.accept(this); failTerminal = fail.isTerminal(); } //if if terminates, put the after label there if (!passTerminal || !failTerminal) { method.label(afterLabel); } return null; } @Override public Node enter(final IndexNode indexNode) { if (indexNode.testResolved()) { return null; } load(indexNode); return null; } @Override public Node enter(final LineNumberNode lineNumberNode) { if (lineNumberNode.testResolved()) { return null; } final Label label = new Label("line:" + lineNumberNode.getLineNumber() + " (" + getCurrentFunctionNode().getName() + ")"); method.label(label); method.lineNumber(lineNumberNode.getLineNumber(), label); return null; } /** * Load a list of nodes as an array of a specific type * The array will contain the visited nodes. * * @param arrayLiteralNode the array of contents * @param arrayType the type of the array, e.g. ARRAY_NUMBER or ARRAY_OBJECT * * @return the method generator that was used */ private MethodEmitter loadArray(final ArrayLiteralNode arrayLiteralNode, final ArrayType arrayType) { assert arrayType == Type.INT_ARRAY || arrayType == Type.NUMBER_ARRAY || arrayType == Type.OBJECT_ARRAY; final Node[] nodes = arrayLiteralNode.getValue(); final Object presets = arrayLiteralNode.getPresets(); final int[] postsets = arrayLiteralNode.getPostsets(); final Class type = arrayType.getTypeClass(); final List units = arrayLiteralNode.getUnits(); loadConstant(presets); final Type elementType = arrayType.getElementType(); if (units != null) { final CompileUnit savedCompileUnit = compileUnit; final MethodEmitter savedMethod = method; try { for (final ArrayUnit unit : units) { compileUnit = unit.getCompileUnit(); final String className = compileUnit.getUnitClassName(); final String name = compiler.uniqueName(SPLIT_PREFIX.tag()); final String signature = methodDescriptor(type, Object.class, ScriptFunction.class, ScriptObject.class, type); method = compileUnit.getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature); method.setFunctionNode(getCurrentFunctionNode()); method.begin(); fixScopeSlot(); method.load(arrayType, SPLIT_ARRAY_ARG.slot()); for (int i = unit.getLo(); i < unit.getHi(); i++) { storeElement(nodes, elementType, postsets[i]); } method._return(); method.end(); savedMethod.loadThis(); savedMethod.swap(); savedMethod.loadCallee(); savedMethod.swap(); savedMethod.loadScope(); savedMethod.swap(); savedMethod.invokeStatic(className, name, signature); } } finally { compileUnit = savedCompileUnit; method = savedMethod; } return method; } for (final int postset : postsets) { storeElement(nodes, elementType, postset); } return method; } private void storeElement(final Node[] nodes, final Type elementType, final int index) { method.dup(); method.load(index); final Node element = nodes[index]; if (element == null) { method.loadEmpty(elementType); } else { assert elementType.isEquivalentTo(element.getType()) : "array element type doesn't match array type"; load(element); } method.arraystore(); } private MethodEmitter loadArgsArray(final List args) { final Object[] array = new Object[args.size()]; loadConstant(array); for (int i = 0; i < args.size(); i++) { method.dup(); method.load(i); load(args.get(i)).convert(Type.OBJECT); //has to be upcast to object or we fail method.arraystore(); } return method; } /** * Load a constant from the constant array. This is only public to be callable from the objects * subpackage. Do not call directly. * * @param string string to load */ public void loadConstant(final String string) { final String unitClassName = compileUnit.getUnitClassName(); final ClassEmitter classEmitter = compileUnit.getClassEmitter(); final int index = compiler.getConstantData().add(string); method.load(index); method.invokeStatic(unitClassName, GET_STRING.tag(), methodDescriptor(String.class, int.class)); classEmitter.needGetConstantMethod(String.class); } /** * Load a constant from the constant array. This is only public to be callable from the objects * subpackage. Do not call directly. * * @param object object to load */ public void loadConstant(final Object object) { final String unitClassName = compileUnit.getUnitClassName(); final ClassEmitter classEmitter = compileUnit.getClassEmitter(); final int index = compiler.getConstantData().add(object); final Class cls = object.getClass(); if (cls == PropertyMap.class) { method.load(index); method.invokeStatic(unitClassName, GET_MAP.tag(), methodDescriptor(PropertyMap.class, int.class)); classEmitter.needGetConstantMethod(PropertyMap.class); } else if (cls.isArray()) { method.load(index); final String methodName = ClassEmitter.getArrayMethodName(cls); method.invokeStatic(unitClassName, methodName, methodDescriptor(cls, int.class)); classEmitter.needGetConstantMethod(cls); } else { method.loadConstants(unitClassName).load(index).arrayload(); if (cls != Object.class) { method.checkcast(cls); } } } // literal values private MethodEmitter load(final LiteralNode node) { final Object value = node.getValue(); if (value == null) { method.loadNull(); } else if (value instanceof Undefined) { method.loadUndefined(Type.OBJECT); } else if (value instanceof String) { final String string = (String)value; if (string.length() > (MethodEmitter.LARGE_STRING_THRESHOLD / 3)) { // 3 == max bytes per encoded char loadConstant(string); } else { method.load(string); } } else if (value instanceof RegexToken) { loadRegex((RegexToken)value); } else if (value instanceof Boolean) { method.load((Boolean)value); } else if (value instanceof Integer) { method.load((Integer)value); } else if (value instanceof Long) { method.load((Long)value); } else if (value instanceof Double) { method.load((Double)value); } else if (node instanceof ArrayLiteralNode) { final ArrayType type = (ArrayType)node.getType(); loadArray((ArrayLiteralNode)node, type); globalAllocateArray(type); } else { assert false : "Unknown literal for " + node.getClass() + " " + value.getClass() + " " + value; } return method; } private MethodEmitter loadRegexToken(final RegexToken value) { method.load(value.getExpression()); method.load(value.getOptions()); return globalNewRegExp(); } private MethodEmitter loadRegex(final RegexToken regexToken) { if (regexFieldCount > MAX_REGEX_FIELDS) { return loadRegexToken(regexToken); } // emit field final String regexName = compiler.uniqueName(REGEX_PREFIX.tag()); final ClassEmitter classEmitter = compileUnit.getClassEmitter(); classEmitter.field(EnumSet.of(PRIVATE, STATIC), regexName, Object.class); regexFieldCount++; // get field, if null create new regex, finally clone regex object method.getStatic(compileUnit.getUnitClassName(), regexName, typeDescriptor(Object.class)); method.dup(); final Label cachedLabel = new Label("cached"); method.ifnonnull(cachedLabel); method.pop(); loadRegexToken(regexToken); method.dup(); method.putStatic(compileUnit.getUnitClassName(), regexName, typeDescriptor(Object.class)); method.label(cachedLabel); globalRegExpCopy(); return method; } @SuppressWarnings("rawtypes") @Override public Node enter(final LiteralNode literalNode) { load(literalNode).store(literalNode.getSymbol()); return null; } @Override public Node enter(final ObjectNode objectNode) { if (objectNode.testResolved()) { return null; } final List elements = objectNode.getElements(); final int size = elements.size(); final List keys = new ArrayList<>(); final List symbols = new ArrayList<>(); final List values = new ArrayList<>(); boolean hasGettersSetters = false; for (int i = 0; i < size; i++) { final PropertyNode propertyNode = (PropertyNode)elements.get(i); final Node value = propertyNode.getValue(); final String key = propertyNode.getKeyName(); final Symbol symbol = value == null ? null : propertyNode.getSymbol(); if (value == null) { hasGettersSetters = true; } keys.add(key); symbols.add(symbol); values.add(value); } new FieldObjectCreator(this, keys, symbols, values) { @Override protected Type getValueType(final Node node) { return node.getType(); } @Override protected void loadValue(final Node node) { load(node); } /** * Ensure that the properties start out as object types so that * we can do putfield initializations instead of dynamicSetIndex * which would be the case to determine initial property type * otherwise. * * Use case, it's very expensive to do a million var x = {a:obj, b:obj} * just to have to invalidate them immediately on initialization * * see NASHORN-594 */ @Override protected MapCreator newMapCreator(final Class fieldObjectClass) { return new ObjectMapCreator(fieldObjectClass, keys, symbols); } }.makeObject(method); method.dup(); globalObjectPrototype(); method.invoke(ScriptObject.SET_PROTO); if (!hasGettersSetters) { method.store(objectNode.getSymbol()); return null; } for (final Node element : elements) { final PropertyNode propertyNode = (PropertyNode)element; final Object key = propertyNode.getKey(); final ReferenceNode getter = (ReferenceNode)propertyNode.getGetter(); final ReferenceNode setter = (ReferenceNode)propertyNode.getSetter(); if (getter == null && setter == null) { continue; } method.dup().loadKey(key); if (getter == null) { method.loadNull(); } else { getter.accept(this); } if (setter == null) { method.loadNull(); } else { setter.accept(this); } method.invoke(ScriptObject.SET_USER_ACCESSORS); } method.store(objectNode.getSymbol()); return null; } @Override public Node enter(final ReferenceNode referenceNode) { if (referenceNode.testResolved()) { return null; } newFunctionObject(referenceNode.getReference()); return null; } @Override public Node enter(final ReturnNode returnNode) { if (returnNode.testResolved()) { return null; } // Set the split return flag in the scope if this is a split method fragment. if (method.getSplitNode() != null) { assert method.getSplitNode().hasReturn() : "unexpected return in split node"; method.loadScope(); method.checkcast(Scope.class); method.load(0); method.invoke(Scope.SET_SPLIT_STATE); } final Node expression = returnNode.getExpression(); if (expression != null) { load(expression); } else { method.loadUndefined(getCurrentFunctionNode().getReturnType()); } method._return(getCurrentFunctionNode().getReturnType()); return null; } private static boolean isNullLiteral(final Node node) { return node instanceof LiteralNode && ((LiteralNode) node).isNull(); } private boolean nullCheck(final RuntimeNode runtimeNode, final List args, final String signature) { final Request request = runtimeNode.getRequest(); if (!Request.isEQ(request) && !Request.isNE(request)) { return false; } assert args.size() == 2 : "EQ or NE or TYPEOF need two args"; Node lhs = args.get(0); Node rhs = args.get(1); if (isNullLiteral(lhs)) { final Node tmp = lhs; lhs = rhs; rhs = tmp; } if (isNullLiteral(rhs)) { final Label trueLabel = new Label("trueLabel"); final Label falseLabel = new Label("falseLabel"); final Label endLabel = new Label("end"); load(lhs); method.dup(); if (Request.isEQ(request)) { method.ifnull(trueLabel); } else if (Request.isNE(request)) { method.ifnonnull(trueLabel); } else { assert false : "Invalid request " + request; } method.label(falseLabel); load(rhs); method.invokeStatic(CompilerConstants.className(ScriptRuntime.class), request.toString(), signature); method._goto(endLabel); method.label(trueLabel); // if NE (not strict) this can be "undefined != null" which is supposed to be false if (request == Request.NE) { method.loadUndefined(Type.OBJECT); final Label isUndefined = new Label("isUndefined"); final Label afterUndefinedCheck = new Label("afterUndefinedCheck"); method.if_acmpeq(isUndefined); // not undefined method.load(true); method._goto(afterUndefinedCheck); method.label(isUndefined); method.load(false); method.label(afterUndefinedCheck); } else { method.pop(); method.load(true); } method.label(endLabel); method.convert(runtimeNode.getType()); method.store(runtimeNode.getSymbol()); return true; } return false; } private boolean specializationCheck(final RuntimeNode.Request request, final Node node, final List args) { if (!request.canSpecialize()) { return false; } assert args.size() == 2; final Node lhs = args.get(0); final Node rhs = args.get(1); final Type returnType = node.getType(); load(lhs); load(rhs); Request finalRequest = request; final Request reverse = Request.reverse(request); if (method.peekType().isObject() && reverse != null) { if (!method.peekType(1).isObject()) { method.swap(); finalRequest = reverse; } } method.dynamicRuntimeCall( new SpecializedRuntimeNode( finalRequest, new Type[] { method.peekType(1), method.peekType() }, returnType).getInitialName(), returnType, finalRequest); method.convert(node.getType()); method.store(node.getSymbol()); return true; } @Override public Node enter(final RuntimeNode runtimeNode) { if (runtimeNode.testResolved()) { return null; } /* * First check if this should be something other than a runtime node * AccessSpecializer might have changed the type */ if (runtimeNode.isPrimitive()) { final Node lhs = runtimeNode.getArgs().get(0); Node rhs = null; if (runtimeNode.getArgs().size() > 1) { rhs = runtimeNode.getArgs().get(1); } final Type type = runtimeNode.getType(); final Symbol symbol = runtimeNode.getSymbol(); switch (runtimeNode.getRequest()) { case EQ: case EQ_STRICT: return enterCmp(lhs, rhs, Condition.EQ, type, symbol); case NE: case NE_STRICT: return enterCmp(lhs, rhs, Condition.NE, type, symbol); case LE: return enterCmp(lhs, rhs, Condition.LE, type, symbol); case LT: return enterCmp(lhs, rhs, Condition.LT, type, symbol); case GE: return enterCmp(lhs, rhs, Condition.GE, type, symbol); case GT: return enterCmp(lhs, rhs, Condition.GT, type, symbol); case ADD: return enterNumericAdd(lhs, rhs, type, symbol); default: // it's ok to send this one on with only primitive arguments, maybe INSTANCEOF(true, true) or similar // assert false : runtimeNode + " has all primitive arguments. This is an inconsistent state"; break; } } // Get the request arguments. final List args = runtimeNode.getArgs(); if (nullCheck(runtimeNode, args, new FunctionSignature(false, runtimeNode.getType(), args).toString())) { return null; } if (specializationCheck(runtimeNode.getRequest(), runtimeNode, args)) { return null; } for (final Node arg : runtimeNode.getArgs()) { load(arg).convert(Type.OBJECT); //TODO this should not be necessary below Lower } method.invokeStatic( CompilerConstants.className(ScriptRuntime.class), runtimeNode.getRequest().toString(), new FunctionSignature( false, runtimeNode.getType(), runtimeNode.getArgs().size()).toString()); method.convert(runtimeNode.getType()); method.store(runtimeNode.getSymbol()); return null; } @Override public Node enter(final SplitNode splitNode) { if (splitNode.testResolved()) { return null; } final CompileUnit splitCompileUnit = splitNode.getCompileUnit(); final FunctionNode fn = getCurrentFunctionNode(); final String className = splitCompileUnit.getUnitClassName(); final String name = splitNode.getName(); final Class rtype = fn.getReturnType().getTypeClass(); final Class[] ptypes = fn.isVarArg() ? new Class[] {Object.class, ScriptFunction.class, ScriptObject.class, Object.class} : new Class[] {Object.class, ScriptFunction.class, ScriptObject.class}; setCurrentCompileUnit(splitCompileUnit); splitNode.setCompileUnit(splitCompileUnit); final Call splitCall = staticCallNoLookup( className, name, methodDescriptor(rtype, ptypes)); setCurrentMethodEmitter( splitCompileUnit.getClassEmitter().method( EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, rtype, ptypes)); method.setFunctionNode(fn); method.setSplitNode(splitNode); splitNode.setMethodEmitter(method); final MethodEmitter caller = splitNode.getCaller(); caller.loadThis(); caller.loadCallee(); caller.loadScope(); if (fn.isVarArg()) { caller.loadArguments(); } caller.invoke(splitCall); caller.storeResult(); method.begin(); method.loadUndefined(fn.getReturnType()); method.storeResult(); fixScopeSlot(); return splitNode; } private void fixScopeSlot() { if (getCurrentFunctionNode().getScopeNode().getSymbol().getSlot() != SCOPE.slot()) { // TODO hack to move the scope to the expected slot (that's needed because split methods reuse the same slots as the root method) method.load(Type.typeFor(ScriptObject.class), SCOPE.slot()); method.storeScope(); } } @Override public Node leave(final SplitNode splitNode) { try { // Wrap up this method. method.loadResult(); method._return(getCurrentFunctionNode().getReturnType()); method.end(); } catch (final Throwable t) { Context.printStackTrace(t); final VerifyError e = new VerifyError("Code generation bug in \"" + splitNode.getName() + "\": likely stack misaligned: " + t + " " + compiler.getSource().getName()); e.initCause(t); throw e; } // Handle return from split method if there was one. final MethodEmitter caller = splitNode.getCaller(); final List