Tue, 06 May 2014 17:54:15 +0530
8042364: Make __proto__ ES6 draft compliant
Reviewed-by: jlaskey, lagergren, attila
jlaskey@3 | 1 | /* |
jlaskey@7 | 2 | * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. |
jlaskey@3 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
jlaskey@3 | 4 | * |
jlaskey@3 | 5 | * This code is free software; you can redistribute it and/or modify it |
jlaskey@3 | 6 | * under the terms of the GNU General Public License version 2 only, as |
jlaskey@3 | 7 | * published by the Free Software Foundation. Oracle designates this |
jlaskey@3 | 8 | * particular file as subject to the "Classpath" exception as provided |
jlaskey@3 | 9 | * by Oracle in the LICENSE file that accompanied this code. |
jlaskey@3 | 10 | * |
jlaskey@3 | 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
jlaskey@3 | 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
jlaskey@3 | 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
jlaskey@3 | 14 | * version 2 for more details (a copy is included in the LICENSE file that |
jlaskey@3 | 15 | * accompanied this code). |
jlaskey@3 | 16 | * |
jlaskey@3 | 17 | * You should have received a copy of the GNU General Public License version |
jlaskey@3 | 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
jlaskey@3 | 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
jlaskey@3 | 20 | * |
jlaskey@3 | 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
jlaskey@3 | 22 | * or visit www.oracle.com if you need additional information or have any |
jlaskey@3 | 23 | * questions. |
jlaskey@3 | 24 | */ |
jlaskey@3 | 25 | |
jlaskey@3 | 26 | package jdk.nashorn.internal.codegen; |
jlaskey@3 | 27 | |
jlaskey@3 | 28 | import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.PRIVATE; |
jlaskey@3 | 29 | import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.STATIC; |
lagergren@211 | 30 | import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS; |
lagergren@211 | 31 | import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE; |
jlaskey@3 | 32 | import static jdk.nashorn.internal.codegen.CompilerConstants.GET_MAP; |
jlaskey@3 | 33 | import static jdk.nashorn.internal.codegen.CompilerConstants.GET_STRING; |
jlaskey@3 | 34 | import static jdk.nashorn.internal.codegen.CompilerConstants.QUICK_PREFIX; |
jlaskey@3 | 35 | import static jdk.nashorn.internal.codegen.CompilerConstants.REGEX_PREFIX; |
lagergren@211 | 36 | import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN; |
jlaskey@3 | 37 | import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; |
jlaskey@3 | 38 | import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_ARRAY_ARG; |
jlaskey@3 | 39 | import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_PREFIX; |
lagergren@211 | 40 | import static jdk.nashorn.internal.codegen.CompilerConstants.THIS; |
lagergren@211 | 41 | import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS; |
lagergren@89 | 42 | import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup; |
jlaskey@3 | 43 | import static jdk.nashorn.internal.codegen.CompilerConstants.interfaceCallNoLookup; |
jlaskey@3 | 44 | import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor; |
jlaskey@3 | 45 | import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; |
jlaskey@3 | 46 | import static jdk.nashorn.internal.codegen.CompilerConstants.typeDescriptor; |
hannesw@482 | 47 | import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup; |
jlaskey@3 | 48 | import static jdk.nashorn.internal.ir.Symbol.IS_INTERNAL; |
jlaskey@3 | 49 | import static jdk.nashorn.internal.ir.Symbol.IS_TEMP; |
jlaskey@3 | 50 | import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_FAST_SCOPE; |
jlaskey@3 | 51 | import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_SCOPE; |
jlaskey@3 | 52 | import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_STRICT; |
jlaskey@3 | 53 | |
jlaskey@3 | 54 | import java.io.PrintWriter; |
jlaskey@3 | 55 | import java.util.ArrayList; |
jlaskey@3 | 56 | import java.util.Arrays; |
jlaskey@3 | 57 | import java.util.EnumSet; |
attila@435 | 58 | import java.util.HashSet; |
jlaskey@3 | 59 | import java.util.Iterator; |
jlaskey@3 | 60 | import java.util.LinkedList; |
jlaskey@3 | 61 | import java.util.List; |
attila@435 | 62 | import java.util.Set; |
jlaskey@3 | 63 | import java.util.TreeMap; |
jlaskey@3 | 64 | import jdk.nashorn.internal.codegen.ClassEmitter.Flag; |
jlaskey@3 | 65 | import jdk.nashorn.internal.codegen.CompilerConstants.Call; |
jlaskey@3 | 66 | import jdk.nashorn.internal.codegen.RuntimeCallSite.SpecializedRuntimeNode; |
jlaskey@3 | 67 | import jdk.nashorn.internal.codegen.types.ArrayType; |
jlaskey@3 | 68 | import jdk.nashorn.internal.codegen.types.Type; |
jlaskey@3 | 69 | import jdk.nashorn.internal.ir.AccessNode; |
jlaskey@3 | 70 | import jdk.nashorn.internal.ir.BaseNode; |
jlaskey@3 | 71 | import jdk.nashorn.internal.ir.BinaryNode; |
jlaskey@3 | 72 | import jdk.nashorn.internal.ir.Block; |
attila@430 | 73 | import jdk.nashorn.internal.ir.BlockStatement; |
jlaskey@3 | 74 | import jdk.nashorn.internal.ir.BreakNode; |
lagergren@211 | 75 | import jdk.nashorn.internal.ir.BreakableNode; |
jlaskey@3 | 76 | import jdk.nashorn.internal.ir.CallNode; |
jlaskey@3 | 77 | import jdk.nashorn.internal.ir.CaseNode; |
jlaskey@3 | 78 | import jdk.nashorn.internal.ir.CatchNode; |
jlaskey@3 | 79 | import jdk.nashorn.internal.ir.ContinueNode; |
jlaskey@3 | 80 | import jdk.nashorn.internal.ir.EmptyNode; |
attila@430 | 81 | import jdk.nashorn.internal.ir.Expression; |
attila@430 | 82 | import jdk.nashorn.internal.ir.ExpressionStatement; |
jlaskey@3 | 83 | import jdk.nashorn.internal.ir.ForNode; |
jlaskey@3 | 84 | import jdk.nashorn.internal.ir.FunctionNode; |
attila@144 | 85 | import jdk.nashorn.internal.ir.FunctionNode.CompilationState; |
jlaskey@3 | 86 | import jdk.nashorn.internal.ir.IdentNode; |
jlaskey@3 | 87 | import jdk.nashorn.internal.ir.IfNode; |
jlaskey@3 | 88 | import jdk.nashorn.internal.ir.IndexNode; |
attila@311 | 89 | import jdk.nashorn.internal.ir.LexicalContext; |
attila@233 | 90 | import jdk.nashorn.internal.ir.LexicalContextNode; |
jlaskey@3 | 91 | import jdk.nashorn.internal.ir.LiteralNode; |
jlaskey@3 | 92 | import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode; |
jlaskey@3 | 93 | import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit; |
lagergren@211 | 94 | import jdk.nashorn.internal.ir.LoopNode; |
jlaskey@3 | 95 | import jdk.nashorn.internal.ir.Node; |
jlaskey@3 | 96 | import jdk.nashorn.internal.ir.ObjectNode; |
jlaskey@3 | 97 | import jdk.nashorn.internal.ir.PropertyNode; |
jlaskey@3 | 98 | import jdk.nashorn.internal.ir.ReturnNode; |
jlaskey@3 | 99 | import jdk.nashorn.internal.ir.RuntimeNode; |
jlaskey@3 | 100 | import jdk.nashorn.internal.ir.RuntimeNode.Request; |
jlaskey@3 | 101 | import jdk.nashorn.internal.ir.SplitNode; |
lagergren@253 | 102 | import jdk.nashorn.internal.ir.Statement; |
jlaskey@3 | 103 | import jdk.nashorn.internal.ir.SwitchNode; |
jlaskey@3 | 104 | import jdk.nashorn.internal.ir.Symbol; |
jlaskey@3 | 105 | import jdk.nashorn.internal.ir.TernaryNode; |
jlaskey@3 | 106 | import jdk.nashorn.internal.ir.ThrowNode; |
jlaskey@3 | 107 | import jdk.nashorn.internal.ir.TryNode; |
jlaskey@3 | 108 | import jdk.nashorn.internal.ir.UnaryNode; |
jlaskey@3 | 109 | import jdk.nashorn.internal.ir.VarNode; |
jlaskey@3 | 110 | import jdk.nashorn.internal.ir.WhileNode; |
jlaskey@3 | 111 | import jdk.nashorn.internal.ir.WithNode; |
jlaskey@3 | 112 | import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor; |
jlaskey@3 | 113 | import jdk.nashorn.internal.ir.visitor.NodeVisitor; |
sundar@414 | 114 | import jdk.nashorn.internal.objects.Global; |
sundar@414 | 115 | import jdk.nashorn.internal.objects.ScriptFunctionImpl; |
jlaskey@3 | 116 | import jdk.nashorn.internal.parser.Lexer.RegexToken; |
jlaskey@3 | 117 | import jdk.nashorn.internal.parser.TokenType; |
jlaskey@3 | 118 | import jdk.nashorn.internal.runtime.Context; |
lagergren@211 | 119 | import jdk.nashorn.internal.runtime.Debug; |
attila@144 | 120 | import jdk.nashorn.internal.runtime.DebugLogger; |
jlaskey@3 | 121 | import jdk.nashorn.internal.runtime.ECMAException; |
hannesw@219 | 122 | import jdk.nashorn.internal.runtime.JSType; |
lagergren@96 | 123 | import jdk.nashorn.internal.runtime.Property; |
jlaskey@3 | 124 | import jdk.nashorn.internal.runtime.PropertyMap; |
lagergren@137 | 125 | import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; |
jlaskey@3 | 126 | import jdk.nashorn.internal.runtime.Scope; |
jlaskey@3 | 127 | import jdk.nashorn.internal.runtime.ScriptFunction; |
jlaskey@3 | 128 | import jdk.nashorn.internal.runtime.ScriptObject; |
jlaskey@3 | 129 | import jdk.nashorn.internal.runtime.ScriptRuntime; |
jlaskey@3 | 130 | import jdk.nashorn.internal.runtime.Source; |
jlaskey@3 | 131 | import jdk.nashorn.internal.runtime.Undefined; |
hannesw@482 | 132 | import jdk.nashorn.internal.runtime.arrays.ArrayData; |
jlaskey@3 | 133 | import jdk.nashorn.internal.runtime.linker.LinkerCallSite; |
jlaskey@3 | 134 | |
jlaskey@3 | 135 | /** |
jlaskey@3 | 136 | * This is the lowest tier of the code generator. It takes lowered ASTs emitted |
jlaskey@3 | 137 | * from Lower and emits Java byte code. The byte code emission logic is broken |
jlaskey@3 | 138 | * out into MethodEmitter. MethodEmitter works internally with a type stack, and |
jlaskey@3 | 139 | * keeps track of the contents of the byte code stack. This way we avoid a large |
jlaskey@3 | 140 | * number of special cases on the form |
jlaskey@3 | 141 | * <pre> |
jlaskey@3 | 142 | * if (type == INT) { |
jlaskey@3 | 143 | * visitInsn(ILOAD, slot); |
jlaskey@3 | 144 | * } else if (type == DOUBLE) { |
jlaskey@3 | 145 | * visitInsn(DOUBLE, slot); |
jlaskey@3 | 146 | * } |
jlaskey@3 | 147 | * </pre> |
jlaskey@3 | 148 | * This quickly became apparent when the code generator was generalized to work |
jlaskey@3 | 149 | * with all types, and not just numbers or objects. |
jlaskey@3 | 150 | * <p> |
jlaskey@3 | 151 | * The CodeGenerator visits nodes only once, tags them as resolved and emits |
jlaskey@3 | 152 | * bytecode for them. |
jlaskey@3 | 153 | */ |
lagergren@290 | 154 | final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContext> { |
jlaskey@3 | 155 | |
sundar@414 | 156 | private static final String GLOBAL_OBJECT = Type.getInternalName(Global.class); |
sundar@414 | 157 | |
sundar@414 | 158 | private static final String SCRIPTFUNCTION_IMPL_OBJECT = Type.getInternalName(ScriptFunctionImpl.class); |
lagergren@89 | 159 | |
lagergren@89 | 160 | /** Constant data & installation. The only reason the compiler keeps this is because it is assigned |
lagergren@89 | 161 | * by reflection in class installation */ |
jlaskey@3 | 162 | private final Compiler compiler; |
jlaskey@3 | 163 | |
jlaskey@3 | 164 | /** Call site flags given to the code generator to be used for all generated call sites */ |
jlaskey@3 | 165 | private final int callSiteFlags; |
jlaskey@3 | 166 | |
jlaskey@3 | 167 | /** How many regexp fields have been emitted */ |
jlaskey@3 | 168 | private int regexFieldCount; |
jlaskey@3 | 169 | |
lagergren@290 | 170 | /** Line number for last statement. If we encounter a new line number, line number bytecode information |
lagergren@290 | 171 | * needs to be generated */ |
lagergren@290 | 172 | private int lastLineNumber = -1; |
lagergren@290 | 173 | |
lagergren@290 | 174 | /** When should we stop caching regexp expressions in fields to limit bytecode size? */ |
lagergren@290 | 175 | private static final int MAX_REGEX_FIELDS = 2 * 1024; |
lagergren@211 | 176 | |
lagergren@211 | 177 | /** Current method emitter */ |
lagergren@211 | 178 | private MethodEmitter method; |
lagergren@211 | 179 | |
lagergren@211 | 180 | /** Current compile unit */ |
lagergren@211 | 181 | private CompileUnit unit; |
attila@144 | 182 | |
attila@144 | 183 | private static final DebugLogger LOG = new DebugLogger("codegen", "nashorn.codegen.debug"); |
attila@144 | 184 | |
hannesw@415 | 185 | /** From what size should we use spill instead of fields for JavaScript objects? */ |
hannesw@415 | 186 | private static final int OBJECT_SPILL_THRESHOLD = 300; |
lagergren@290 | 187 | |
attila@435 | 188 | private final Set<String> emittedMethods = new HashSet<>(); |
attila@435 | 189 | |
jlaskey@3 | 190 | /** |
jlaskey@3 | 191 | * Constructor. |
jlaskey@3 | 192 | * |
jlaskey@3 | 193 | * @param compiler |
jlaskey@3 | 194 | */ |
jlaskey@3 | 195 | CodeGenerator(final Compiler compiler) { |
lagergren@290 | 196 | super(new CodeGeneratorLexicalContext()); |
jlaskey@3 | 197 | this.compiler = compiler; |
sundar@118 | 198 | this.callSiteFlags = compiler.getEnv()._callsite_flags; |
jlaskey@3 | 199 | } |
jlaskey@3 | 200 | |
jlaskey@3 | 201 | /** |
jlaskey@3 | 202 | * Gets the call site flags, adding the strict flag if the current function |
jlaskey@3 | 203 | * being generated is in strict mode |
jlaskey@3 | 204 | * |
jlaskey@3 | 205 | * @return the correct flags for a call site in the current function |
jlaskey@3 | 206 | */ |
lagergren@96 | 207 | int getCallSiteFlags() { |
lagergren@290 | 208 | return lc.getCurrentFunction().isStrict() ? callSiteFlags | CALLSITE_STRICT : callSiteFlags; |
jlaskey@3 | 209 | } |
jlaskey@3 | 210 | |
jlaskey@3 | 211 | /** |
jlaskey@3 | 212 | * Load an identity node |
jlaskey@3 | 213 | * |
jlaskey@3 | 214 | * @param identNode an identity node to load |
jlaskey@3 | 215 | * @return the method generator used |
jlaskey@3 | 216 | */ |
lagergren@605 | 217 | private MethodEmitter loadIdent(final IdentNode identNode, final Type type) { |
jlaskey@3 | 218 | final Symbol symbol = identNode.getSymbol(); |
jlaskey@3 | 219 | |
jlaskey@3 | 220 | if (!symbol.isScope()) { |
attila@65 | 221 | assert symbol.hasSlot() || symbol.isParam(); |
lagergren@605 | 222 | return method.load(symbol).convert(type); |
jlaskey@3 | 223 | } |
jlaskey@3 | 224 | |
lagergren@252 | 225 | final String name = symbol.getName(); |
lagergren@290 | 226 | final Source source = lc.getCurrentFunction().getSource(); |
jlaskey@3 | 227 | |
jlaskey@3 | 228 | if (CompilerConstants.__FILE__.name().equals(name)) { |
lagergren@252 | 229 | return method.load(source.getName()); |
jlaskey@3 | 230 | } else if (CompilerConstants.__DIR__.name().equals(name)) { |
lagergren@252 | 231 | return method.load(source.getBase()); |
jlaskey@3 | 232 | } else if (CompilerConstants.__LINE__.name().equals(name)) { |
lagergren@252 | 233 | return method.load(source.getLine(identNode.position())).convert(Type.OBJECT); |
jlaskey@3 | 234 | } else { |
jlaskey@3 | 235 | assert identNode.getSymbol().isScope() : identNode + " is not in scope!"; |
jlaskey@3 | 236 | |
jlaskey@3 | 237 | final int flags = CALLSITE_SCOPE | getCallSiteFlags(); |
lagergren@211 | 238 | method.loadCompilerConstant(SCOPE); |
jlaskey@3 | 239 | |
attila@144 | 240 | if (isFastScope(symbol)) { |
jlaskey@3 | 241 | // Only generate shared scope getter for fast-scope symbols so we know we can dial in correct scope. |
jlaskey@3 | 242 | if (symbol.getUseCount() > SharedScopeCall.FAST_SCOPE_GET_THRESHOLD) { |
lagergren@605 | 243 | return loadSharedScopeVar(type, symbol, flags); |
jlaskey@3 | 244 | } |
lagergren@605 | 245 | return loadFastScopeVar(type, symbol, flags, identNode.isFunction()); |
jlaskey@3 | 246 | } |
lagergren@605 | 247 | return method.dynamicGet(type, identNode.getName(), flags, identNode.isFunction()); |
jlaskey@3 | 248 | } |
jlaskey@3 | 249 | } |
jlaskey@3 | 250 | |
attila@144 | 251 | /** |
attila@144 | 252 | * Check if this symbol can be accessed directly with a putfield or getfield or dynamic load |
attila@144 | 253 | * |
hannesw@380 | 254 | * @param symbol symbol to check for fast scope |
attila@144 | 255 | * @return true if fast scope |
attila@144 | 256 | */ |
attila@144 | 257 | private boolean isFastScope(final Symbol symbol) { |
lagergren@290 | 258 | if (!symbol.isScope()) { |
attila@144 | 259 | return false; |
attila@144 | 260 | } |
lagergren@290 | 261 | |
lagergren@290 | 262 | if (!lc.inDynamicScope()) { |
attila@233 | 263 | // If there's no with or eval in context, and the symbol is marked as scoped, it is fast scoped. Such a |
attila@233 | 264 | // symbol must either be global, or its defining block must need scope. |
attila@233 | 265 | assert symbol.isGlobal() || lc.getDefiningBlock(symbol).needsScope() : symbol.getName(); |
attila@233 | 266 | return true; |
attila@233 | 267 | } |
lagergren@290 | 268 | |
lagergren@290 | 269 | if (symbol.isGlobal()) { |
attila@233 | 270 | // Shortcut: if there's a with or eval in context, globals can't be fast scoped |
attila@233 | 271 | return false; |
attila@233 | 272 | } |
lagergren@290 | 273 | |
attila@233 | 274 | // Otherwise, check if there's a dynamic scope between use of the symbol and its definition |
attila@233 | 275 | final String name = symbol.getName(); |
attila@233 | 276 | boolean previousWasBlock = false; |
attila@233 | 277 | for (final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) { |
attila@233 | 278 | final LexicalContextNode node = it.next(); |
lagergren@290 | 279 | if (node instanceof Block) { |
attila@233 | 280 | // If this block defines the symbol, then we can fast scope the symbol. |
attila@233 | 281 | final Block block = (Block)node; |
lagergren@290 | 282 | if (block.getExistingSymbol(name) == symbol) { |
attila@233 | 283 | assert block.needsScope(); |
attila@233 | 284 | return true; |
attila@233 | 285 | } |
attila@233 | 286 | previousWasBlock = true; |
attila@233 | 287 | } else { |
lagergren@290 | 288 | if ((node instanceof WithNode && previousWasBlock) || (node instanceof FunctionNode && CodeGeneratorLexicalContext.isFunctionDynamicScope((FunctionNode)node))) { |
attila@233 | 289 | // If we hit a scope that can have symbols introduced into it at run time before finding the defining |
attila@233 | 290 | // block, the symbol can't be fast scoped. A WithNode only counts if we've immediately seen a block |
attila@233 | 291 | // before - its block. Otherwise, we are currently processing the WithNode's expression, and that's |
attila@233 | 292 | // obviously not subjected to introducing new symbols. |
attila@233 | 293 | return false; |
attila@233 | 294 | } |
attila@233 | 295 | previousWasBlock = false; |
attila@144 | 296 | } |
attila@144 | 297 | } |
attila@233 | 298 | // Should've found the symbol defined in a block |
attila@233 | 299 | throw new AssertionError(); |
attila@144 | 300 | } |
attila@144 | 301 | |
jlaskey@3 | 302 | private MethodEmitter loadSharedScopeVar(final Type valueType, final Symbol symbol, final int flags) { |
lagergren@290 | 303 | method.load(isFastScope(symbol) ? getScopeProtoDepth(lc.getCurrentBlock(), symbol) : -1); |
lagergren@290 | 304 | final SharedScopeCall scopeCall = lc.getScopeGet(unit, valueType, symbol, flags | CALLSITE_FAST_SCOPE); |
lagergren@290 | 305 | return scopeCall.generateInvoke(method); |
jlaskey@3 | 306 | } |
jlaskey@3 | 307 | |
jlaskey@3 | 308 | private MethodEmitter loadFastScopeVar(final Type valueType, final Symbol symbol, final int flags, final boolean isMethod) { |
jlaskey@3 | 309 | loadFastScopeProto(symbol, false); |
lagergren@290 | 310 | return method.dynamicGet(valueType, symbol.getName(), flags | CALLSITE_FAST_SCOPE, isMethod); |
jlaskey@3 | 311 | } |
jlaskey@3 | 312 | |
lagergren@605 | 313 | private MethodEmitter storeFastScopeVar(final Symbol symbol, final int flags) { |
jlaskey@3 | 314 | loadFastScopeProto(symbol, true); |
lagergren@605 | 315 | method.dynamicSet(symbol.getName(), flags | CALLSITE_FAST_SCOPE); |
jlaskey@3 | 316 | return method; |
jlaskey@3 | 317 | } |
jlaskey@3 | 318 | |
attila@144 | 319 | private int getScopeProtoDepth(final Block startingBlock, final Symbol symbol) { |
attila@144 | 320 | int depth = 0; |
lagergren@211 | 321 | final String name = symbol.getName(); |
lagergren@290 | 322 | for(final Iterator<Block> blocks = lc.getBlocks(startingBlock); blocks.hasNext();) { |
attila@144 | 323 | final Block currentBlock = blocks.next(); |
lagergren@211 | 324 | if (currentBlock.getExistingSymbol(name) == symbol) { |
attila@144 | 325 | return depth; |
attila@144 | 326 | } |
attila@144 | 327 | if (currentBlock.needsScope()) { |
attila@144 | 328 | ++depth; |
jlaskey@3 | 329 | } |
jlaskey@3 | 330 | } |
jlaskey@3 | 331 | return -1; |
jlaskey@3 | 332 | } |
jlaskey@3 | 333 | |
jlaskey@3 | 334 | private void loadFastScopeProto(final Symbol symbol, final boolean swap) { |
lagergren@290 | 335 | final int depth = getScopeProtoDepth(lc.getCurrentBlock(), symbol); |
jlaskey@3 | 336 | assert depth != -1; |
lagergren@211 | 337 | if (depth > 0) { |
jlaskey@3 | 338 | if (swap) { |
jlaskey@3 | 339 | method.swap(); |
jlaskey@3 | 340 | } |
jlaskey@3 | 341 | for (int i = 0; i < depth; i++) { |
jlaskey@3 | 342 | method.invoke(ScriptObject.GET_PROTO); |
jlaskey@3 | 343 | } |
jlaskey@3 | 344 | if (swap) { |
jlaskey@3 | 345 | method.swap(); |
jlaskey@3 | 346 | } |
jlaskey@3 | 347 | } |
jlaskey@3 | 348 | } |
jlaskey@3 | 349 | |
jlaskey@3 | 350 | /** |
jlaskey@3 | 351 | * Generate code that loads this node to the stack. This method is only |
jlaskey@3 | 352 | * public to be accessible from the maps sub package. Do not call externally |
jlaskey@3 | 353 | * |
jlaskey@3 | 354 | * @param node node to load |
jlaskey@3 | 355 | * |
jlaskey@3 | 356 | * @return the method emitter used |
jlaskey@3 | 357 | */ |
attila@430 | 358 | MethodEmitter load(final Expression node) { |
lagergren@605 | 359 | return load(node, node.hasType() ? node.getType() : null, false); |
jlaskey@3 | 360 | } |
jlaskey@3 | 361 | |
hannesw@652 | 362 | // Test whether conversion from source to target involves a call of ES 9.1 ToPrimitive |
hannesw@652 | 363 | // with possible side effects from calling an object's toString or valueOf methods. |
hannesw@652 | 364 | private boolean noToPrimitiveConversion(final Type source, final Type target) { |
hannesw@652 | 365 | // Object to boolean conversion does not cause ToPrimitive call |
hannesw@652 | 366 | return source.isJSPrimitive() || !target.isJSPrimitive() || target.isBoolean(); |
lagergren@605 | 367 | } |
lagergren@605 | 368 | |
lagergren@605 | 369 | MethodEmitter loadBinaryOperands(final Expression lhs, final Expression rhs, final Type type) { |
lagergren@605 | 370 | return loadBinaryOperands(lhs, rhs, type, false); |
lagergren@605 | 371 | } |
lagergren@605 | 372 | |
lagergren@605 | 373 | private MethodEmitter loadBinaryOperands(final Expression lhs, final Expression rhs, final Type type, final boolean baseAlreadyOnStack) { |
lagergren@605 | 374 | // ECMAScript 5.1 specification (sections 11.5-11.11 and 11.13) prescribes that when evaluating a binary |
lagergren@605 | 375 | // expression "LEFT op RIGHT", the order of operations must be: LOAD LEFT, LOAD RIGHT, CONVERT LEFT, CONVERT |
lagergren@605 | 376 | // RIGHT, EXECUTE OP. Unfortunately, doing it in this order defeats potential optimizations that arise when we |
lagergren@605 | 377 | // can combine a LOAD with a CONVERT operation (e.g. use a dynamic getter with the conversion target type as its |
lagergren@605 | 378 | // return value). What we do here is reorder LOAD RIGHT and CONVERT LEFT when possible; it is possible only when |
lagergren@605 | 379 | // we can prove that executing CONVERT LEFT can't have a side effect that changes the value of LOAD RIGHT. |
hannesw@652 | 380 | // Basically, if we know that either LEFT already is a primitive value, or does not have to be converted to |
hannesw@652 | 381 | // a primitive value, or RIGHT is an expression that loads without side effects, then we can do the |
lagergren@605 | 382 | // reordering and collapse LOAD/CONVERT into a single operation; otherwise we need to do the more costly |
lagergren@605 | 383 | // separate operations to preserve specification semantics. |
hannesw@652 | 384 | if (noToPrimitiveConversion(lhs.getType(), type) || rhs.isLocal()) { |
lagergren@605 | 385 | // Can reorder. Combine load and convert into single operations. |
lagergren@605 | 386 | load(lhs, type, baseAlreadyOnStack); |
lagergren@605 | 387 | load(rhs, type, false); |
hannesw@652 | 388 | } else { |
hannesw@652 | 389 | // Can't reorder. Load and convert separately. |
hannesw@652 | 390 | load(lhs, lhs.getType(), baseAlreadyOnStack); |
hannesw@652 | 391 | load(rhs, rhs.getType(), false); |
hannesw@652 | 392 | method.swap().convert(type).swap().convert(type); |
lagergren@605 | 393 | } |
lagergren@605 | 394 | |
lagergren@605 | 395 | return method; |
lagergren@605 | 396 | } |
lagergren@605 | 397 | |
lagergren@605 | 398 | MethodEmitter loadBinaryOperands(final BinaryNode node) { |
lagergren@605 | 399 | return loadBinaryOperands(node.lhs(), node.rhs(), node.getType(), false); |
lagergren@605 | 400 | } |
lagergren@605 | 401 | |
attila@644 | 402 | MethodEmitter load(final Expression node, final Type type) { |
lagergren@605 | 403 | return load(node, type, false); |
lagergren@605 | 404 | } |
lagergren@605 | 405 | |
lagergren@605 | 406 | private MethodEmitter load(final Expression node, final Type type, final boolean baseAlreadyOnStack) { |
jlaskey@3 | 407 | final Symbol symbol = node.getSymbol(); |
jlaskey@3 | 408 | |
jlaskey@3 | 409 | // If we lack symbols, we just generate what we see. |
lagergren@605 | 410 | if (symbol == null || type == null) { |
jlaskey@3 | 411 | node.accept(this); |
jlaskey@3 | 412 | return method; |
jlaskey@3 | 413 | } |
jlaskey@3 | 414 | |
attila@671 | 415 | assert !type.isUnknown(); |
attila@671 | 416 | |
jlaskey@3 | 417 | /* |
jlaskey@3 | 418 | * The load may be of type IdentNode, e.g. "x", AccessNode, e.g. "x.y" |
jlaskey@3 | 419 | * or IndexNode e.g. "x[y]". Both AccessNodes and IndexNodes are |
jlaskey@3 | 420 | * BaseNodes and the logic for loading the base object is reused |
jlaskey@3 | 421 | */ |
jlaskey@3 | 422 | final CodeGenerator codegen = this; |
jlaskey@3 | 423 | |
lagergren@605 | 424 | node.accept(new NodeVisitor<LexicalContext>(lc) { |
jlaskey@3 | 425 | @Override |
lagergren@211 | 426 | public boolean enterIdentNode(final IdentNode identNode) { |
lagergren@605 | 427 | loadIdent(identNode, type); |
lagergren@211 | 428 | return false; |
jlaskey@3 | 429 | } |
jlaskey@3 | 430 | |
jlaskey@3 | 431 | @Override |
lagergren@211 | 432 | public boolean enterAccessNode(final AccessNode accessNode) { |
jlaskey@3 | 433 | if (!baseAlreadyOnStack) { |
attila@644 | 434 | load(accessNode.getBase(), Type.OBJECT); |
jlaskey@3 | 435 | } |
jlaskey@3 | 436 | assert method.peekType().isObject(); |
lagergren@605 | 437 | method.dynamicGet(type, accessNode.getProperty().getName(), getCallSiteFlags(), accessNode.isFunction()); |
lagergren@211 | 438 | return false; |
jlaskey@3 | 439 | } |
jlaskey@3 | 440 | |
jlaskey@3 | 441 | @Override |
lagergren@211 | 442 | public boolean enterIndexNode(final IndexNode indexNode) { |
jlaskey@3 | 443 | if (!baseAlreadyOnStack) { |
attila@644 | 444 | load(indexNode.getBase(), Type.OBJECT); |
jlaskey@3 | 445 | load(indexNode.getIndex()); |
jlaskey@3 | 446 | } |
lagergren@605 | 447 | method.dynamicGetIndex(type, getCallSiteFlags(), indexNode.isFunction()); |
lagergren@211 | 448 | return false; |
jlaskey@3 | 449 | } |
jlaskey@3 | 450 | |
jlaskey@3 | 451 | @Override |
lagergren@211 | 452 | public boolean enterFunctionNode(FunctionNode functionNode) { |
attila@144 | 453 | // function nodes will always leave a constructed function object on stack, no need to load the symbol |
attila@144 | 454 | // separately as in enterDefault() |
attila@622 | 455 | lc.pop(functionNode); |
attila@144 | 456 | functionNode.accept(codegen); |
attila@622 | 457 | // NOTE: functionNode.accept() will produce a different FunctionNode that we discard. This incidentally |
attila@622 | 458 | // doesn't cause problems as we're never touching FunctionNode again after it's visited here - codegen |
attila@622 | 459 | // is the last element in the compilation pipeline, the AST it produces is not used externally. So, we |
attila@622 | 460 | // re-push the original functionNode. |
attila@622 | 461 | lc.push(functionNode); |
lagergren@605 | 462 | method.convert(type); |
lagergren@211 | 463 | return false; |
attila@144 | 464 | } |
attila@144 | 465 | |
attila@144 | 466 | @Override |
lagergren@605 | 467 | public boolean enterCallNode(CallNode callNode) { |
lagergren@605 | 468 | return codegen.enterCallNode(callNode, type); |
lagergren@605 | 469 | } |
lagergren@605 | 470 | |
lagergren@605 | 471 | @Override |
lagergren@605 | 472 | public boolean enterLiteralNode(LiteralNode<?> literalNode) { |
lagergren@605 | 473 | return codegen.enterLiteralNode(literalNode, type); |
lagergren@605 | 474 | } |
lagergren@605 | 475 | |
lagergren@605 | 476 | @Override |
lagergren@211 | 477 | public boolean enterDefault(final Node otherNode) { |
lagergren@605 | 478 | final Node currentDiscard = codegen.lc.getCurrentDiscard(); |
jlaskey@3 | 479 | otherNode.accept(codegen); // generate code for whatever we are looking at. |
lagergren@605 | 480 | if(currentDiscard != otherNode) { |
lagergren@605 | 481 | method.load(symbol); // load the final symbol to the stack (or nop if no slot, then result is already there) |
lagergren@605 | 482 | assert method.peekType() != null; |
lagergren@605 | 483 | method.convert(type); |
lagergren@605 | 484 | } |
lagergren@211 | 485 | return false; |
jlaskey@3 | 486 | } |
jlaskey@3 | 487 | }); |
jlaskey@3 | 488 | |
jlaskey@3 | 489 | return method; |
jlaskey@3 | 490 | } |
jlaskey@3 | 491 | |
jlaskey@3 | 492 | @Override |
lagergren@211 | 493 | public boolean enterAccessNode(final AccessNode accessNode) { |
jlaskey@3 | 494 | load(accessNode); |
lagergren@211 | 495 | return false; |
jlaskey@3 | 496 | } |
jlaskey@3 | 497 | |
jlaskey@3 | 498 | /** |
jlaskey@3 | 499 | * Initialize a specific set of vars to undefined. This has to be done at |
jlaskey@3 | 500 | * the start of each method for local variables that aren't passed as |
jlaskey@3 | 501 | * parameters. |
jlaskey@3 | 502 | * |
jlaskey@3 | 503 | * @param symbols list of symbols. |
jlaskey@3 | 504 | */ |
jlaskey@3 | 505 | private void initSymbols(final Iterable<Symbol> symbols) { |
jlaskey@3 | 506 | final LinkedList<Symbol> numbers = new LinkedList<>(); |
jlaskey@3 | 507 | final LinkedList<Symbol> objects = new LinkedList<>(); |
jlaskey@3 | 508 | |
jlaskey@3 | 509 | for (final Symbol symbol : symbols) { |
jlaskey@3 | 510 | /* |
jlaskey@3 | 511 | * The following symbols are guaranteed to be defined and thus safe |
attila@62 | 512 | * from having undefined written to them: parameters internals this |
jlaskey@3 | 513 | * |
jlaskey@3 | 514 | * Otherwise we must, unless we perform control/escape analysis, |
jlaskey@3 | 515 | * assign them undefined. |
jlaskey@3 | 516 | */ |
jlaskey@3 | 517 | final boolean isInternal = symbol.isParam() || symbol.isInternal() || symbol.isThis() || !symbol.canBeUndefined(); |
jlaskey@3 | 518 | |
jlaskey@3 | 519 | if (symbol.hasSlot() && !isInternal) { |
lagergren@290 | 520 | assert symbol.getSymbolType().isNumber() || symbol.getSymbolType().isObject() : "no potentially undefined narrower local vars than doubles are allowed: " + symbol + " in " + lc.getCurrentFunction(); |
jlaskey@3 | 521 | if (symbol.getSymbolType().isNumber()) { |
jlaskey@3 | 522 | numbers.add(symbol); |
jlaskey@3 | 523 | } else if (symbol.getSymbolType().isObject()) { |
jlaskey@3 | 524 | objects.add(symbol); |
jlaskey@3 | 525 | } |
jlaskey@3 | 526 | } |
jlaskey@3 | 527 | } |
jlaskey@3 | 528 | |
jlaskey@3 | 529 | initSymbols(numbers, Type.NUMBER); |
jlaskey@3 | 530 | initSymbols(objects, Type.OBJECT); |
jlaskey@3 | 531 | } |
jlaskey@3 | 532 | |
jlaskey@3 | 533 | private void initSymbols(final LinkedList<Symbol> symbols, final Type type) { |
attila@312 | 534 | final Iterator<Symbol> it = symbols.iterator(); |
attila@312 | 535 | if(it.hasNext()) { |
attila@312 | 536 | method.loadUndefined(type); |
attila@312 | 537 | boolean hasNext; |
attila@312 | 538 | do { |
attila@312 | 539 | final Symbol symbol = it.next(); |
attila@312 | 540 | hasNext = it.hasNext(); |
attila@312 | 541 | if(hasNext) { |
attila@312 | 542 | method.dup(); |
attila@312 | 543 | } |
attila@312 | 544 | method.store(symbol); |
attila@312 | 545 | } while(hasNext); |
jlaskey@3 | 546 | } |
jlaskey@3 | 547 | } |
jlaskey@3 | 548 | |
jlaskey@3 | 549 | /** |
jlaskey@3 | 550 | * Create symbol debug information. |
jlaskey@3 | 551 | * |
jlaskey@3 | 552 | * @param block block containing symbols. |
jlaskey@3 | 553 | */ |
jlaskey@3 | 554 | private void symbolInfo(final Block block) { |
lagergren@247 | 555 | for (final Symbol symbol : block.getSymbols()) { |
lagergren@211 | 556 | if (symbol.hasSlot()) { |
lagergren@211 | 557 | method.localVariable(symbol, block.getEntryLabel(), block.getBreakLabel()); |
lagergren@211 | 558 | } |
jlaskey@3 | 559 | } |
jlaskey@3 | 560 | } |
jlaskey@3 | 561 | |
jlaskey@3 | 562 | @Override |
lagergren@211 | 563 | public boolean enterBlock(final Block block) { |
attila@435 | 564 | if(lc.isFunctionBody() && emittedMethods.contains(lc.getCurrentFunction().getName())) { |
attila@435 | 565 | return false; |
attila@435 | 566 | } |
jlaskey@3 | 567 | method.label(block.getEntryLabel()); |
jlaskey@3 | 568 | initLocals(block); |
jlaskey@3 | 569 | |
lagergren@211 | 570 | return true; |
jlaskey@3 | 571 | } |
jlaskey@3 | 572 | |
jlaskey@3 | 573 | @Override |
attila@144 | 574 | public Node leaveBlock(final Block block) { |
jlaskey@3 | 575 | method.label(block.getBreakLabel()); |
jlaskey@3 | 576 | symbolInfo(block); |
jlaskey@3 | 577 | |
lagergren@211 | 578 | if (block.needsScope() && !block.isTerminal()) { |
jlaskey@3 | 579 | popBlockScope(block); |
jlaskey@3 | 580 | } |
jlaskey@3 | 581 | return block; |
jlaskey@3 | 582 | } |
jlaskey@3 | 583 | |
jlaskey@3 | 584 | private void popBlockScope(final Block block) { |
jlaskey@3 | 585 | final Label exitLabel = new Label("block_exit"); |
jlaskey@3 | 586 | final Label recoveryLabel = new Label("block_catch"); |
jlaskey@3 | 587 | final Label skipLabel = new Label("skip_catch"); |
jlaskey@3 | 588 | |
jlaskey@3 | 589 | /* pop scope a la try-finally */ |
lagergren@211 | 590 | method.loadCompilerConstant(SCOPE); |
jlaskey@3 | 591 | method.invoke(ScriptObject.GET_PROTO); |
lagergren@211 | 592 | method.storeCompilerConstant(SCOPE); |
jlaskey@3 | 593 | method._goto(skipLabel); |
jlaskey@3 | 594 | method.label(exitLabel); |
jlaskey@3 | 595 | |
jlaskey@3 | 596 | method._catch(recoveryLabel); |
lagergren@211 | 597 | method.loadCompilerConstant(SCOPE); |
jlaskey@3 | 598 | method.invoke(ScriptObject.GET_PROTO); |
lagergren@211 | 599 | method.storeCompilerConstant(SCOPE); |
jlaskey@3 | 600 | method.athrow(); |
jlaskey@3 | 601 | method.label(skipLabel); |
jlaskey@3 | 602 | method._try(block.getEntryLabel(), exitLabel, recoveryLabel, Throwable.class); |
jlaskey@3 | 603 | } |
jlaskey@3 | 604 | |
jlaskey@3 | 605 | @Override |
lagergren@211 | 606 | public boolean enterBreakNode(final BreakNode breakNode) { |
lagergren@253 | 607 | lineNumber(breakNode); |
lagergren@253 | 608 | |
lagergren@290 | 609 | final BreakableNode breakFrom = lc.getBreakable(breakNode.getLabel()); |
lagergren@290 | 610 | for (int i = 0; i < lc.getScopeNestingLevelTo(breakFrom); i++) { |
jlaskey@3 | 611 | closeWith(); |
jlaskey@3 | 612 | } |
lagergren@290 | 613 | method.splitAwareGoto(lc, breakFrom.getBreakLabel()); |
lagergren@211 | 614 | |
lagergren@211 | 615 | return false; |
jlaskey@3 | 616 | } |
jlaskey@3 | 617 | |
attila@430 | 618 | private int loadArgs(final List<Expression> args) { |
jlaskey@3 | 619 | return loadArgs(args, null, false, args.size()); |
jlaskey@3 | 620 | } |
jlaskey@3 | 621 | |
attila@430 | 622 | private int loadArgs(final List<Expression> args, final String signature, final boolean isVarArg, final int argCount) { |
jlaskey@3 | 623 | // arg have already been converted to objects here. |
jlaskey@3 | 624 | if (isVarArg || argCount > LinkerCallSite.ARGLIMIT) { |
jlaskey@3 | 625 | loadArgsArray(args); |
lagergren@66 | 626 | return 1; |
jlaskey@3 | 627 | } |
jlaskey@3 | 628 | |
jlaskey@3 | 629 | // pad with undefined if size is too short. argCount is the real number of args |
jlaskey@3 | 630 | int n = 0; |
jlaskey@3 | 631 | final Type[] params = signature == null ? null : Type.getMethodArguments(signature); |
attila@430 | 632 | for (final Expression arg : args) { |
jlaskey@3 | 633 | assert arg != null; |
attila@31 | 634 | if (n >= argCount) { |
attila@644 | 635 | load(arg); |
attila@31 | 636 | method.pop(); // we had to load the arg for its side effects |
attila@31 | 637 | } else if (params != null) { |
attila@644 | 638 | load(arg, params[n]); |
attila@644 | 639 | } else { |
attila@644 | 640 | load(arg); |
jlaskey@3 | 641 | } |
jlaskey@3 | 642 | n++; |
jlaskey@3 | 643 | } |
jlaskey@3 | 644 | |
jlaskey@3 | 645 | while (n < argCount) { |
jlaskey@3 | 646 | method.loadUndefined(Type.OBJECT); |
jlaskey@3 | 647 | n++; |
jlaskey@3 | 648 | } |
jlaskey@3 | 649 | |
lagergren@66 | 650 | return argCount; |
jlaskey@3 | 651 | } |
jlaskey@3 | 652 | |
lagergren@605 | 653 | |
jlaskey@3 | 654 | @Override |
lagergren@211 | 655 | public boolean enterCallNode(final CallNode callNode) { |
lagergren@605 | 656 | return enterCallNode(callNode, callNode.getType()); |
lagergren@605 | 657 | } |
lagergren@605 | 658 | |
lagergren@605 | 659 | private boolean enterCallNode(final CallNode callNode, final Type callNodeType) { |
attila@430 | 660 | lineNumber(callNode.getLineNumber()); |
attila@430 | 661 | |
attila@430 | 662 | final List<Expression> args = callNode.getArgs(); |
attila@430 | 663 | final Expression function = callNode.getFunction(); |
attila@430 | 664 | final Block currentBlock = lc.getCurrentBlock(); |
lagergren@290 | 665 | final CodeGeneratorLexicalContext codegenLexicalContext = lc; |
lagergren@290 | 666 | |
lagergren@290 | 667 | function.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { |
lagergren@290 | 668 | |
lagergren@290 | 669 | private MethodEmitter sharedScopeCall(final IdentNode identNode, final int flags) { |
jlaskey@3 | 670 | final Symbol symbol = identNode.getSymbol(); |
jlaskey@3 | 671 | int scopeCallFlags = flags; |
lagergren@211 | 672 | method.loadCompilerConstant(SCOPE); |
attila@144 | 673 | if (isFastScope(symbol)) { |
jlaskey@3 | 674 | method.load(getScopeProtoDepth(currentBlock, symbol)); |
jlaskey@3 | 675 | scopeCallFlags |= CALLSITE_FAST_SCOPE; |
jlaskey@3 | 676 | } else { |
jlaskey@3 | 677 | method.load(-1); // Bypass fast-scope code in shared callsite |
jlaskey@3 | 678 | } |
jlaskey@3 | 679 | loadArgs(args); |
jlaskey@3 | 680 | final Type[] paramTypes = method.getTypesFromStack(args.size()); |
lagergren@407 | 681 | final SharedScopeCall scopeCall = codegenLexicalContext.getScopeCall(unit, symbol, identNode.getType(), callNodeType, paramTypes, scopeCallFlags); |
lagergren@290 | 682 | return scopeCall.generateInvoke(method); |
jlaskey@3 | 683 | } |
jlaskey@3 | 684 | |
jlaskey@3 | 685 | private void scopeCall(final IdentNode node, final int flags) { |
lagergren@605 | 686 | load(node, Type.OBJECT); // Type.OBJECT as foo() makes no sense if foo == 3 |
jlaskey@3 | 687 | // ScriptFunction will see CALLSITE_SCOPE and will bind scope accordingly. |
hannesw@769 | 688 | method.loadUndefined(Type.OBJECT); //the 'this' object |
lagergren@407 | 689 | method.dynamicCall(callNodeType, 2 + loadArgs(args), flags); |
jlaskey@3 | 690 | } |
jlaskey@3 | 691 | |
jlaskey@3 | 692 | private void evalCall(final IdentNode node, final int flags) { |
lagergren@605 | 693 | load(node, Type.OBJECT); // Type.OBJECT as foo() makes no sense if foo == 3 |
jlaskey@3 | 694 | |
jlaskey@3 | 695 | final Label not_eval = new Label("not_eval"); |
jlaskey@3 | 696 | final Label eval_done = new Label("eval_done"); |
jlaskey@3 | 697 | |
jlaskey@3 | 698 | // check if this is the real built-in eval |
jlaskey@3 | 699 | method.dup(); |
jlaskey@3 | 700 | globalIsEval(); |
jlaskey@3 | 701 | |
jlaskey@3 | 702 | method.ifeq(not_eval); |
jlaskey@3 | 703 | // We don't need ScriptFunction object for 'eval' |
jlaskey@3 | 704 | method.pop(); |
jlaskey@3 | 705 | |
lagergren@211 | 706 | method.loadCompilerConstant(SCOPE); // Load up self (scope). |
jlaskey@3 | 707 | |
jlaskey@3 | 708 | final CallNode.EvalArgs evalArgs = callNode.getEvalArgs(); |
jlaskey@3 | 709 | // load evaluated code |
lagergren@605 | 710 | load(evalArgs.getCode(), Type.OBJECT); |
sundar@657 | 711 | // load second and subsequent args for side-effect |
sundar@657 | 712 | final List<Expression> args = callNode.getArgs(); |
sundar@657 | 713 | final int numArgs = args.size(); |
sundar@657 | 714 | for (int i = 1; i < numArgs; i++) { |
sundar@657 | 715 | load(args.get(i)).pop(); |
sundar@657 | 716 | } |
jlaskey@3 | 717 | // special/extra 'eval' arguments |
lagergren@57 | 718 | load(evalArgs.getThis()); |
lagergren@57 | 719 | method.load(evalArgs.getLocation()); |
lagergren@57 | 720 | method.load(evalArgs.getStrictMode()); |
jlaskey@3 | 721 | method.convert(Type.OBJECT); |
jlaskey@3 | 722 | |
jlaskey@3 | 723 | // direct call to Global.directEval |
jlaskey@3 | 724 | globalDirectEval(); |
lagergren@407 | 725 | method.convert(callNodeType); |
jlaskey@3 | 726 | method._goto(eval_done); |
jlaskey@3 | 727 | |
jlaskey@3 | 728 | method.label(not_eval); |
jlaskey@3 | 729 | // This is some scope 'eval' or global eval replaced by user |
jlaskey@3 | 730 | // but not the built-in ECMAScript 'eval' function call |
jlaskey@3 | 731 | method.loadNull(); |
lagergren@407 | 732 | method.dynamicCall(callNodeType, 2 + loadArgs(args), flags); |
jlaskey@3 | 733 | |
jlaskey@3 | 734 | method.label(eval_done); |
jlaskey@3 | 735 | } |
jlaskey@3 | 736 | |
jlaskey@3 | 737 | @Override |
lagergren@211 | 738 | public boolean enterIdentNode(final IdentNode node) { |
jlaskey@3 | 739 | final Symbol symbol = node.getSymbol(); |
jlaskey@3 | 740 | |
jlaskey@3 | 741 | if (symbol.isScope()) { |
jlaskey@3 | 742 | final int flags = getCallSiteFlags() | CALLSITE_SCOPE; |
jlaskey@3 | 743 | final int useCount = symbol.getUseCount(); |
jlaskey@3 | 744 | |
jlaskey@3 | 745 | // Threshold for generating shared scope callsite is lower for fast scope symbols because we know |
attila@217 | 746 | // we can dial in the correct scope. However, we also need to enable it for non-fast scopes to |
jlaskey@3 | 747 | // support huge scripts like mandreel.js. |
jlaskey@3 | 748 | if (callNode.isEval()) { |
jlaskey@3 | 749 | evalCall(node, flags); |
jlaskey@3 | 750 | } else if (useCount <= SharedScopeCall.FAST_SCOPE_CALL_THRESHOLD |
attila@144 | 751 | || (!isFastScope(symbol) && useCount <= SharedScopeCall.SLOW_SCOPE_CALL_THRESHOLD) |
lagergren@290 | 752 | || CodeGenerator.this.lc.inDynamicScope()) { |
jlaskey@3 | 753 | scopeCall(node, flags); |
jlaskey@3 | 754 | } else { |
jlaskey@3 | 755 | sharedScopeCall(node, flags); |
jlaskey@3 | 756 | } |
lagergren@407 | 757 | assert method.peekType().equals(callNodeType) : method.peekType() + "!=" + callNode.getType(); |
jlaskey@3 | 758 | } else { |
jlaskey@3 | 759 | enterDefault(node); |
jlaskey@3 | 760 | } |
jlaskey@3 | 761 | |
lagergren@211 | 762 | return false; |
jlaskey@3 | 763 | } |
jlaskey@3 | 764 | |
jlaskey@3 | 765 | @Override |
lagergren@211 | 766 | public boolean enterAccessNode(final AccessNode node) { |
lagergren@605 | 767 | load(node.getBase(), Type.OBJECT); |
jlaskey@3 | 768 | method.dup(); |
jlaskey@3 | 769 | method.dynamicGet(node.getType(), node.getProperty().getName(), getCallSiteFlags(), true); |
jlaskey@3 | 770 | method.swap(); |
lagergren@407 | 771 | method.dynamicCall(callNodeType, 2 + loadArgs(args), getCallSiteFlags()); |
jlaskey@3 | 772 | |
lagergren@211 | 773 | return false; |
jlaskey@3 | 774 | } |
jlaskey@3 | 775 | |
jlaskey@3 | 776 | @Override |
lagergren@211 | 777 | public boolean enterFunctionNode(final FunctionNode origCallee) { |
lagergren@211 | 778 | // NOTE: visiting the callee will leave a constructed ScriptFunction object on the stack if |
lagergren@211 | 779 | // callee.needsCallee() == true |
lagergren@211 | 780 | final FunctionNode callee = (FunctionNode)origCallee.accept(CodeGenerator.this); |
lagergren@211 | 781 | |
jlaskey@3 | 782 | final boolean isVarArg = callee.isVarArg(); |
jlaskey@3 | 783 | final int argCount = isVarArg ? -1 : callee.getParameters().size(); |
jlaskey@3 | 784 | |
jlaskey@3 | 785 | final String signature = new FunctionSignature(true, callee.needsCallee(), callee.getReturnType(), isVarArg ? null : callee.getParameters()).toString(); |
jlaskey@3 | 786 | |
lagergren@211 | 787 | if (callee.isStrict()) { // self is undefined |
jlaskey@3 | 788 | method.loadUndefined(Type.OBJECT); |
jlaskey@3 | 789 | } else { // get global from scope (which is the self) |
jlaskey@3 | 790 | globalInstance(); |
jlaskey@3 | 791 | } |
jlaskey@3 | 792 | loadArgs(args, signature, isVarArg, argCount); |
lagergren@211 | 793 | assert callee.getCompileUnit() != null : "no compile unit for " + callee.getName() + " " + Debug.id(callee) + " " + callNode; |
lagergren@96 | 794 | method.invokestatic(callee.getCompileUnit().getUnitClassName(), callee.getName(), signature); |
jlaskey@3 | 795 | assert method.peekType().equals(callee.getReturnType()) : method.peekType() + " != " + callee.getReturnType(); |
lagergren@407 | 796 | method.convert(callNodeType); |
lagergren@211 | 797 | return false; |
jlaskey@3 | 798 | } |
jlaskey@3 | 799 | |
jlaskey@3 | 800 | @Override |
lagergren@211 | 801 | public boolean enterIndexNode(final IndexNode node) { |
lagergren@605 | 802 | load(node.getBase(), Type.OBJECT); |
jlaskey@3 | 803 | method.dup(); |
jlaskey@3 | 804 | final Type indexType = node.getIndex().getType(); |
jlaskey@3 | 805 | if (indexType.isObject() || indexType.isBoolean()) { |
lagergren@605 | 806 | load(node.getIndex(), Type.OBJECT); //TODO |
lagergren@605 | 807 | } else { |
lagergren@605 | 808 | load(node.getIndex()); |
jlaskey@3 | 809 | } |
jlaskey@3 | 810 | method.dynamicGetIndex(node.getType(), getCallSiteFlags(), true); |
jlaskey@3 | 811 | method.swap(); |
lagergren@407 | 812 | method.dynamicCall(callNodeType, 2 + loadArgs(args), getCallSiteFlags()); |
jlaskey@3 | 813 | |
lagergren@211 | 814 | return false; |
jlaskey@3 | 815 | } |
jlaskey@3 | 816 | |
jlaskey@3 | 817 | @Override |
lagergren@211 | 818 | protected boolean enterDefault(final Node node) { |
jlaskey@3 | 819 | // Load up function. |
lagergren@605 | 820 | load(function, Type.OBJECT); //TODO, e.g. booleans can be used as functions |
hannesw@769 | 821 | method.loadUndefined(Type.OBJECT); // ScriptFunction will figure out the correct this when it sees CALLSITE_SCOPE |
lagergren@407 | 822 | method.dynamicCall(callNodeType, 2 + loadArgs(args), getCallSiteFlags() | CALLSITE_SCOPE); |
jlaskey@3 | 823 | |
lagergren@211 | 824 | return false; |
jlaskey@3 | 825 | } |
jlaskey@3 | 826 | }); |
jlaskey@3 | 827 | |
jlaskey@3 | 828 | method.store(callNode.getSymbol()); |
jlaskey@3 | 829 | |
lagergren@211 | 830 | return false; |
jlaskey@3 | 831 | } |
jlaskey@3 | 832 | |
jlaskey@3 | 833 | @Override |
lagergren@211 | 834 | public boolean enterContinueNode(final ContinueNode continueNode) { |
lagergren@253 | 835 | lineNumber(continueNode); |
lagergren@253 | 836 | |
lagergren@290 | 837 | final LoopNode continueTo = lc.getContinueTo(continueNode.getLabel()); |
lagergren@290 | 838 | for (int i = 0; i < lc.getScopeNestingLevelTo(continueTo); i++) { |
jlaskey@3 | 839 | closeWith(); |
jlaskey@3 | 840 | } |
lagergren@290 | 841 | method.splitAwareGoto(lc, continueTo.getContinueLabel()); |
lagergren@211 | 842 | |
lagergren@211 | 843 | return false; |
jlaskey@3 | 844 | } |
jlaskey@3 | 845 | |
jlaskey@3 | 846 | @Override |
lagergren@211 | 847 | public boolean enterEmptyNode(final EmptyNode emptyNode) { |
lagergren@253 | 848 | lineNumber(emptyNode); |
lagergren@253 | 849 | |
lagergren@211 | 850 | return false; |
jlaskey@3 | 851 | } |
jlaskey@3 | 852 | |
jlaskey@3 | 853 | @Override |
attila@430 | 854 | public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) { |
attila@430 | 855 | lineNumber(expressionStatement); |
attila@430 | 856 | |
attila@430 | 857 | expressionStatement.getExpression().accept(this); |
attila@430 | 858 | |
attila@430 | 859 | return false; |
attila@430 | 860 | } |
attila@430 | 861 | |
attila@430 | 862 | @Override |
attila@430 | 863 | public boolean enterBlockStatement(final BlockStatement blockStatement) { |
attila@430 | 864 | lineNumber(blockStatement); |
attila@430 | 865 | |
attila@430 | 866 | blockStatement.getBlock().accept(this); |
jlaskey@3 | 867 | |
lagergren@211 | 868 | return false; |
jlaskey@3 | 869 | } |
jlaskey@3 | 870 | |
jlaskey@3 | 871 | @Override |
lagergren@211 | 872 | public boolean enterForNode(final ForNode forNode) { |
lagergren@253 | 873 | lineNumber(forNode); |
lagergren@253 | 874 | |
lagergren@290 | 875 | if (forNode.isForIn()) { |
lagergren@290 | 876 | enterForIn(forNode); |
lagergren@290 | 877 | } else { |
lagergren@290 | 878 | enterFor(forNode); |
lagergren@290 | 879 | } |
lagergren@290 | 880 | |
lagergren@290 | 881 | return false; |
lagergren@290 | 882 | } |
lagergren@290 | 883 | |
lagergren@290 | 884 | private void enterFor(final ForNode forNode) { |
attila@430 | 885 | final Expression init = forNode.getInit(); |
attila@430 | 886 | final Expression test = forNode.getTest(); |
attila@430 | 887 | final Block body = forNode.getBody(); |
attila@430 | 888 | final Expression modify = forNode.getModify(); |
jlaskey@3 | 889 | |
lagergren@290 | 890 | if (init != null) { |
lagergren@290 | 891 | init.accept(this); |
lagergren@290 | 892 | } |
lagergren@290 | 893 | |
lagergren@290 | 894 | final Label loopLabel = new Label("loop"); |
lagergren@290 | 895 | final Label testLabel = new Label("test"); |
lagergren@290 | 896 | |
lagergren@290 | 897 | method._goto(testLabel); |
lagergren@290 | 898 | method.label(loopLabel); |
lagergren@290 | 899 | body.accept(this); |
lagergren@290 | 900 | method.label(forNode.getContinueLabel()); |
lagergren@290 | 901 | |
lagergren@290 | 902 | if (!body.isTerminal() && modify != null) { |
lagergren@290 | 903 | load(modify); |
lagergren@290 | 904 | } |
lagergren@290 | 905 | |
lagergren@290 | 906 | method.label(testLabel); |
lagergren@290 | 907 | if (test != null) { |
lagergren@290 | 908 | new BranchOptimizer(this, method).execute(test, loopLabel, true); |
lagergren@290 | 909 | } else { |
lagergren@290 | 910 | method._goto(loopLabel); |
lagergren@290 | 911 | } |
lagergren@290 | 912 | |
lagergren@290 | 913 | method.label(forNode.getBreakLabel()); |
lagergren@290 | 914 | } |
lagergren@290 | 915 | |
lagergren@290 | 916 | private void enterForIn(final ForNode forNode) { |
lagergren@290 | 917 | final Block body = forNode.getBody(); |
attila@430 | 918 | final Expression modify = forNode.getModify(); |
lagergren@290 | 919 | |
lagergren@290 | 920 | final Symbol iter = forNode.getIterator(); |
lagergren@290 | 921 | final Label loopLabel = new Label("loop"); |
jlaskey@3 | 922 | |
attila@430 | 923 | final Expression init = forNode.getInit(); |
lagergren@290 | 924 | |
lagergren@605 | 925 | load(modify, Type.OBJECT); |
lagergren@290 | 926 | method.invoke(forNode.isForEach() ? ScriptRuntime.TO_VALUE_ITERATOR : ScriptRuntime.TO_PROPERTY_ITERATOR); |
lagergren@290 | 927 | method.store(iter); |
lagergren@290 | 928 | method._goto(forNode.getContinueLabel()); |
lagergren@290 | 929 | method.label(loopLabel); |
lagergren@290 | 930 | |
attila@430 | 931 | new Store<Expression>(init) { |
lagergren@290 | 932 | @Override |
lagergren@290 | 933 | protected void storeNonDiscard() { |
lagergren@290 | 934 | return; |
jlaskey@3 | 935 | } |
lagergren@290 | 936 | @Override |
lagergren@290 | 937 | protected void evaluate() { |
lagergren@290 | 938 | method.load(iter); |
lagergren@290 | 939 | method.invoke(interfaceCallNoLookup(Iterator.class, "next", Object.class)); |
jlaskey@3 | 940 | } |
lagergren@290 | 941 | }.store(); |
lagergren@290 | 942 | |
lagergren@290 | 943 | body.accept(this); |
lagergren@290 | 944 | |
lagergren@290 | 945 | method.label(forNode.getContinueLabel()); |
lagergren@290 | 946 | method.load(iter); |
lagergren@290 | 947 | method.invoke(interfaceCallNoLookup(Iterator.class, "hasNext", boolean.class)); |
lagergren@290 | 948 | method.ifne(loopLabel); |
lagergren@290 | 949 | method.label(forNode.getBreakLabel()); |
jlaskey@3 | 950 | } |
jlaskey@3 | 951 | |
jlaskey@3 | 952 | /** |
jlaskey@3 | 953 | * Initialize the slots in a frame to undefined. |
jlaskey@3 | 954 | * |
jlaskey@3 | 955 | * @param block block with local vars. |
jlaskey@3 | 956 | */ |
lagergren@24 | 957 | private void initLocals(final Block block) { |
lagergren@290 | 958 | lc.nextFreeSlot(block); |
lagergren@290 | 959 | |
lagergren@290 | 960 | final boolean isFunctionBody = lc.isFunctionBody(); |
lagergren@290 | 961 | |
lagergren@290 | 962 | final FunctionNode function = lc.getCurrentFunction(); |
lagergren@211 | 963 | if (isFunctionBody) { |
attila@416 | 964 | if(method.hasScope()) { |
attila@416 | 965 | if (function.needsParentScope()) { |
attila@416 | 966 | method.loadCompilerConstant(CALLEE); |
attila@416 | 967 | method.invoke(ScriptFunction.GET_SCOPE); |
attila@416 | 968 | } else { |
attila@416 | 969 | assert function.hasScopeBlock(); |
attila@416 | 970 | method.loadNull(); |
attila@416 | 971 | } |
attila@416 | 972 | method.storeCompilerConstant(SCOPE); |
attila@62 | 973 | } |
attila@62 | 974 | if (function.needsArguments()) { |
attila@62 | 975 | initArguments(function); |
attila@62 | 976 | } |
attila@62 | 977 | } |
attila@62 | 978 | |
jlaskey@3 | 979 | /* |
jlaskey@3 | 980 | * Determine if block needs scope, if not, just do initSymbols for this block. |
jlaskey@3 | 981 | */ |
jlaskey@3 | 982 | if (block.needsScope()) { |
jlaskey@3 | 983 | /* |
jlaskey@3 | 984 | * Determine if function is varargs and consequently variables have to |
jlaskey@3 | 985 | * be in the scope. |
jlaskey@3 | 986 | */ |
lagergren@108 | 987 | final boolean varsInScope = function.allVarsInScope(); |
jlaskey@3 | 988 | |
jlaskey@3 | 989 | // TODO for LET we can do better: if *block* does not contain any eval/with, we don't need its vars in scope. |
jlaskey@3 | 990 | |
jlaskey@3 | 991 | final List<String> nameList = new ArrayList<>(); |
jlaskey@3 | 992 | final List<Symbol> locals = new ArrayList<>(); |
jlaskey@3 | 993 | |
jlaskey@3 | 994 | // Initalize symbols and values |
jlaskey@3 | 995 | final List<Symbol> newSymbols = new ArrayList<>(); |
jlaskey@3 | 996 | final List<Symbol> values = new ArrayList<>(); |
jlaskey@3 | 997 | |
attila@62 | 998 | final boolean hasArguments = function.needsArguments(); |
lagergren@211 | 999 | |
lagergren@247 | 1000 | for (final Symbol symbol : block.getSymbols()) { |
lagergren@211 | 1001 | |
lagergren@211 | 1002 | if (symbol.isInternal() || symbol.isThis() || symbol.isTemp()) { |
jlaskey@3 | 1003 | continue; |
jlaskey@3 | 1004 | } |
jlaskey@3 | 1005 | |
attila@62 | 1006 | if (symbol.isVar()) { |
lagergren@290 | 1007 | if (varsInScope || symbol.isScope()) { |
attila@62 | 1008 | nameList.add(symbol.getName()); |
attila@62 | 1009 | newSymbols.add(symbol); |
attila@62 | 1010 | values.add(null); |
attila@62 | 1011 | assert symbol.isScope() : "scope for " + symbol + " should have been set in Lower already " + function.getName(); |
attila@62 | 1012 | assert !symbol.hasSlot() : "slot for " + symbol + " should have been removed in Lower already" + function.getName(); |
attila@62 | 1013 | } else { |
attila@62 | 1014 | assert symbol.hasSlot() : symbol + " should have a slot only, no scope"; |
attila@62 | 1015 | locals.add(symbol); |
attila@62 | 1016 | } |
attila@62 | 1017 | } else if (symbol.isParam() && (varsInScope || hasArguments || symbol.isScope())) { |
jlaskey@3 | 1018 | nameList.add(symbol.getName()); |
jlaskey@3 | 1019 | newSymbols.add(symbol); |
attila@62 | 1020 | values.add(hasArguments ? null : symbol); |
attila@62 | 1021 | assert symbol.isScope() : "scope for " + symbol + " should have been set in Lower already " + function.getName() + " varsInScope="+varsInScope+" hasArguments="+hasArguments+" symbol.isScope()=" + symbol.isScope(); |
attila@62 | 1022 | assert !(hasArguments && symbol.hasSlot()) : "slot for " + symbol + " should have been removed in Lower already " + function.getName(); |
jlaskey@3 | 1023 | } |
jlaskey@3 | 1024 | } |
jlaskey@3 | 1025 | |
jlaskey@3 | 1026 | // we may have locals that need to be initialized |
jlaskey@3 | 1027 | initSymbols(locals); |
jlaskey@3 | 1028 | |
jlaskey@3 | 1029 | /* |
jlaskey@3 | 1030 | * Create a new object based on the symbols and values, generate |
jlaskey@3 | 1031 | * bootstrap code for object |
jlaskey@3 | 1032 | */ |
hannesw@415 | 1033 | new FieldObjectCreator<Symbol>(this, nameList, newSymbols, values, true, hasArguments) { |
jlaskey@3 | 1034 | @Override |
jlaskey@3 | 1035 | protected void loadValue(final Symbol value) { |
jlaskey@3 | 1036 | method.load(value); |
jlaskey@3 | 1037 | } |
hannesw@415 | 1038 | }.makeObject(method); |
jlaskey@3 | 1039 | |
jlaskey@3 | 1040 | // runScript(): merge scope into global |
lagergren@211 | 1041 | if (isFunctionBody && function.isProgram()) { |
jlaskey@3 | 1042 | method.invoke(ScriptRuntime.MERGE_SCOPE); |
jlaskey@3 | 1043 | } |
jlaskey@3 | 1044 | |
lagergren@211 | 1045 | method.storeCompilerConstant(SCOPE); |
jlaskey@3 | 1046 | } else { |
attila@62 | 1047 | // Since we don't have a scope, parameters didn't get assigned array indices by the FieldObjectCreator, so |
attila@62 | 1048 | // we need to assign them separately here. |
attila@62 | 1049 | int nextParam = 0; |
lagergren@211 | 1050 | if (isFunctionBody && function.isVarArg()) { |
attila@62 | 1051 | for (final IdentNode param : function.getParameters()) { |
attila@62 | 1052 | param.getSymbol().setFieldIndex(nextParam++); |
attila@62 | 1053 | } |
attila@62 | 1054 | } |
lagergren@211 | 1055 | |
lagergren@247 | 1056 | initSymbols(block.getSymbols()); |
jlaskey@3 | 1057 | } |
jlaskey@3 | 1058 | |
jlaskey@3 | 1059 | // Debugging: print symbols? @see --print-symbols flag |
lagergren@211 | 1060 | printSymbols(block, (isFunctionBody ? "Function " : "Block in ") + (function.getIdent() == null ? "<anonymous>" : function.getIdent().getName())); |
jlaskey@3 | 1061 | } |
jlaskey@3 | 1062 | |
attila@62 | 1063 | private void initArguments(final FunctionNode function) { |
lagergren@211 | 1064 | method.loadCompilerConstant(VARARGS); |
lagergren@290 | 1065 | if (function.needsCallee()) { |
lagergren@211 | 1066 | method.loadCompilerConstant(CALLEE); |
attila@62 | 1067 | } else { |
attila@62 | 1068 | // If function is strict mode, "arguments.callee" is not populated, so we don't necessarily need the |
attila@62 | 1069 | // caller. |
lagergren@211 | 1070 | assert function.isStrict(); |
attila@62 | 1071 | method.loadNull(); |
attila@62 | 1072 | } |
attila@62 | 1073 | method.load(function.getParameters().size()); |
attila@62 | 1074 | globalAllocateArguments(); |
lagergren@211 | 1075 | method.storeCompilerConstant(ARGUMENTS); |
attila@62 | 1076 | } |
attila@62 | 1077 | |
jlaskey@3 | 1078 | @Override |
lagergren@211 | 1079 | public boolean enterFunctionNode(final FunctionNode functionNode) { |
lagergren@211 | 1080 | if (functionNode.isLazy()) { |
lagergren@211 | 1081 | // Must do it now; can't postpone it until leaveFunctionNode() |
lagergren@211 | 1082 | newFunctionObject(functionNode, functionNode); |
lagergren@211 | 1083 | return false; |
attila@144 | 1084 | } |
attila@144 | 1085 | |
attila@435 | 1086 | final String fnName = functionNode.getName(); |
attila@435 | 1087 | // NOTE: we only emit the method for a function with the given name once. We can have multiple functions with |
attila@435 | 1088 | // the same name as a result of inlining finally blocks. However, in the future -- with type specialization, |
attila@435 | 1089 | // notably -- we might need to check for both name *and* signature. Of course, even that might not be |
attila@435 | 1090 | // sufficient; the function might have a code dependency on the type of the variables in its enclosing scopes, |
attila@435 | 1091 | // and the type of such a variable can be different in catch and finally blocks. So, in the future we will have |
attila@435 | 1092 | // to decide to either generate a unique method for each inlined copy of the function, maybe figure out its |
attila@435 | 1093 | // exact type closure and deduplicate based on that, or just decide that functions in finally blocks aren't |
attila@435 | 1094 | // worth it, and generate one method with most generic type closure. |
attila@435 | 1095 | if(!emittedMethods.contains(fnName)) { |
attila@435 | 1096 | LOG.info("=== BEGIN ", fnName); |
attila@435 | 1097 | |
attila@435 | 1098 | assert functionNode.getCompileUnit() != null : "no compile unit for " + fnName + " " + Debug.id(functionNode); |
attila@435 | 1099 | unit = lc.pushCompileUnit(functionNode.getCompileUnit()); |
attila@435 | 1100 | assert lc.hasCompileUnits(); |
attila@435 | 1101 | |
attila@435 | 1102 | method = lc.pushMethodEmitter(unit.getClassEmitter().method(functionNode)); |
attila@435 | 1103 | // new method - reset last line number |
attila@435 | 1104 | lastLineNumber = -1; |
attila@435 | 1105 | // Mark end for variable tables. |
attila@435 | 1106 | method.begin(); |
attila@435 | 1107 | } |
lagergren@211 | 1108 | |
lagergren@211 | 1109 | return true; |
jlaskey@3 | 1110 | } |
jlaskey@3 | 1111 | |
jlaskey@3 | 1112 | @Override |
attila@144 | 1113 | public Node leaveFunctionNode(final FunctionNode functionNode) { |
jlaskey@3 | 1114 | try { |
attila@435 | 1115 | if(emittedMethods.add(functionNode.getName())) { |
attila@435 | 1116 | method.end(); // wrap up this method |
attila@435 | 1117 | unit = lc.popCompileUnit(functionNode.getCompileUnit()); |
attila@435 | 1118 | method = lc.popMethodEmitter(method); |
attila@435 | 1119 | LOG.info("=== END ", functionNode.getName()); |
attila@435 | 1120 | } |
lagergren@211 | 1121 | |
lagergren@290 | 1122 | final FunctionNode newFunctionNode = functionNode.setState(lc, CompilationState.EMITTED); |
lagergren@211 | 1123 | newFunctionObject(newFunctionNode, functionNode); |
lagergren@211 | 1124 | return newFunctionNode; |
jlaskey@3 | 1125 | } catch (final Throwable t) { |
jlaskey@3 | 1126 | Context.printStackTrace(t); |
jlaskey@3 | 1127 | final VerifyError e = new VerifyError("Code generation bug in \"" + functionNode.getName() + "\": likely stack misaligned: " + t + " " + functionNode.getSource().getName()); |
jlaskey@3 | 1128 | e.initCause(t); |
jlaskey@3 | 1129 | throw e; |
jlaskey@3 | 1130 | } |
jlaskey@3 | 1131 | } |
jlaskey@3 | 1132 | |
jlaskey@3 | 1133 | @Override |
lagergren@211 | 1134 | public boolean enterIdentNode(final IdentNode identNode) { |
lagergren@211 | 1135 | return false; |
jlaskey@3 | 1136 | } |
jlaskey@3 | 1137 | |
jlaskey@3 | 1138 | @Override |
lagergren@211 | 1139 | public boolean enterIfNode(final IfNode ifNode) { |
lagergren@253 | 1140 | lineNumber(ifNode); |
lagergren@253 | 1141 | |
attila@430 | 1142 | final Expression test = ifNode.getTest(); |
jlaskey@3 | 1143 | final Block pass = ifNode.getPass(); |
jlaskey@3 | 1144 | final Block fail = ifNode.getFail(); |
jlaskey@3 | 1145 | |
jlaskey@3 | 1146 | final Label failLabel = new Label("if_fail"); |
jlaskey@3 | 1147 | final Label afterLabel = fail == null ? failLabel : new Label("if_done"); |
jlaskey@3 | 1148 | |
jlaskey@3 | 1149 | new BranchOptimizer(this, method).execute(test, failLabel, false); |
jlaskey@3 | 1150 | |
jlaskey@3 | 1151 | boolean passTerminal = false; |
jlaskey@3 | 1152 | boolean failTerminal = false; |
jlaskey@3 | 1153 | |
jlaskey@3 | 1154 | pass.accept(this); |
jlaskey@3 | 1155 | if (!pass.hasTerminalFlags()) { |
jlaskey@3 | 1156 | method._goto(afterLabel); //don't fallthru to fail block |
jlaskey@3 | 1157 | } else { |
jlaskey@3 | 1158 | passTerminal = pass.isTerminal(); |
jlaskey@3 | 1159 | } |
jlaskey@3 | 1160 | |
jlaskey@3 | 1161 | if (fail != null) { |
jlaskey@3 | 1162 | method.label(failLabel); |
jlaskey@3 | 1163 | fail.accept(this); |
jlaskey@3 | 1164 | failTerminal = fail.isTerminal(); |
jlaskey@3 | 1165 | } |
jlaskey@3 | 1166 | |
jlaskey@3 | 1167 | //if if terminates, put the after label there |
jlaskey@3 | 1168 | if (!passTerminal || !failTerminal) { |
jlaskey@3 | 1169 | method.label(afterLabel); |
jlaskey@3 | 1170 | } |
jlaskey@3 | 1171 | |
lagergren@211 | 1172 | return false; |
jlaskey@3 | 1173 | } |
jlaskey@3 | 1174 | |
jlaskey@3 | 1175 | @Override |
lagergren@211 | 1176 | public boolean enterIndexNode(final IndexNode indexNode) { |
jlaskey@3 | 1177 | load(indexNode); |
lagergren@211 | 1178 | return false; |
jlaskey@3 | 1179 | } |
jlaskey@3 | 1180 | |
lagergren@253 | 1181 | private void lineNumber(final Statement statement) { |
attila@430 | 1182 | lineNumber(statement.getLineNumber()); |
attila@430 | 1183 | } |
attila@430 | 1184 | |
attila@430 | 1185 | private void lineNumber(int lineNumber) { |
lagergren@253 | 1186 | if (lineNumber != lastLineNumber) { |
sundar@383 | 1187 | method.lineNumber(lineNumber); |
lagergren@253 | 1188 | } |
lagergren@253 | 1189 | lastLineNumber = lineNumber; |
jlaskey@3 | 1190 | } |
jlaskey@3 | 1191 | |
jlaskey@3 | 1192 | /** |
jlaskey@3 | 1193 | * Load a list of nodes as an array of a specific type |
jlaskey@3 | 1194 | * The array will contain the visited nodes. |
jlaskey@3 | 1195 | * |
jlaskey@3 | 1196 | * @param arrayLiteralNode the array of contents |
jlaskey@3 | 1197 | * @param arrayType the type of the array, e.g. ARRAY_NUMBER or ARRAY_OBJECT |
jlaskey@3 | 1198 | * |
jlaskey@3 | 1199 | * @return the method generator that was used |
jlaskey@3 | 1200 | */ |
jlaskey@3 | 1201 | private MethodEmitter loadArray(final ArrayLiteralNode arrayLiteralNode, final ArrayType arrayType) { |
lagergren@395 | 1202 | assert arrayType == Type.INT_ARRAY || arrayType == Type.LONG_ARRAY || arrayType == Type.NUMBER_ARRAY || arrayType == Type.OBJECT_ARRAY; |
jlaskey@3 | 1203 | |
attila@430 | 1204 | final Expression[] nodes = arrayLiteralNode.getValue(); |
jlaskey@3 | 1205 | final Object presets = arrayLiteralNode.getPresets(); |
jlaskey@3 | 1206 | final int[] postsets = arrayLiteralNode.getPostsets(); |
jlaskey@3 | 1207 | final Class<?> type = arrayType.getTypeClass(); |
jlaskey@3 | 1208 | final List<ArrayUnit> units = arrayLiteralNode.getUnits(); |
jlaskey@3 | 1209 | |
jlaskey@3 | 1210 | loadConstant(presets); |
jlaskey@3 | 1211 | |
jlaskey@3 | 1212 | final Type elementType = arrayType.getElementType(); |
jlaskey@3 | 1213 | |
jlaskey@3 | 1214 | if (units != null) { |
hannesw@455 | 1215 | final MethodEmitter savedMethod = method; |
hannesw@455 | 1216 | final FunctionNode currentFunction = lc.getCurrentFunction(); |
lagergren@211 | 1217 | |
lagergren@211 | 1218 | for (final ArrayUnit arrayUnit : units) { |
lagergren@290 | 1219 | unit = lc.pushCompileUnit(arrayUnit.getCompileUnit()); |
lagergren@211 | 1220 | |
lagergren@211 | 1221 | final String className = unit.getUnitClassName(); |
hannesw@455 | 1222 | final String name = currentFunction.uniqueName(SPLIT_PREFIX.symbolName()); |
hannesw@455 | 1223 | final String signature = methodDescriptor(type, ScriptFunction.class, Object.class, ScriptObject.class, type); |
lagergren@211 | 1224 | |
lagergren@211 | 1225 | final MethodEmitter me = unit.getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature); |
lagergren@290 | 1226 | method = lc.pushMethodEmitter(me); |
lagergren@290 | 1227 | |
hannesw@455 | 1228 | method.setFunctionNode(currentFunction); |
lagergren@211 | 1229 | method.begin(); |
lagergren@211 | 1230 | |
hannesw@455 | 1231 | fixScopeSlot(currentFunction); |
lagergren@211 | 1232 | |
lagergren@211 | 1233 | method.load(arrayType, SPLIT_ARRAY_ARG.slot()); |
lagergren@211 | 1234 | |
lagergren@211 | 1235 | for (int i = arrayUnit.getLo(); i < arrayUnit.getHi(); i++) { |
lagergren@211 | 1236 | storeElement(nodes, elementType, postsets[i]); |
jlaskey@3 | 1237 | } |
lagergren@211 | 1238 | |
lagergren@211 | 1239 | method._return(); |
lagergren@211 | 1240 | method.end(); |
lagergren@290 | 1241 | method = lc.popMethodEmitter(me); |
lagergren@211 | 1242 | |
lagergren@211 | 1243 | assert method == savedMethod; |
hannesw@455 | 1244 | method.loadCompilerConstant(CALLEE); |
hannesw@455 | 1245 | method.swap(); |
lagergren@211 | 1246 | method.loadCompilerConstant(THIS); |
lagergren@211 | 1247 | method.swap(); |
lagergren@211 | 1248 | method.loadCompilerConstant(SCOPE); |
lagergren@211 | 1249 | method.swap(); |
lagergren@211 | 1250 | method.invokestatic(className, name, signature); |
lagergren@211 | 1251 | |
lagergren@290 | 1252 | unit = lc.popCompileUnit(unit); |
jlaskey@3 | 1253 | } |
jlaskey@3 | 1254 | |
jlaskey@3 | 1255 | return method; |
jlaskey@3 | 1256 | } |
jlaskey@3 | 1257 | |
jlaskey@3 | 1258 | for (final int postset : postsets) { |
jlaskey@3 | 1259 | storeElement(nodes, elementType, postset); |
jlaskey@3 | 1260 | } |
jlaskey@3 | 1261 | |
jlaskey@3 | 1262 | return method; |
jlaskey@3 | 1263 | } |
jlaskey@3 | 1264 | |
attila@430 | 1265 | private void storeElement(final Expression[] nodes, final Type elementType, final int index) { |
jlaskey@3 | 1266 | method.dup(); |
jlaskey@3 | 1267 | method.load(index); |
jlaskey@3 | 1268 | |
attila@430 | 1269 | final Expression element = nodes[index]; |
jlaskey@3 | 1270 | |
jlaskey@3 | 1271 | if (element == null) { |
jlaskey@3 | 1272 | method.loadEmpty(elementType); |
jlaskey@3 | 1273 | } else { |
lagergren@605 | 1274 | load(element, elementType); |
jlaskey@3 | 1275 | } |
jlaskey@3 | 1276 | |
jlaskey@3 | 1277 | method.arraystore(); |
jlaskey@3 | 1278 | } |
jlaskey@3 | 1279 | |
attila@430 | 1280 | private MethodEmitter loadArgsArray(final List<Expression> args) { |
jlaskey@3 | 1281 | final Object[] array = new Object[args.size()]; |
jlaskey@3 | 1282 | loadConstant(array); |
jlaskey@3 | 1283 | |
jlaskey@3 | 1284 | for (int i = 0; i < args.size(); i++) { |
jlaskey@3 | 1285 | method.dup(); |
jlaskey@3 | 1286 | method.load(i); |
attila@644 | 1287 | load(args.get(i), Type.OBJECT); //has to be upcast to object or we fail |
jlaskey@3 | 1288 | method.arraystore(); |
jlaskey@3 | 1289 | } |
jlaskey@3 | 1290 | |
jlaskey@3 | 1291 | return method; |
jlaskey@3 | 1292 | } |
jlaskey@3 | 1293 | |
jlaskey@3 | 1294 | /** |
jlaskey@3 | 1295 | * Load a constant from the constant array. This is only public to be callable from the objects |
jlaskey@3 | 1296 | * subpackage. Do not call directly. |
jlaskey@3 | 1297 | * |
jlaskey@3 | 1298 | * @param string string to load |
jlaskey@3 | 1299 | */ |
lagergren@96 | 1300 | void loadConstant(final String string) { |
lagergren@211 | 1301 | final String unitClassName = unit.getUnitClassName(); |
lagergren@211 | 1302 | final ClassEmitter classEmitter = unit.getClassEmitter(); |
jlaskey@3 | 1303 | final int index = compiler.getConstantData().add(string); |
jlaskey@3 | 1304 | |
jlaskey@3 | 1305 | method.load(index); |
lagergren@211 | 1306 | method.invokestatic(unitClassName, GET_STRING.symbolName(), methodDescriptor(String.class, int.class)); |
jlaskey@3 | 1307 | classEmitter.needGetConstantMethod(String.class); |
jlaskey@3 | 1308 | } |
jlaskey@3 | 1309 | |
jlaskey@3 | 1310 | /** |
jlaskey@3 | 1311 | * Load a constant from the constant array. This is only public to be callable from the objects |
jlaskey@3 | 1312 | * subpackage. Do not call directly. |
jlaskey@3 | 1313 | * |
jlaskey@3 | 1314 | * @param object object to load |
jlaskey@3 | 1315 | */ |
lagergren@96 | 1316 | void loadConstant(final Object object) { |
lagergren@211 | 1317 | final String unitClassName = unit.getUnitClassName(); |
lagergren@211 | 1318 | final ClassEmitter classEmitter = unit.getClassEmitter(); |
jlaskey@3 | 1319 | final int index = compiler.getConstantData().add(object); |
jlaskey@3 | 1320 | final Class<?> cls = object.getClass(); |
jlaskey@3 | 1321 | |
jlaskey@3 | 1322 | if (cls == PropertyMap.class) { |
jlaskey@3 | 1323 | method.load(index); |
lagergren@211 | 1324 | method.invokestatic(unitClassName, GET_MAP.symbolName(), methodDescriptor(PropertyMap.class, int.class)); |
jlaskey@3 | 1325 | classEmitter.needGetConstantMethod(PropertyMap.class); |
jlaskey@3 | 1326 | } else if (cls.isArray()) { |
jlaskey@3 | 1327 | method.load(index); |
jlaskey@3 | 1328 | final String methodName = ClassEmitter.getArrayMethodName(cls); |
lagergren@96 | 1329 | method.invokestatic(unitClassName, methodName, methodDescriptor(cls, int.class)); |
jlaskey@3 | 1330 | classEmitter.needGetConstantMethod(cls); |
jlaskey@3 | 1331 | } else { |
attila@144 | 1332 | method.loadConstants().load(index).arrayload(); |
hannesw@482 | 1333 | if (object instanceof ArrayData) { |
hannesw@482 | 1334 | // avoid cast to non-public ArrayData subclass |
hannesw@482 | 1335 | method.checkcast(ArrayData.class); |
hannesw@482 | 1336 | method.invoke(virtualCallNoLookup(ArrayData.class, "copy", ArrayData.class)); |
hannesw@482 | 1337 | } else if (cls != Object.class) { |
jlaskey@3 | 1338 | method.checkcast(cls); |
jlaskey@3 | 1339 | } |
jlaskey@3 | 1340 | } |
jlaskey@3 | 1341 | } |
jlaskey@3 | 1342 | |
jlaskey@3 | 1343 | // literal values |
lagergren@605 | 1344 | private MethodEmitter loadLiteral(final LiteralNode<?> node, final Type type) { |
jlaskey@3 | 1345 | final Object value = node.getValue(); |
jlaskey@3 | 1346 | |
jlaskey@3 | 1347 | if (value == null) { |
jlaskey@3 | 1348 | method.loadNull(); |
jlaskey@3 | 1349 | } else if (value instanceof Undefined) { |
jlaskey@3 | 1350 | method.loadUndefined(Type.OBJECT); |
jlaskey@3 | 1351 | } else if (value instanceof String) { |
jlaskey@3 | 1352 | final String string = (String)value; |
jlaskey@3 | 1353 | |
jlaskey@3 | 1354 | if (string.length() > (MethodEmitter.LARGE_STRING_THRESHOLD / 3)) { // 3 == max bytes per encoded char |
jlaskey@3 | 1355 | loadConstant(string); |
jlaskey@3 | 1356 | } else { |
jlaskey@3 | 1357 | method.load(string); |
jlaskey@3 | 1358 | } |
jlaskey@3 | 1359 | } else if (value instanceof RegexToken) { |
jlaskey@3 | 1360 | loadRegex((RegexToken)value); |
jlaskey@3 | 1361 | } else if (value instanceof Boolean) { |
jlaskey@3 | 1362 | method.load((Boolean)value); |
jlaskey@3 | 1363 | } else if (value instanceof Integer) { |
lagergren@605 | 1364 | if(type.isEquivalentTo(Type.NUMBER)) { |
lagergren@605 | 1365 | method.load(((Integer)value).doubleValue()); |
lagergren@605 | 1366 | } else if(type.isEquivalentTo(Type.LONG)) { |
lagergren@605 | 1367 | method.load(((Integer)value).longValue()); |
lagergren@605 | 1368 | } else { |
lagergren@605 | 1369 | method.load((Integer)value); |
lagergren@605 | 1370 | } |
jlaskey@3 | 1371 | } else if (value instanceof Long) { |
lagergren@605 | 1372 | if(type.isEquivalentTo(Type.NUMBER)) { |
lagergren@605 | 1373 | method.load(((Long)value).doubleValue()); |
lagergren@605 | 1374 | } else { |
lagergren@605 | 1375 | method.load((Long)value); |
lagergren@605 | 1376 | } |
jlaskey@3 | 1377 | } else if (value instanceof Double) { |
jlaskey@3 | 1378 | method.load((Double)value); |
jlaskey@3 | 1379 | } else if (node instanceof ArrayLiteralNode) { |
lagergren@605 | 1380 | final ArrayLiteralNode arrayLiteral = (ArrayLiteralNode)node; |
lagergren@605 | 1381 | final ArrayType atype = arrayLiteral.getArrayType(); |
lagergren@605 | 1382 | loadArray(arrayLiteral, atype); |
lagergren@605 | 1383 | globalAllocateArray(atype); |
jlaskey@3 | 1384 | } else { |
jlaskey@3 | 1385 | assert false : "Unknown literal for " + node.getClass() + " " + value.getClass() + " " + value; |
jlaskey@3 | 1386 | } |
jlaskey@3 | 1387 | |
jlaskey@3 | 1388 | return method; |
jlaskey@3 | 1389 | } |
jlaskey@3 | 1390 | |
jlaskey@3 | 1391 | private MethodEmitter loadRegexToken(final RegexToken value) { |
jlaskey@3 | 1392 | method.load(value.getExpression()); |
jlaskey@3 | 1393 | method.load(value.getOptions()); |
jlaskey@3 | 1394 | return globalNewRegExp(); |
jlaskey@3 | 1395 | } |
jlaskey@3 | 1396 | |
jlaskey@3 | 1397 | private MethodEmitter loadRegex(final RegexToken regexToken) { |
jlaskey@3 | 1398 | if (regexFieldCount > MAX_REGEX_FIELDS) { |
jlaskey@3 | 1399 | return loadRegexToken(regexToken); |
jlaskey@3 | 1400 | } |
jlaskey@3 | 1401 | // emit field |
lagergren@290 | 1402 | final String regexName = lc.getCurrentFunction().uniqueName(REGEX_PREFIX.symbolName()); |
lagergren@211 | 1403 | final ClassEmitter classEmitter = unit.getClassEmitter(); |
jlaskey@3 | 1404 | |
jlaskey@3 | 1405 | classEmitter.field(EnumSet.of(PRIVATE, STATIC), regexName, Object.class); |
jlaskey@3 | 1406 | regexFieldCount++; |
jlaskey@3 | 1407 | |
jlaskey@3 | 1408 | // get field, if null create new regex, finally clone regex object |
lagergren@211 | 1409 | method.getStatic(unit.getUnitClassName(), regexName, typeDescriptor(Object.class)); |
jlaskey@3 | 1410 | method.dup(); |
jlaskey@3 | 1411 | final Label cachedLabel = new Label("cached"); |
jlaskey@3 | 1412 | method.ifnonnull(cachedLabel); |
jlaskey@3 | 1413 | |
jlaskey@3 | 1414 | method.pop(); |
jlaskey@3 | 1415 | loadRegexToken(regexToken); |
jlaskey@3 | 1416 | method.dup(); |
lagergren@211 | 1417 | method.putStatic(unit.getUnitClassName(), regexName, typeDescriptor(Object.class)); |
jlaskey@3 | 1418 | |
jlaskey@3 | 1419 | method.label(cachedLabel); |
jlaskey@3 | 1420 | globalRegExpCopy(); |
jlaskey@3 | 1421 | |
jlaskey@3 | 1422 | return method; |
jlaskey@3 | 1423 | } |
jlaskey@3 | 1424 | |
jlaskey@3 | 1425 | @Override |
sundar@428 | 1426 | public boolean enterLiteralNode(final LiteralNode<?> literalNode) { |
lagergren@605 | 1427 | return enterLiteralNode(literalNode, literalNode.getType()); |
lagergren@605 | 1428 | } |
lagergren@605 | 1429 | |
lagergren@605 | 1430 | private boolean enterLiteralNode(final LiteralNode<?> literalNode, final Type type) { |
lagergren@57 | 1431 | assert literalNode.getSymbol() != null : literalNode + " has no symbol"; |
lagergren@605 | 1432 | loadLiteral(literalNode, type).convert(type).store(literalNode.getSymbol()); |
lagergren@211 | 1433 | return false; |
jlaskey@3 | 1434 | } |
jlaskey@3 | 1435 | |
jlaskey@3 | 1436 | @Override |
lagergren@211 | 1437 | public boolean enterObjectNode(final ObjectNode objectNode) { |
attila@324 | 1438 | final List<PropertyNode> elements = objectNode.getElements(); |
jlaskey@3 | 1439 | |
attila@430 | 1440 | final List<String> keys = new ArrayList<>(); |
attila@430 | 1441 | final List<Symbol> symbols = new ArrayList<>(); |
attila@430 | 1442 | final List<Expression> values = new ArrayList<>(); |
jlaskey@3 | 1443 | |
jlaskey@3 | 1444 | boolean hasGettersSetters = false; |
sundar@538 | 1445 | Expression protoNode = null; |
jlaskey@3 | 1446 | |
attila@324 | 1447 | for (PropertyNode propertyNode: elements) { |
attila@430 | 1448 | final Expression value = propertyNode.getValue(); |
jlaskey@3 | 1449 | final String key = propertyNode.getKeyName(); |
attila@430 | 1450 | final Symbol symbol = value == null ? null : propertyNode.getKey().getSymbol(); |
jlaskey@3 | 1451 | |
jlaskey@3 | 1452 | if (value == null) { |
jlaskey@3 | 1453 | hasGettersSetters = true; |
sundar@847 | 1454 | } else if (propertyNode.getKey() instanceof IdentNode && |
sundar@847 | 1455 | key.equals(ScriptObject.PROTO_PROPERTY_NAME)) { |
sundar@847 | 1456 | // ES6 draft compliant __proto__ inside object literal |
sundar@847 | 1457 | // Identifier key and name is __proto__ |
sundar@538 | 1458 | protoNode = value; |
sundar@538 | 1459 | continue; |
jlaskey@3 | 1460 | } |
jlaskey@3 | 1461 | |
jlaskey@3 | 1462 | keys.add(key); |
jlaskey@3 | 1463 | symbols.add(symbol); |
jlaskey@3 | 1464 | values.add(value); |
jlaskey@3 | 1465 | } |
jlaskey@3 | 1466 | |
hannesw@415 | 1467 | if (elements.size() > OBJECT_SPILL_THRESHOLD) { |
hannesw@415 | 1468 | new SpillObjectCreator(this, keys, symbols, values).makeObject(method); |
hannesw@415 | 1469 | } else { |
attila@430 | 1470 | new FieldObjectCreator<Expression>(this, keys, symbols, values) { |
hannesw@415 | 1471 | @Override |
attila@430 | 1472 | protected void loadValue(final Expression node) { |
hannesw@415 | 1473 | load(node); |
hannesw@415 | 1474 | } |
hannesw@415 | 1475 | |
hannesw@415 | 1476 | /** |
hannesw@415 | 1477 | * Ensure that the properties start out as object types so that |
hannesw@415 | 1478 | * we can do putfield initializations instead of dynamicSetIndex |
hannesw@415 | 1479 | * which would be the case to determine initial property type |
hannesw@415 | 1480 | * otherwise. |
hannesw@415 | 1481 | * |
hannesw@415 | 1482 | * Use case, it's very expensive to do a million var x = {a:obj, b:obj} |
hannesw@415 | 1483 | * just to have to invalidate them immediately on initialization |
hannesw@415 | 1484 | * |
hannesw@415 | 1485 | * see NASHORN-594 |
hannesw@415 | 1486 | */ |
hannesw@415 | 1487 | @Override |
hannesw@415 | 1488 | protected MapCreator newMapCreator(final Class<?> fieldObjectClass) { |
hannesw@415 | 1489 | return new MapCreator(fieldObjectClass, keys, symbols) { |
hannesw@415 | 1490 | @Override |
hannesw@415 | 1491 | protected int getPropertyFlags(final Symbol symbol, final boolean hasArguments) { |
hannesw@415 | 1492 | return super.getPropertyFlags(symbol, hasArguments) | Property.IS_ALWAYS_OBJECT; |
hannesw@415 | 1493 | } |
hannesw@415 | 1494 | }; |
hannesw@415 | 1495 | } |
hannesw@415 | 1496 | |
hannesw@415 | 1497 | }.makeObject(method); |
hannesw@415 | 1498 | } |
jlaskey@3 | 1499 | |
jlaskey@3 | 1500 | method.dup(); |
sundar@538 | 1501 | if (protoNode != null) { |
sundar@538 | 1502 | load(protoNode); |
sundar@538 | 1503 | method.invoke(ScriptObject.SET_PROTO_CHECK); |
sundar@538 | 1504 | } else { |
sundar@538 | 1505 | globalObjectPrototype(); |
sundar@538 | 1506 | method.invoke(ScriptObject.SET_PROTO); |
sundar@538 | 1507 | } |
jlaskey@3 | 1508 | |
hannesw@415 | 1509 | if (hasGettersSetters) { |
hannesw@415 | 1510 | for (final PropertyNode propertyNode : elements) { |
hannesw@415 | 1511 | final FunctionNode getter = propertyNode.getGetter(); |
hannesw@415 | 1512 | final FunctionNode setter = propertyNode.getSetter(); |
hannesw@415 | 1513 | |
hannesw@415 | 1514 | if (getter == null && setter == null) { |
hannesw@415 | 1515 | continue; |
hannesw@415 | 1516 | } |
hannesw@415 | 1517 | |
hannesw@415 | 1518 | method.dup().loadKey(propertyNode.getKey()); |
hannesw@415 | 1519 | |
hannesw@415 | 1520 | if (getter == null) { |
hannesw@415 | 1521 | method.loadNull(); |
hannesw@415 | 1522 | } else { |
hannesw@415 | 1523 | getter.accept(this); |
hannesw@415 | 1524 | } |
hannesw@415 | 1525 | |
hannesw@415 | 1526 | if (setter == null) { |
hannesw@415 | 1527 | method.loadNull(); |
hannesw@415 | 1528 | } else { |
hannesw@415 | 1529 | setter.accept(this); |
hannesw@415 | 1530 | } |
hannesw@415 | 1531 | |
hannesw@415 | 1532 | method.invoke(ScriptObject.SET_USER_ACCESSORS); |
hannesw@415 | 1533 | } |
jlaskey@3 | 1534 | } |
jlaskey@3 | 1535 | |
jlaskey@3 | 1536 | method.store(objectNode.getSymbol()); |
lagergren@211 | 1537 | return false; |
jlaskey@3 | 1538 | } |
jlaskey@3 | 1539 | |
jlaskey@3 | 1540 | @Override |
lagergren@211 | 1541 | public boolean enterReturnNode(final ReturnNode returnNode) { |
lagergren@253 | 1542 | lineNumber(returnNode); |
lagergren@253 | 1543 | |
lagergren@211 | 1544 | method.registerReturn(); |
lagergren@211 | 1545 | |
lagergren@290 | 1546 | final Type returnType = lc.getCurrentFunction().getReturnType(); |
jlaskey@3 | 1547 | |
attila@430 | 1548 | final Expression expression = returnNode.getExpression(); |
jlaskey@3 | 1549 | if (expression != null) { |
jlaskey@3 | 1550 | load(expression); |
jlaskey@3 | 1551 | } else { |
lagergren@211 | 1552 | method.loadUndefined(returnType); |
jlaskey@3 | 1553 | } |
jlaskey@3 | 1554 | |
lagergren@211 | 1555 | method._return(returnType); |
lagergren@211 | 1556 | |
lagergren@211 | 1557 | return false; |
jlaskey@3 | 1558 | } |
jlaskey@3 | 1559 | |
jlaskey@3 | 1560 | private static boolean isNullLiteral(final Node node) { |
jlaskey@3 | 1561 | return node instanceof LiteralNode<?> && ((LiteralNode<?>) node).isNull(); |
jlaskey@3 | 1562 | } |
jlaskey@3 | 1563 | |
attila@430 | 1564 | private boolean nullCheck(final RuntimeNode runtimeNode, final List<Expression> args, final String signature) { |
jlaskey@3 | 1565 | final Request request = runtimeNode.getRequest(); |
jlaskey@3 | 1566 | |
jlaskey@3 | 1567 | if (!Request.isEQ(request) && !Request.isNE(request)) { |
jlaskey@3 | 1568 | return false; |
jlaskey@3 | 1569 | } |
jlaskey@3 | 1570 | |
jlaskey@3 | 1571 | assert args.size() == 2 : "EQ or NE or TYPEOF need two args"; |
jlaskey@3 | 1572 | |
attila@430 | 1573 | Expression lhs = args.get(0); |
attila@430 | 1574 | Expression rhs = args.get(1); |
jlaskey@3 | 1575 | |
jlaskey@3 | 1576 | if (isNullLiteral(lhs)) { |
attila@430 | 1577 | final Expression tmp = lhs; |
jlaskey@3 | 1578 | lhs = rhs; |
jlaskey@3 | 1579 | rhs = tmp; |
jlaskey@3 | 1580 | } |
jlaskey@3 | 1581 | |
lagergren@393 | 1582 | // this is a null literal check, so if there is implicit coercion |
lagergren@393 | 1583 | // involved like {D}x=null, we will fail - this is very rare |
lagergren@393 | 1584 | if (isNullLiteral(rhs) && lhs.getType().isObject()) { |
jlaskey@3 | 1585 | final Label trueLabel = new Label("trueLabel"); |
jlaskey@3 | 1586 | final Label falseLabel = new Label("falseLabel"); |
jlaskey@3 | 1587 | final Label endLabel = new Label("end"); |
jlaskey@3 | 1588 | |
jlaskey@3 | 1589 | load(lhs); |
jlaskey@3 | 1590 | method.dup(); |
jlaskey@3 | 1591 | if (Request.isEQ(request)) { |
jlaskey@3 | 1592 | method.ifnull(trueLabel); |
jlaskey@3 | 1593 | } else if (Request.isNE(request)) { |
jlaskey@3 | 1594 | method.ifnonnull(trueLabel); |
jlaskey@3 | 1595 | } else { |
jlaskey@3 | 1596 | assert false : "Invalid request " + request; |
jlaskey@3 | 1597 | } |
jlaskey@3 | 1598 | |
jlaskey@3 | 1599 | method.label(falseLabel); |
jlaskey@3 | 1600 | load(rhs); |
lagergren@96 | 1601 | method.invokestatic(CompilerConstants.className(ScriptRuntime.class), request.toString(), signature); |
jlaskey@3 | 1602 | method._goto(endLabel); |
jlaskey@3 | 1603 | |
jlaskey@3 | 1604 | method.label(trueLabel); |
jlaskey@3 | 1605 | // if NE (not strict) this can be "undefined != null" which is supposed to be false |
jlaskey@3 | 1606 | if (request == Request.NE) { |
jlaskey@3 | 1607 | method.loadUndefined(Type.OBJECT); |
jlaskey@3 | 1608 | final Label isUndefined = new Label("isUndefined"); |
jlaskey@3 | 1609 | final Label afterUndefinedCheck = new Label("afterUndefinedCheck"); |
jlaskey@3 | 1610 | method.if_acmpeq(isUndefined); |
jlaskey@3 | 1611 | // not undefined |
jlaskey@3 | 1612 | method.load(true); |
jlaskey@3 | 1613 | method._goto(afterUndefinedCheck); |
jlaskey@3 | 1614 | method.label(isUndefined); |
jlaskey@3 | 1615 | method.load(false); |
jlaskey@3 | 1616 | method.label(afterUndefinedCheck); |
jlaskey@3 | 1617 | } else { |
jlaskey@3 | 1618 | method.pop(); |
jlaskey@3 | 1619 | method.load(true); |
jlaskey@3 | 1620 | } |
jlaskey@3 | 1621 | method.label(endLabel); |
jlaskey@3 | 1622 | method.convert(runtimeNode.getType()); |
jlaskey@3 | 1623 | method.store(runtimeNode.getSymbol()); |
jlaskey@3 | 1624 | |
jlaskey@3 | 1625 | return true; |
jlaskey@3 | 1626 | } |
jlaskey@3 | 1627 | |
jlaskey@3 | 1628 | return false; |
jlaskey@3 | 1629 | } |
jlaskey@3 | 1630 | |
attila@430 | 1631 | private boolean specializationCheck(final RuntimeNode.Request request, final Expression node, final List<Expression> args) { |
jlaskey@3 | 1632 | if (!request.canSpecialize()) { |
jlaskey@3 | 1633 | return false; |
jlaskey@3 | 1634 | } |
jlaskey@3 | 1635 | |
jlaskey@3 | 1636 | assert args.size() == 2; |
jlaskey@3 | 1637 | final Type returnType = node.getType(); |
lagergren@211 | 1638 | |
lagergren@211 | 1639 | load(args.get(0)); |
lagergren@211 | 1640 | load(args.get(1)); |
jlaskey@3 | 1641 | |
jlaskey@3 | 1642 | Request finalRequest = request; |
jlaskey@3 | 1643 | |
lagergren@211 | 1644 | //if the request is a comparison, i.e. one that can be reversed |
lagergren@211 | 1645 | //it keeps its semantic, but make sure that the object comes in |
lagergren@211 | 1646 | //last |
jlaskey@3 | 1647 | final Request reverse = Request.reverse(request); |
lagergren@211 | 1648 | if (method.peekType().isObject() && reverse != null) { //rhs is object |
lagergren@211 | 1649 | if (!method.peekType(1).isObject()) { //lhs is not object |
lagergren@211 | 1650 | method.swap(); //prefer object as lhs |
jlaskey@3 | 1651 | finalRequest = reverse; |
jlaskey@3 | 1652 | } |
jlaskey@3 | 1653 | } |
jlaskey@3 | 1654 | |
jlaskey@3 | 1655 | method.dynamicRuntimeCall( |
jlaskey@3 | 1656 | new SpecializedRuntimeNode( |
jlaskey@3 | 1657 | finalRequest, |
jlaskey@3 | 1658 | new Type[] { |
jlaskey@3 | 1659 | method.peekType(1), |
jlaskey@3 | 1660 | method.peekType() |
jlaskey@3 | 1661 | }, |
jlaskey@3 | 1662 | returnType).getInitialName(), |
jlaskey@3 | 1663 | returnType, |
jlaskey@3 | 1664 | finalRequest); |
jlaskey@3 | 1665 | |
jlaskey@3 | 1666 | method.convert(node.getType()); |
jlaskey@3 | 1667 | method.store(node.getSymbol()); |
jlaskey@3 | 1668 | |
jlaskey@3 | 1669 | return true; |
jlaskey@3 | 1670 | } |
jlaskey@3 | 1671 | |
lagergren@89 | 1672 | private static boolean isReducible(final Request request) { |
lagergren@89 | 1673 | return Request.isComparison(request) || request == Request.ADD; |
lagergren@89 | 1674 | } |
lagergren@89 | 1675 | |
jlaskey@3 | 1676 | @Override |
lagergren@211 | 1677 | public boolean enterRuntimeNode(final RuntimeNode runtimeNode) { |
jlaskey@3 | 1678 | /* |
jlaskey@3 | 1679 | * First check if this should be something other than a runtime node |
jlaskey@3 | 1680 | * AccessSpecializer might have changed the type |
lagergren@57 | 1681 | * |
lagergren@57 | 1682 | * TODO - remove this - Access Specializer will always know after Attr/Lower |
jlaskey@3 | 1683 | */ |
attila@430 | 1684 | final List<Expression> args = runtimeNode.getArgs(); |
lagergren@89 | 1685 | if (runtimeNode.isPrimitive() && !runtimeNode.isFinal() && isReducible(runtimeNode.getRequest())) { |
attila@430 | 1686 | final Expression lhs = args.get(0); |
attila@430 | 1687 | assert args.size() > 1 : runtimeNode + " must have two args"; |
attila@430 | 1688 | final Expression rhs = args.get(1); |
jlaskey@3 | 1689 | |
jlaskey@3 | 1690 | final Type type = runtimeNode.getType(); |
jlaskey@3 | 1691 | final Symbol symbol = runtimeNode.getSymbol(); |
jlaskey@3 | 1692 | |
jlaskey@3 | 1693 | switch (runtimeNode.getRequest()) { |
jlaskey@3 | 1694 | case EQ: |
jlaskey@3 | 1695 | case EQ_STRICT: |
jlaskey@3 | 1696 | return enterCmp(lhs, rhs, Condition.EQ, type, symbol); |
jlaskey@3 | 1697 | case NE: |
jlaskey@3 | 1698 | case NE_STRICT: |
jlaskey@3 | 1699 | return enterCmp(lhs, rhs, Condition.NE, type, symbol); |
jlaskey@3 | 1700 | case LE: |
jlaskey@3 | 1701 | return enterCmp(lhs, rhs, Condition.LE, type, symbol); |
jlaskey@3 | 1702 | case LT: |
jlaskey@3 | 1703 | return enterCmp(lhs, rhs, Condition.LT, type, symbol); |
jlaskey@3 | 1704 | case GE: |
jlaskey@3 | 1705 | return enterCmp(lhs, rhs, Condition.GE, type, symbol); |
jlaskey@3 | 1706 | case GT: |
jlaskey@3 | 1707 | return enterCmp(lhs, rhs, Condition.GT, type, symbol); |
jlaskey@3 | 1708 | case ADD: |
lagergren@57 | 1709 | Type widest = Type.widest(lhs.getType(), rhs.getType()); |
lagergren@605 | 1710 | load(lhs, widest); |
lagergren@605 | 1711 | load(rhs, widest); |
lagergren@57 | 1712 | method.add(); |
lagergren@57 | 1713 | method.convert(type); |
lagergren@57 | 1714 | method.store(symbol); |
lagergren@211 | 1715 | return false; |
jlaskey@3 | 1716 | default: |
jlaskey@3 | 1717 | // it's ok to send this one on with only primitive arguments, maybe INSTANCEOF(true, true) or similar |
jlaskey@3 | 1718 | // assert false : runtimeNode + " has all primitive arguments. This is an inconsistent state"; |
jlaskey@3 | 1719 | break; |
jlaskey@3 | 1720 | } |
jlaskey@3 | 1721 | } |
jlaskey@3 | 1722 | |
lagergren@66 | 1723 | if (nullCheck(runtimeNode, args, new FunctionSignature(false, false, runtimeNode.getType(), args).toString())) { |
lagergren@605 | 1724 | return false; |
jlaskey@3 | 1725 | } |
jlaskey@3 | 1726 | |
lagergren@57 | 1727 | if (!runtimeNode.isFinal() && specializationCheck(runtimeNode.getRequest(), runtimeNode, args)) { |
lagergren@605 | 1728 | return false; |
jlaskey@3 | 1729 | } |
jlaskey@3 | 1730 | |
attila@430 | 1731 | for (final Expression arg : args) { |
attila@644 | 1732 | load(arg, Type.OBJECT); |
jlaskey@3 | 1733 | } |
jlaskey@3 | 1734 | |
lagergren@96 | 1735 | method.invokestatic( |
jlaskey@3 | 1736 | CompilerConstants.className(ScriptRuntime.class), |
jlaskey@3 | 1737 | runtimeNode.getRequest().toString(), |
jlaskey@3 | 1738 | new FunctionSignature( |
jlaskey@3 | 1739 | false, |
lagergren@66 | 1740 | false, |
jlaskey@3 | 1741 | runtimeNode.getType(), |
attila@430 | 1742 | args.size()).toString()); |
jlaskey@3 | 1743 | method.convert(runtimeNode.getType()); |
jlaskey@3 | 1744 | method.store(runtimeNode.getSymbol()); |
jlaskey@3 | 1745 | |
lagergren@211 | 1746 | return false; |
jlaskey@3 | 1747 | } |
jlaskey@3 | 1748 | |
jlaskey@3 | 1749 | @Override |
lagergren@211 | 1750 | public boolean enterSplitNode(final SplitNode splitNode) { |
jlaskey@3 | 1751 | final CompileUnit splitCompileUnit = splitNode.getCompileUnit(); |
jlaskey@3 | 1752 | |
lagergren@290 | 1753 | final FunctionNode fn = lc.getCurrentFunction(); |
jlaskey@3 | 1754 | final String className = splitCompileUnit.getUnitClassName(); |
jlaskey@3 | 1755 | final String name = splitNode.getName(); |
jlaskey@3 | 1756 | |
lagergren@211 | 1757 | final Class<?> rtype = fn.getReturnType().getTypeClass(); |
lagergren@211 | 1758 | final boolean needsArguments = fn.needsArguments(); |
lagergren@211 | 1759 | final Class<?>[] ptypes = needsArguments ? |
attila@81 | 1760 | new Class<?>[] {ScriptFunction.class, Object.class, ScriptObject.class, Object.class} : |
attila@81 | 1761 | new Class<?>[] {ScriptFunction.class, Object.class, ScriptObject.class}; |
jlaskey@3 | 1762 | |
lagergren@211 | 1763 | final MethodEmitter caller = method; |
lagergren@290 | 1764 | unit = lc.pushCompileUnit(splitCompileUnit); |
jlaskey@3 | 1765 | |
jlaskey@3 | 1766 | final Call splitCall = staticCallNoLookup( |
jlaskey@3 | 1767 | className, |
jlaskey@3 | 1768 | name, |
jlaskey@3 | 1769 | methodDescriptor(rtype, ptypes)); |
jlaskey@3 | 1770 | |
lagergren@211 | 1771 | final MethodEmitter splitEmitter = |
lagergren@211 | 1772 | splitCompileUnit.getClassEmitter().method( |
lagergren@211 | 1773 | splitNode, |
lagergren@211 | 1774 | name, |
lagergren@211 | 1775 | rtype, |
lagergren@211 | 1776 | ptypes); |
lagergren@211 | 1777 | |
lagergren@290 | 1778 | method = lc.pushMethodEmitter(splitEmitter); |
jlaskey@3 | 1779 | method.setFunctionNode(fn); |
lagergren@211 | 1780 | |
hannesw@455 | 1781 | assert fn.needsCallee() : "split function should require callee"; |
hannesw@455 | 1782 | caller.loadCompilerConstant(CALLEE); |
lagergren@211 | 1783 | caller.loadCompilerConstant(THIS); |
lagergren@211 | 1784 | caller.loadCompilerConstant(SCOPE); |
attila@62 | 1785 | if (needsArguments) { |
lagergren@211 | 1786 | caller.loadCompilerConstant(ARGUMENTS); |
jlaskey@3 | 1787 | } |
jlaskey@3 | 1788 | caller.invoke(splitCall); |
lagergren@211 | 1789 | caller.storeCompilerConstant(RETURN); |
jlaskey@3 | 1790 | |
jlaskey@3 | 1791 | method.begin(); |
hannesw@455 | 1792 | // Copy scope to its target slot as first thing because the original slot could be used by return symbol. |
hannesw@455 | 1793 | fixScopeSlot(fn); |
jlaskey@3 | 1794 | |
jlaskey@3 | 1795 | method.loadUndefined(fn.getReturnType()); |
lagergren@211 | 1796 | method.storeCompilerConstant(RETURN); |
jlaskey@3 | 1797 | |
lagergren@211 | 1798 | return true; |
jlaskey@3 | 1799 | } |
jlaskey@3 | 1800 | |
hannesw@455 | 1801 | private void fixScopeSlot(final FunctionNode functionNode) { |
hannesw@455 | 1802 | // TODO hack to move the scope to the expected slot (needed because split methods reuse the same slots as the root method) |
hannesw@455 | 1803 | if (functionNode.compilerConstant(SCOPE).getSlot() != SCOPE.slot()) { |
jlaskey@3 | 1804 | method.load(Type.typeFor(ScriptObject.class), SCOPE.slot()); |
lagergren@211 | 1805 | method.storeCompilerConstant(SCOPE); |
jlaskey@3 | 1806 | } |
jlaskey@3 | 1807 | } |
jlaskey@3 | 1808 | |
jlaskey@3 | 1809 | @Override |
attila@144 | 1810 | public Node leaveSplitNode(final SplitNode splitNode) { |
lagergren@211 | 1811 | assert method instanceof SplitMethodEmitter; |
lagergren@211 | 1812 | final boolean hasReturn = method.hasReturn(); |
lagergren@211 | 1813 | final List<Label> targets = method.getExternalTargets(); |
lagergren@211 | 1814 | |
jlaskey@3 | 1815 | try { |
jlaskey@3 | 1816 | // Wrap up this method. |
lagergren@211 | 1817 | |
lagergren@211 | 1818 | method.loadCompilerConstant(RETURN); |
lagergren@290 | 1819 | method._return(lc.getCurrentFunction().getReturnType()); |
jlaskey@3 | 1820 | method.end(); |
lagergren@211 | 1821 | |
lagergren@290 | 1822 | unit = lc.popCompileUnit(splitNode.getCompileUnit()); |
lagergren@290 | 1823 | method = lc.popMethodEmitter(method); |
lagergren@211 | 1824 | |
jlaskey@3 | 1825 | } catch (final Throwable t) { |
jlaskey@3 | 1826 | Context.printStackTrace(t); |
lagergren@290 | 1827 | final VerifyError e = new VerifyError("Code generation bug in \"" + splitNode.getName() + "\": likely stack misaligned: " + t + " " + lc.getCurrentFunction().getSource().getName()); |
jlaskey@3 | 1828 | e.initCause(t); |
jlaskey@3 | 1829 | throw e; |
jlaskey@3 | 1830 | } |
jlaskey@3 | 1831 | |
jlaskey@3 | 1832 | // Handle return from split method if there was one. |
lagergren@211 | 1833 | final MethodEmitter caller = method; |
lagergren@211 | 1834 | final int targetCount = targets.size(); |
lagergren@211 | 1835 | |
lagergren@211 | 1836 | //no external jump targets or return in switch node |
lagergren@211 | 1837 | if (!hasReturn && targets.isEmpty()) { |
lagergren@211 | 1838 | return splitNode; |
lagergren@211 | 1839 | } |
lagergren@211 | 1840 | |
lagergren@211 | 1841 | caller.loadCompilerConstant(SCOPE); |
lagergren@211 | 1842 | caller.checkcast(Scope.class); |
lagergren@211 | 1843 | caller.invoke(Scope.GET_SPLIT_STATE); |
lagergren@211 | 1844 | |
lagergren@211 | 1845 | final Label breakLabel = new Label("no_split_state"); |
lagergren@211 | 1846 | // Split state is -1 for no split state, 0 for return, 1..n+1 for break/continue |
lagergren@211 | 1847 | |
lagergren@211 | 1848 | //the common case is that we don't need a switch |
lagergren@211 | 1849 | if (targetCount == 0) { |
lagergren@211 | 1850 | assert hasReturn; |
lagergren@211 | 1851 | caller.ifne(breakLabel); |
lagergren@211 | 1852 | //has to be zero |
lagergren@211 | 1853 | caller.label(new Label("split_return")); |
hannesw@466 | 1854 | caller.loadCompilerConstant(RETURN); |
lagergren@290 | 1855 | caller._return(lc.getCurrentFunction().getReturnType()); |
lagergren@211 | 1856 | caller.label(breakLabel); |
lagergren@211 | 1857 | } else { |
lagergren@211 | 1858 | assert !targets.isEmpty(); |
lagergren@211 | 1859 | |
lagergren@211 | 1860 | final int low = hasReturn ? 0 : 1; |
lagergren@211 | 1861 | final int labelCount = targetCount + 1 - low; |
lagergren@211 | 1862 | final Label[] labels = new Label[labelCount]; |
jlaskey@3 | 1863 | |
jlaskey@3 | 1864 | for (int i = 0; i < labelCount; i++) { |
lagergren@211 | 1865 | labels[i] = new Label(i == 0 ? "split_return" : "split_" + targets.get(i - 1)); |
jlaskey@3 | 1866 | } |
lagergren@96 | 1867 | caller.tableswitch(low, targetCount, breakLabel, labels); |
jlaskey@3 | 1868 | for (int i = low; i <= targetCount; i++) { |
jlaskey@3 | 1869 | caller.label(labels[i - low]); |
jlaskey@3 | 1870 | if (i == 0) { |
lagergren@211 | 1871 | caller.loadCompilerConstant(RETURN); |
lagergren@290 | 1872 | caller._return(lc.getCurrentFunction().getReturnType()); |
jlaskey@3 | 1873 | } else { |
jlaskey@3 | 1874 | // Clear split state. |
lagergren@211 | 1875 | caller.loadCompilerConstant(SCOPE); |
jlaskey@3 | 1876 | caller.checkcast(Scope.class); |
jlaskey@3 | 1877 | caller.load(-1); |
jlaskey@3 | 1878 | caller.invoke(Scope.SET_SPLIT_STATE); |
lagergren@290 | 1879 | caller.splitAwareGoto(lc, targets.get(i - 1)); |
jlaskey@3 | 1880 | } |
jlaskey@3 | 1881 | } |
jlaskey@3 | 1882 | caller.label(breakLabel); |
jlaskey@3 | 1883 | } |
jlaskey@3 | 1884 | |
hannesw@466 | 1885 | // If split has a return and caller is itself a split method it needs to propagate the return. |
hannesw@466 | 1886 | if (hasReturn) { |
hannesw@466 | 1887 | caller.setHasReturn(); |
hannesw@466 | 1888 | } |
hannesw@466 | 1889 | |
jlaskey@3 | 1890 | return splitNode; |
jlaskey@3 | 1891 | } |
jlaskey@3 | 1892 | |
jlaskey@3 | 1893 | @Override |
lagergren@211 | 1894 | public boolean enterSwitchNode(final SwitchNode switchNode) { |
lagergren@253 | 1895 | lineNumber(switchNode); |
lagergren@253 | 1896 | |
attila@430 | 1897 | final Expression expression = switchNode.getExpression(); |
jlaskey@3 | 1898 | final Symbol tag = switchNode.getTag(); |
jlaskey@3 | 1899 | final boolean allInteger = tag.getSymbolType().isInteger(); |
jlaskey@3 | 1900 | final List<CaseNode> cases = switchNode.getCases(); |
jlaskey@3 | 1901 | final CaseNode defaultCase = switchNode.getDefaultCase(); |
jlaskey@3 | 1902 | final Label breakLabel = switchNode.getBreakLabel(); |
jlaskey@3 | 1903 | |
jlaskey@3 | 1904 | Label defaultLabel = breakLabel; |
jlaskey@3 | 1905 | boolean hasDefault = false; |
jlaskey@3 | 1906 | |
jlaskey@3 | 1907 | if (defaultCase != null) { |
jlaskey@3 | 1908 | defaultLabel = defaultCase.getEntry(); |
jlaskey@3 | 1909 | hasDefault = true; |
jlaskey@3 | 1910 | } |
jlaskey@3 | 1911 | |
jlaskey@3 | 1912 | if (cases.isEmpty()) { |
sundar@592 | 1913 | // still evaluate expression for side-effects. |
sundar@592 | 1914 | load(expression).pop(); |
jlaskey@3 | 1915 | method.label(breakLabel); |
lagergren@211 | 1916 | return false; |
jlaskey@3 | 1917 | } |
jlaskey@3 | 1918 | |
jlaskey@3 | 1919 | if (allInteger) { |
jlaskey@3 | 1920 | // Tree for sorting values. |
jlaskey@3 | 1921 | final TreeMap<Integer, Label> tree = new TreeMap<>(); |
jlaskey@3 | 1922 | |
jlaskey@3 | 1923 | // Build up sorted tree. |
jlaskey@3 | 1924 | for (final CaseNode caseNode : cases) { |
jlaskey@3 | 1925 | final Node test = caseNode.getTest(); |
jlaskey@3 | 1926 | |
jlaskey@3 | 1927 | if (test != null) { |
lagergren@91 | 1928 | final Integer value = (Integer)((LiteralNode<?>)test).getValue(); |
jlaskey@3 | 1929 | final Label entry = caseNode.getEntry(); |
jlaskey@3 | 1930 | |
jlaskey@3 | 1931 | // Take first duplicate. |
jlaskey@3 | 1932 | if (!(tree.containsKey(value))) { |
jlaskey@3 | 1933 | tree.put(value, entry); |
jlaskey@3 | 1934 | } |
jlaskey@3 | 1935 | } |
jlaskey@3 | 1936 | } |
jlaskey@3 | 1937 | |
jlaskey@3 | 1938 | // Copy values and labels to arrays. |
jlaskey@3 | 1939 | final int size = tree.size(); |
jlaskey@3 | 1940 | final Integer[] values = tree.keySet().toArray(new Integer[size]); |
jlaskey@3 | 1941 | final Label[] labels = tree.values().toArray(new Label[size]); |
jlaskey@3 | 1942 | |
jlaskey@3 | 1943 | // Discern low, high and range. |
jlaskey@3 | 1944 | final int lo = values[0]; |
jlaskey@3 | 1945 | final int hi = values[size - 1]; |
jlaskey@3 | 1946 | final int range = hi - lo + 1; |
jlaskey@3 | 1947 | |
jlaskey@3 | 1948 | // Find an unused value for default. |
jlaskey@3 | 1949 | int deflt = Integer.MIN_VALUE; |
jlaskey@3 | 1950 | for (final int value : values) { |
jlaskey@3 | 1951 | if (deflt == value) { |
jlaskey@3 | 1952 | deflt++; |
jlaskey@3 | 1953 | } else if (deflt < value) { |
jlaskey@3 | 1954 | break; |
jlaskey@3 | 1955 | } |
jlaskey@3 | 1956 | } |
jlaskey@3 | 1957 | |
jlaskey@3 | 1958 | // Load switch expression. |
jlaskey@3 | 1959 | load(expression); |
jlaskey@3 | 1960 | final Type type = expression.getType(); |
jlaskey@3 | 1961 | |
jlaskey@3 | 1962 | // If expression not int see if we can convert, if not use deflt to trigger default. |
jlaskey@3 | 1963 | if (!type.isInteger()) { |
jlaskey@3 | 1964 | method.load(deflt); |
lagergren@405 | 1965 | final Class<?> exprClass = type.getTypeClass(); |
sundar@390 | 1966 | method.invoke(staticCallNoLookup(ScriptRuntime.class, "switchTagAsInt", int.class, exprClass.isPrimitive()? exprClass : Object.class, int.class)); |
jlaskey@3 | 1967 | } |
jlaskey@3 | 1968 | |
jlaskey@3 | 1969 | // If reasonable size and not too sparse (80%), use table otherwise use lookup. |
jlaskey@3 | 1970 | if (range > 0 && range < 4096 && range < (size * 5 / 4)) { |
jlaskey@3 | 1971 | final Label[] table = new Label[range]; |
jlaskey@3 | 1972 | Arrays.fill(table, defaultLabel); |
jlaskey@3 | 1973 | |
jlaskey@3 | 1974 | for (int i = 0; i < size; i++) { |
jlaskey@3 | 1975 | final int value = values[i]; |
jlaskey@3 | 1976 | table[value - lo] = labels[i]; |
jlaskey@3 | 1977 | } |
jlaskey@3 | 1978 | |
lagergren@96 | 1979 | method.tableswitch(lo, hi, defaultLabel, table); |
jlaskey@3 | 1980 | } else { |
jlaskey@3 | 1981 | final int[] ints = new int[size]; |
jlaskey@3 | 1982 | for (int i = 0; i < size; i++) { |
jlaskey@3 | 1983 | ints[i] = values[i]; |
jlaskey@3 | 1984 | } |
jlaskey@3 | 1985 | |
lagergren@96 | 1986 | method.lookupswitch(defaultLabel, ints, labels); |
jlaskey@3 | 1987 | } |
jlaskey@3 | 1988 | } else { |
lagergren@605 | 1989 | load(expression, Type.OBJECT); |
lagergren@605 | 1990 | method.store(tag); |
jlaskey@3 | 1991 | |
jlaskey@3 | 1992 | for (final CaseNode caseNode : cases) { |
attila@430 | 1993 | final Expression test = caseNode.getTest(); |
jlaskey@3 | 1994 | |
jlaskey@3 | 1995 | if (test != null) { |
jlaskey@3 | 1996 | method.load(tag); |
lagergren@605 | 1997 | load(test, Type.OBJECT); |
jlaskey@3 | 1998 | method.invoke(ScriptRuntime.EQ_STRICT); |
jlaskey@3 | 1999 | method.ifne(caseNode.getEntry()); |
jlaskey@3 | 2000 | } |
jlaskey@3 | 2001 | } |
jlaskey@3 | 2002 | |
jlaskey@3 | 2003 | method._goto(hasDefault ? defaultLabel : breakLabel); |
jlaskey@3 | 2004 | } |
jlaskey@3 | 2005 | |
jlaskey@3 | 2006 | for (final CaseNode caseNode : cases) { |
jlaskey@3 | 2007 | method.label(caseNode.getEntry()); |
jlaskey@3 | 2008 | caseNode.getBody().accept(this); |
jlaskey@3 | 2009 | } |
jlaskey@3 | 2010 | |
jlaskey@3 | 2011 | if (!switchNode.isTerminal()) { |
jlaskey@3 | 2012 | method.label(breakLabel); |
jlaskey@3 | 2013 | } |
jlaskey@3 | 2014 | |
lagergren@211 | 2015 | return false; |
jlaskey@3 | 2016 | } |
jlaskey@3 | 2017 | |
jlaskey@3 | 2018 | @Override |
lagergren@211 | 2019 | public boolean enterThrowNode(final ThrowNode throwNode) { |
lagergren@253 | 2020 | lineNumber(throwNode); |
lagergren@253 | 2021 | |
lagergren@265 | 2022 | if (throwNode.isSyntheticRethrow()) { |
lagergren@265 | 2023 | //do not wrap whatever this is in an ecma exception, just rethrow it |
lagergren@265 | 2024 | load(throwNode.getExpression()); |
lagergren@265 | 2025 | method.athrow(); |
lagergren@265 | 2026 | return false; |
lagergren@265 | 2027 | } |
lagergren@265 | 2028 | |
lagergren@290 | 2029 | final Source source = lc.getCurrentFunction().getSource(); |
lagergren@252 | 2030 | |
attila@430 | 2031 | final Expression expression = throwNode.getExpression(); |
attila@430 | 2032 | final int position = throwNode.position(); |
hannesw@582 | 2033 | final int line = throwNode.getLineNumber(); |
attila@430 | 2034 | final int column = source.getColumn(position); |
jlaskey@3 | 2035 | |
lagergren@605 | 2036 | load(expression, Type.OBJECT); |
jlaskey@3 | 2037 | |
jlaskey@3 | 2038 | method.load(source.getName()); |
jlaskey@3 | 2039 | method.load(line); |
jlaskey@3 | 2040 | method.load(column); |
sundar@758 | 2041 | method.invoke(ECMAException.CREATE); |
jlaskey@3 | 2042 | |
jlaskey@3 | 2043 | method.athrow(); |
jlaskey@3 | 2044 | |
lagergren@211 | 2045 | return false; |
jlaskey@3 | 2046 | } |
jlaskey@3 | 2047 | |
jlaskey@3 | 2048 | @Override |
lagergren@211 | 2049 | public boolean enterTryNode(final TryNode tryNode) { |
lagergren@253 | 2050 | lineNumber(tryNode); |
lagergren@253 | 2051 | |
jlaskey@3 | 2052 | final Block body = tryNode.getBody(); |
jlaskey@3 | 2053 | final List<Block> catchBlocks = tryNode.getCatchBlocks(); |
jlaskey@3 | 2054 | final Symbol symbol = tryNode.getException(); |
jlaskey@3 | 2055 | final Label entry = new Label("try"); |
jlaskey@3 | 2056 | final Label recovery = new Label("catch"); |
jlaskey@3 | 2057 | final Label exit = tryNode.getExit(); |
jlaskey@3 | 2058 | final Label skip = new Label("skip"); |
jlaskey@3 | 2059 | |
jlaskey@3 | 2060 | method.label(entry); |
jlaskey@3 | 2061 | |
jlaskey@3 | 2062 | body.accept(this); |
jlaskey@3 | 2063 | |
jlaskey@3 | 2064 | if (!body.hasTerminalFlags()) { |
jlaskey@3 | 2065 | method._goto(skip); |
jlaskey@3 | 2066 | } |
jlaskey@3 | 2067 | |
jlaskey@3 | 2068 | method.label(exit); |
jlaskey@3 | 2069 | |
jlaskey@3 | 2070 | method._catch(recovery); |
jlaskey@3 | 2071 | method.store(symbol); |
jlaskey@3 | 2072 | |
jlaskey@3 | 2073 | for (int i = 0; i < catchBlocks.size(); i++) { |
jlaskey@3 | 2074 | final Block catchBlock = catchBlocks.get(i); |
jlaskey@3 | 2075 | |
lagergren@211 | 2076 | //TODO this is very ugly - try not to call enter/leave methods directly |
lagergren@211 | 2077 | //better to use the implicit lexical context scoping given by the visitor's |
lagergren@211 | 2078 | //accept method. |
lagergren@290 | 2079 | lc.push(catchBlock); |
lagergren@211 | 2080 | enterBlock(catchBlock); |
lagergren@211 | 2081 | |
attila@430 | 2082 | final CatchNode catchNode = (CatchNode)catchBlocks.get(i).getStatements().get(0); |
attila@430 | 2083 | final IdentNode exception = catchNode.getException(); |
attila@430 | 2084 | final Expression exceptionCondition = catchNode.getExceptionCondition(); |
attila@430 | 2085 | final Block catchBody = catchNode.getBody(); |
lagergren@211 | 2086 | |
lagergren@211 | 2087 | new Store<IdentNode>(exception) { |
lagergren@211 | 2088 | @Override |
lagergren@211 | 2089 | protected void storeNonDiscard() { |
lagergren@211 | 2090 | return; |
lagergren@211 | 2091 | } |
lagergren@290 | 2092 | |
lagergren@211 | 2093 | @Override |
lagergren@211 | 2094 | protected void evaluate() { |
lagergren@265 | 2095 | if (catchNode.isSyntheticRethrow()) { |
lagergren@265 | 2096 | method.load(symbol); |
lagergren@265 | 2097 | return; |
lagergren@265 | 2098 | } |
lagergren@211 | 2099 | /* |
lagergren@211 | 2100 | * If caught object is an instance of ECMAException, then |
lagergren@211 | 2101 | * bind obj.thrown to the script catch var. Or else bind the |
lagergren@211 | 2102 | * caught object itself to the script catch var. |
lagergren@211 | 2103 | */ |
lagergren@211 | 2104 | final Label notEcmaException = new Label("no_ecma_exception"); |
lagergren@211 | 2105 | method.load(symbol).dup()._instanceof(ECMAException.class).ifeq(notEcmaException); |
lagergren@211 | 2106 | method.checkcast(ECMAException.class); //TODO is this necessary? |
lagergren@211 | 2107 | method.getField(ECMAException.THROWN); |
lagergren@211 | 2108 | method.label(notEcmaException); |
lagergren@211 | 2109 | } |
lagergren@211 | 2110 | }.store(); |
lagergren@211 | 2111 | |
lagergren@211 | 2112 | final Label next; |
lagergren@211 | 2113 | |
lagergren@211 | 2114 | if (exceptionCondition != null) { |
lagergren@211 | 2115 | next = new Label("next"); |
attila@644 | 2116 | load(exceptionCondition, Type.BOOLEAN).ifeq(next); |
lagergren@211 | 2117 | } else { |
lagergren@211 | 2118 | next = null; |
lagergren@211 | 2119 | } |
lagergren@211 | 2120 | |
lagergren@211 | 2121 | catchBody.accept(this); |
lagergren@211 | 2122 | |
lagergren@211 | 2123 | if (i + 1 != catchBlocks.size() && !catchBody.hasTerminalFlags()) { |
lagergren@211 | 2124 | method._goto(skip); |
lagergren@211 | 2125 | } |
lagergren@211 | 2126 | |
lagergren@211 | 2127 | if (next != null) { |
lagergren@211 | 2128 | if (i + 1 == catchBlocks.size()) { |
lagergren@211 | 2129 | // no next catch block - rethrow if condition failed |
lagergren@211 | 2130 | method._goto(skip); |
lagergren@211 | 2131 | method.label(next); |
jlaskey@3 | 2132 | method.load(symbol).athrow(); |
lagergren@211 | 2133 | } else { |
lagergren@211 | 2134 | method.label(next); |
jlaskey@3 | 2135 | } |
jlaskey@3 | 2136 | } |
lagergren@211 | 2137 | |
lagergren@211 | 2138 | leaveBlock(catchBlock); |
lagergren@290 | 2139 | lc.pop(catchBlock); |
jlaskey@3 | 2140 | } |
jlaskey@3 | 2141 | |
jlaskey@3 | 2142 | method.label(skip); |
jlaskey@3 | 2143 | method._try(entry, exit, recovery, Throwable.class); |
jlaskey@3 | 2144 | |
jlaskey@3 | 2145 | // Finally body is always inlined elsewhere so it doesn't need to be emitted |
jlaskey@3 | 2146 | |
lagergren@211 | 2147 | return false; |
jlaskey@3 | 2148 | } |
jlaskey@3 | 2149 | |
jlaskey@3 | 2150 | @Override |
lagergren@211 | 2151 | public boolean enterVarNode(final VarNode varNode) { |
lagergren@253 | 2152 | |
attila@430 | 2153 | final Expression init = varNode.getInit(); |
jlaskey@3 | 2154 | |
lagergren@211 | 2155 | if (init == null) { |
lagergren@211 | 2156 | return false; |
jlaskey@3 | 2157 | } |
jlaskey@3 | 2158 | |
lagergren@253 | 2159 | lineNumber(varNode); |
lagergren@253 | 2160 | |
lagergren@605 | 2161 | final IdentNode identNode = varNode.getName(); |
lagergren@605 | 2162 | final Symbol identSymbol = identNode.getSymbol(); |
lagergren@605 | 2163 | assert identSymbol != null : "variable node " + varNode + " requires a name with a symbol"; |
jlaskey@3 | 2164 | |
jlaskey@3 | 2165 | assert method != null; |
jlaskey@3 | 2166 | |
lagergren@605 | 2167 | final boolean needsScope = identSymbol.isScope(); |
jlaskey@3 | 2168 | if (needsScope) { |
lagergren@211 | 2169 | method.loadCompilerConstant(SCOPE); |
jlaskey@3 | 2170 | } |
jlaskey@3 | 2171 | |
jlaskey@3 | 2172 | if (needsScope) { |
lagergren@605 | 2173 | load(init); |
jlaskey@3 | 2174 | int flags = CALLSITE_SCOPE | getCallSiteFlags(); |
lagergren@605 | 2175 | if (isFastScope(identSymbol)) { |
lagergren@605 | 2176 | storeFastScopeVar(identSymbol, flags); |
jlaskey@3 | 2177 | } else { |
lagergren@605 | 2178 | method.dynamicSet(identNode.getName(), flags); |
jlaskey@3 | 2179 | } |
jlaskey@3 | 2180 | } else { |
lagergren@605 | 2181 | load(init, identNode.getType()); |
lagergren@605 | 2182 | method.store(identSymbol); |
jlaskey@3 | 2183 | } |
jlaskey@3 | 2184 | |
lagergren@211 | 2185 | return false; |
jlaskey@3 | 2186 | } |
jlaskey@3 | 2187 | |
jlaskey@3 | 2188 | @Override |
lagergren@211 | 2189 | public boolean enterWhileNode(final WhileNode whileNode) { |
attila@430 | 2190 | final Expression test = whileNode.getTest(); |
attila@430 | 2191 | final Block body = whileNode.getBody(); |
attila@430 | 2192 | final Label breakLabel = whileNode.getBreakLabel(); |
attila@430 | 2193 | final Label continueLabel = whileNode.getContinueLabel(); |
lagergren@679 | 2194 | final boolean isDoWhile = whileNode.isDoWhile(); |
attila@430 | 2195 | final Label loopLabel = new Label("loop"); |
jlaskey@3 | 2196 | |
lagergren@679 | 2197 | if (!isDoWhile) { |
jlaskey@3 | 2198 | method._goto(continueLabel); |
jlaskey@3 | 2199 | } |
jlaskey@3 | 2200 | |
jlaskey@3 | 2201 | method.label(loopLabel); |
jlaskey@3 | 2202 | body.accept(this); |
jlaskey@3 | 2203 | if (!whileNode.isTerminal()) { |
jlaskey@3 | 2204 | method.label(continueLabel); |
lagergren@679 | 2205 | lineNumber(whileNode); |
jlaskey@3 | 2206 | new BranchOptimizer(this, method).execute(test, loopLabel, true); |
jlaskey@3 | 2207 | method.label(breakLabel); |
jlaskey@3 | 2208 | } |
jlaskey@3 | 2209 | |
lagergren@211 | 2210 | return false; |
jlaskey@3 | 2211 | } |
jlaskey@3 | 2212 | |
jlaskey@3 | 2213 | private void closeWith() { |
lagergren@253 | 2214 | if (method.hasScope()) { |
attila@233 | 2215 | method.loadCompilerConstant(SCOPE); |
attila@233 | 2216 | method.invoke(ScriptRuntime.CLOSE_WITH); |
attila@233 | 2217 | method.storeCompilerConstant(SCOPE); |
attila@233 | 2218 | } |
jlaskey@3 | 2219 | } |
jlaskey@3 | 2220 | |
jlaskey@3 | 2221 | @Override |
lagergren@211 | 2222 | public boolean enterWithNode(final WithNode withNode) { |
attila@430 | 2223 | final Expression expression = withNode.getExpression(); |
attila@430 | 2224 | final Node body = withNode.getBody(); |
jlaskey@3 | 2225 | |
attila@233 | 2226 | // It is possible to have a "pathological" case where the with block does not reference *any* identifiers. It's |
attila@233 | 2227 | // pointless, but legal. In that case, if nothing else in the method forced the assignment of a slot to the |
attila@233 | 2228 | // scope object, its' possible that it won't have a slot assigned. In this case we'll only evaluate expression |
attila@233 | 2229 | // for its side effect and visit the body, and not bother opening and closing a WithObject. |
attila@233 | 2230 | final boolean hasScope = method.hasScope(); |
attila@233 | 2231 | |
attila@233 | 2232 | final Label tryLabel; |
lagergren@290 | 2233 | if (hasScope) { |
attila@233 | 2234 | tryLabel = new Label("with_try"); |
attila@233 | 2235 | method.label(tryLabel); |
attila@233 | 2236 | method.loadCompilerConstant(SCOPE); |
attila@233 | 2237 | } else { |
attila@233 | 2238 | tryLabel = null; |
attila@233 | 2239 | } |
attila@233 | 2240 | |
lagergren@605 | 2241 | load(expression, Type.OBJECT); |
jlaskey@3 | 2242 | |
lagergren@290 | 2243 | if (hasScope) { |
attila@233 | 2244 | // Construct a WithObject if we have a scope |
attila@233 | 2245 | method.invoke(ScriptRuntime.OPEN_WITH); |
attila@233 | 2246 | method.storeCompilerConstant(SCOPE); |
attila@233 | 2247 | } else { |
sundar@565 | 2248 | // We just loaded the expression for its side effect and to check |
sundar@565 | 2249 | // for null or undefined value. |
sundar@565 | 2250 | globalCheckObjectCoercible(); |
attila@233 | 2251 | } |
attila@233 | 2252 | |
attila@233 | 2253 | |
attila@233 | 2254 | // Always process body |
jlaskey@3 | 2255 | body.accept(this); |
jlaskey@3 | 2256 | |
lagergren@253 | 2257 | if (hasScope) { |
attila@233 | 2258 | // Ensure we always close the WithObject |
attila@233 | 2259 | final Label endLabel = new Label("with_end"); |
attila@233 | 2260 | final Label catchLabel = new Label("with_catch"); |
attila@233 | 2261 | final Label exitLabel = new Label("with_exit"); |
attila@233 | 2262 | |
attila@233 | 2263 | if (!body.isTerminal()) { |
attila@233 | 2264 | closeWith(); |
attila@233 | 2265 | method._goto(exitLabel); |
attila@233 | 2266 | } |
attila@233 | 2267 | |
attila@233 | 2268 | method.label(endLabel); |
attila@233 | 2269 | |
attila@233 | 2270 | method._catch(catchLabel); |
jlaskey@3 | 2271 | closeWith(); |
attila@233 | 2272 | method.athrow(); |
attila@233 | 2273 | |
attila@233 | 2274 | method.label(exitLabel); |
attila@233 | 2275 | |
attila@233 | 2276 | method._try(tryLabel, endLabel, catchLabel); |
jlaskey@3 | 2277 | } |
lagergren@211 | 2278 | return false; |
jlaskey@3 | 2279 | } |
jlaskey@3 | 2280 | |
jlaskey@3 | 2281 | @Override |
lagergren@211 | 2282 | public boolean enterADD(final UnaryNode unaryNode) { |
lagergren@605 | 2283 | load(unaryNode.rhs(), unaryNode.getType()); |
lagergren@605 | 2284 | assert unaryNode.getType().isNumeric(); |
jlaskey@3 | 2285 | method.store(unaryNode.getSymbol()); |
lagergren@211 | 2286 | return false; |
jlaskey@3 | 2287 | } |
jlaskey@3 | 2288 | |
jlaskey@3 | 2289 | @Override |
lagergren@211 | 2290 | public boolean enterBIT_NOT(final UnaryNode unaryNode) { |
lagergren@605 | 2291 | load(unaryNode.rhs(), Type.INT).load(-1).xor().store(unaryNode.getSymbol()); |
lagergren@211 | 2292 | return false; |
jlaskey@3 | 2293 | } |
jlaskey@3 | 2294 | |
jlaskey@3 | 2295 | @Override |
lagergren@211 | 2296 | public boolean enterDECINC(final UnaryNode unaryNode) { |
attila@430 | 2297 | final Expression rhs = unaryNode.rhs(); |
attila@430 | 2298 | final Type type = unaryNode.getType(); |
attila@430 | 2299 | final TokenType tokenType = unaryNode.tokenType(); |
attila@430 | 2300 | final boolean isPostfix = tokenType == TokenType.DECPOSTFIX || tokenType == TokenType.INCPOSTFIX; |
attila@430 | 2301 | final boolean isIncrement = tokenType == TokenType.INCPREFIX || tokenType == TokenType.INCPOSTFIX; |
jlaskey@3 | 2302 | |
jlaskey@3 | 2303 | assert !type.isObject(); |
jlaskey@3 | 2304 | |
jlaskey@3 | 2305 | new SelfModifyingStore<UnaryNode>(unaryNode, rhs) { |
jlaskey@3 | 2306 | |
jlaskey@3 | 2307 | @Override |
jlaskey@3 | 2308 | protected void evaluate() { |
lagergren@605 | 2309 | load(rhs, type, true); |
jlaskey@3 | 2310 | if (!isPostfix) { |
jlaskey@3 | 2311 | if (type.isInteger()) { |
jlaskey@3 | 2312 | method.load(isIncrement ? 1 : -1); |
jlaskey@3 | 2313 | } else if (type.isLong()) { |
jlaskey@3 | 2314 | method.load(isIncrement ? 1L : -1L); |
jlaskey@3 | 2315 | } else { |
jlaskey@3 | 2316 | method.load(isIncrement ? 1.0 : -1.0); |
jlaskey@3 | 2317 | } |
jlaskey@3 | 2318 | method.add(); |
jlaskey@3 | 2319 | } |
jlaskey@3 | 2320 | } |
jlaskey@3 | 2321 | |
jlaskey@3 | 2322 | @Override |
jlaskey@3 | 2323 | protected void storeNonDiscard() { |
jlaskey@3 | 2324 | super.storeNonDiscard(); |
jlaskey@3 | 2325 | if (isPostfix) { |
jlaskey@3 | 2326 | if (type.isInteger()) { |
jlaskey@3 | 2327 | method.load(isIncrement ? 1 : -1); |
jlaskey@3 | 2328 | } else if (type.isLong()) { |
jlaskey@3 | 2329 | method.load(isIncrement ? 1L : 1L); |
jlaskey@3 | 2330 | } else { |
jlaskey@3 | 2331 | method.load(isIncrement ? 1.0 : -1.0); |
jlaskey@3 | 2332 | } |
jlaskey@3 | 2333 | method.add(); |
jlaskey@3 | 2334 | } |
jlaskey@3 | 2335 | } |
jlaskey@3 | 2336 | }.store(); |
jlaskey@3 | 2337 | |
lagergren@211 | 2338 | return false; |
jlaskey@3 | 2339 | } |
jlaskey@3 | 2340 | |
jlaskey@3 | 2341 | @Override |
lagergren@211 | 2342 | public boolean enterDISCARD(final UnaryNode unaryNode) { |
attila@430 | 2343 | final Expression rhs = unaryNode.rhs(); |
lagergren@211 | 2344 | |
lagergren@290 | 2345 | lc.pushDiscard(rhs); |
lagergren@211 | 2346 | load(rhs); |
lagergren@211 | 2347 | |
lagergren@290 | 2348 | if (lc.getCurrentDiscard() == rhs) { |
lagergren@211 | 2349 | assert !rhs.isAssignment(); |
lagergren@211 | 2350 | method.pop(); |
lagergren@290 | 2351 | lc.popDiscard(); |
jlaskey@3 | 2352 | } |
lagergren@247 | 2353 | |
lagergren@211 | 2354 | return false; |
jlaskey@3 | 2355 | } |
jlaskey@3 | 2356 | |
jlaskey@3 | 2357 | @Override |
lagergren@211 | 2358 | public boolean enterNEW(final UnaryNode unaryNode) { |
jlaskey@3 | 2359 | final CallNode callNode = (CallNode)unaryNode.rhs(); |
attila@430 | 2360 | final List<Expression> args = callNode.getArgs(); |
jlaskey@3 | 2361 | |
jlaskey@3 | 2362 | // Load function reference. |
attila@644 | 2363 | load(callNode.getFunction(), Type.OBJECT); // must detect type error |
jlaskey@3 | 2364 | |
lagergren@66 | 2365 | method.dynamicNew(1 + loadArgs(args), getCallSiteFlags()); |
jlaskey@3 | 2366 | method.store(unaryNode.getSymbol()); |
jlaskey@3 | 2367 | |
lagergren@211 | 2368 | return false; |
jlaskey@3 | 2369 | } |
jlaskey@3 | 2370 | |
jlaskey@3 | 2371 | @Override |
lagergren@211 | 2372 | public boolean enterNOT(final UnaryNode unaryNode) { |
attila@430 | 2373 | final Expression rhs = unaryNode.rhs(); |
jlaskey@3 | 2374 | |
lagergren@605 | 2375 | load(rhs, Type.BOOLEAN); |
jlaskey@3 | 2376 | |
jlaskey@3 | 2377 | final Label trueLabel = new Label("true"); |
jlaskey@3 | 2378 | final Label afterLabel = new Label("after"); |
jlaskey@3 | 2379 | |
jlaskey@3 | 2380 | method.ifne(trueLabel); |
jlaskey@3 | 2381 | method.load(true); |
jlaskey@3 | 2382 | method._goto(afterLabel); |
jlaskey@3 | 2383 | method.label(trueLabel); |
jlaskey@3 | 2384 | method.load(false); |
jlaskey@3 | 2385 | method.label(afterLabel); |
jlaskey@3 | 2386 | method.store(unaryNode.getSymbol()); |
jlaskey@3 | 2387 | |
lagergren@211 | 2388 | return false; |
jlaskey@3 | 2389 | } |
jlaskey@3 | 2390 | |
jlaskey@3 | 2391 | @Override |
lagergren@211 | 2392 | public boolean enterSUB(final UnaryNode unaryNode) { |
lagergren@605 | 2393 | assert unaryNode.getType().isNumeric(); |
attila@644 | 2394 | load(unaryNode.rhs(), unaryNode.getType()).neg().store(unaryNode.getSymbol()); |
lagergren@211 | 2395 | return false; |
jlaskey@3 | 2396 | } |
jlaskey@3 | 2397 | |
sundar@401 | 2398 | @Override |
sundar@401 | 2399 | public boolean enterVOID(final UnaryNode unaryNode) { |
sundar@401 | 2400 | load(unaryNode.rhs()).pop(); |
sundar@401 | 2401 | method.loadUndefined(Type.OBJECT); |
sundar@401 | 2402 | |
sundar@401 | 2403 | return false; |
sundar@401 | 2404 | } |
sundar@401 | 2405 | |
attila@430 | 2406 | private void enterNumericAdd(final Expression lhs, final Expression rhs, final Type type, final Symbol symbol) { |
lagergren@605 | 2407 | loadBinaryOperands(lhs, rhs, type); |
lagergren@290 | 2408 | method.add(); //if the symbol is optimistic, it always needs to be written, not on the stack? |
jlaskey@3 | 2409 | method.store(symbol); |
jlaskey@3 | 2410 | } |
jlaskey@3 | 2411 | |
jlaskey@3 | 2412 | @Override |
lagergren@211 | 2413 | public boolean enterADD(final BinaryNode binaryNode) { |
attila@430 | 2414 | final Expression lhs = binaryNode.lhs(); |
attila@430 | 2415 | final Expression rhs = binaryNode.rhs(); |
jlaskey@3 | 2416 | |
jlaskey@3 | 2417 | final Type type = binaryNode.getType(); |
jlaskey@3 | 2418 | if (type.isNumeric()) { |
jlaskey@3 | 2419 | enterNumericAdd(lhs, rhs, type, binaryNode.getSymbol()); |
jlaskey@3 | 2420 | } else { |
lagergren@605 | 2421 | loadBinaryOperands(binaryNode); |
jlaskey@3 | 2422 | method.add(); |
jlaskey@3 | 2423 | method.store(binaryNode.getSymbol()); |
jlaskey@3 | 2424 | } |
jlaskey@3 | 2425 | |
lagergren@211 | 2426 | return false; |
jlaskey@3 | 2427 | } |
jlaskey@3 | 2428 | |
lagergren@211 | 2429 | private boolean enterAND_OR(final BinaryNode binaryNode) { |
attila@430 | 2430 | final Expression lhs = binaryNode.lhs(); |
attila@430 | 2431 | final Expression rhs = binaryNode.rhs(); |
jlaskey@3 | 2432 | |
jlaskey@3 | 2433 | final Label skip = new Label("skip"); |
jlaskey@3 | 2434 | |
attila@644 | 2435 | load(lhs, Type.OBJECT).dup().convert(Type.BOOLEAN); |
jlaskey@3 | 2436 | |
jlaskey@3 | 2437 | if (binaryNode.tokenType() == TokenType.AND) { |
jlaskey@3 | 2438 | method.ifeq(skip); |
jlaskey@3 | 2439 | } else { |
jlaskey@3 | 2440 | method.ifne(skip); |
jlaskey@3 | 2441 | } |
jlaskey@3 | 2442 | |
jlaskey@3 | 2443 | method.pop(); |
attila@644 | 2444 | load(rhs, Type.OBJECT); |
jlaskey@3 | 2445 | method.label(skip); |
jlaskey@3 | 2446 | method.store(binaryNode.getSymbol()); |
jlaskey@3 | 2447 | |
lagergren@211 | 2448 | return false; |
jlaskey@3 | 2449 | } |
jlaskey@3 | 2450 | |
jlaskey@3 | 2451 | @Override |
lagergren@211 | 2452 | public boolean enterAND(final BinaryNode binaryNode) { |
jlaskey@3 | 2453 | return enterAND_OR(binaryNode); |
jlaskey@3 | 2454 | } |
jlaskey@3 | 2455 | |
jlaskey@3 | 2456 | @Override |
lagergren@211 | 2457 | public boolean enterASSIGN(final BinaryNode binaryNode) { |
attila@430 | 2458 | final Expression lhs = binaryNode.lhs(); |
attila@430 | 2459 | final Expression rhs = binaryNode.rhs(); |
jlaskey@3 | 2460 | |
jlaskey@3 | 2461 | final Type lhsType = lhs.getType(); |
jlaskey@3 | 2462 | final Type rhsType = rhs.getType(); |
jlaskey@3 | 2463 | |
jlaskey@3 | 2464 | if (!lhsType.isEquivalentTo(rhsType)) { |
jlaskey@3 | 2465 | //this is OK if scoped, only locals are wrong |
jlaskey@3 | 2466 | } |
jlaskey@3 | 2467 | |
jlaskey@3 | 2468 | new Store<BinaryNode>(binaryNode, lhs) { |
jlaskey@3 | 2469 | @Override |
jlaskey@3 | 2470 | protected void evaluate() { |
lagergren@605 | 2471 | if ((lhs instanceof IdentNode) && !lhs.getSymbol().isScope()) { |
lagergren@605 | 2472 | load(rhs, lhsType); |
lagergren@605 | 2473 | } else { |
lagergren@605 | 2474 | load(rhs); |
lagergren@605 | 2475 | } |
jlaskey@3 | 2476 | } |
jlaskey@3 | 2477 | }.store(); |
jlaskey@3 | 2478 | |
lagergren@211 | 2479 | return false; |
jlaskey@3 | 2480 | } |
jlaskey@3 | 2481 | |
jlaskey@3 | 2482 | /** |
jlaskey@3 | 2483 | * Helper class for assignment ops, e.g. *=, += and so on.. |
jlaskey@3 | 2484 | */ |
jlaskey@3 | 2485 | private abstract class AssignOp extends SelfModifyingStore<BinaryNode> { |
jlaskey@3 | 2486 | |
jlaskey@3 | 2487 | /** The type of the resulting operation */ |
jlaskey@3 | 2488 | private final Type opType; |
jlaskey@3 | 2489 | |
jlaskey@3 | 2490 | /** |
jlaskey@3 | 2491 | * Constructor |
jlaskey@3 | 2492 | * |
jlaskey@3 | 2493 | * @param node the assign op node |
jlaskey@3 | 2494 | */ |
jlaskey@3 | 2495 | AssignOp(final BinaryNode node) { |
jlaskey@3 | 2496 | this(node.getType(), node); |
jlaskey@3 | 2497 | } |
jlaskey@3 | 2498 | |
jlaskey@3 | 2499 | /** |
jlaskey@3 | 2500 | * Constructor |
jlaskey@3 | 2501 | * |
jlaskey@3 | 2502 | * @param opType type of the computation - overriding the type of the node |
jlaskey@3 | 2503 | * @param node the assign op node |
jlaskey@3 | 2504 | */ |
jlaskey@3 | 2505 | AssignOp(final Type opType, final BinaryNode node) { |
jlaskey@3 | 2506 | super(node, node.lhs()); |
jlaskey@3 | 2507 | this.opType = opType; |
jlaskey@3 | 2508 | } |
jlaskey@3 | 2509 | |
jlaskey@3 | 2510 | protected abstract void op(); |
jlaskey@3 | 2511 | |
jlaskey@3 | 2512 | @Override |
jlaskey@3 | 2513 | protected void evaluate() { |
lagergren@605 | 2514 | loadBinaryOperands(assignNode.lhs(), assignNode.rhs(), opType, true); |
jlaskey@3 | 2515 | op(); |
jlaskey@3 | 2516 | method.convert(assignNode.getType()); |
jlaskey@3 | 2517 | } |
jlaskey@3 | 2518 | } |
jlaskey@3 | 2519 | |
jlaskey@3 | 2520 | @Override |
lagergren@211 | 2521 | public boolean enterASSIGN_ADD(final BinaryNode binaryNode) { |
jlaskey@3 | 2522 | assert RuntimeNode.Request.ADD.canSpecialize(); |
lagergren@211 | 2523 | final Type lhsType = binaryNode.lhs().getType(); |
lagergren@211 | 2524 | final Type rhsType = binaryNode.rhs().getType(); |
jlaskey@3 | 2525 | final boolean specialize = binaryNode.getType() == Type.OBJECT; |
jlaskey@3 | 2526 | |
jlaskey@3 | 2527 | new AssignOp(binaryNode) { |
jlaskey@3 | 2528 | |
jlaskey@3 | 2529 | @Override |
jlaskey@3 | 2530 | protected void op() { |
lagergren@211 | 2531 | if (specialize) { |
lagergren@211 | 2532 | method.dynamicRuntimeCall( |
lagergren@211 | 2533 | new SpecializedRuntimeNode( |
lagergren@211 | 2534 | Request.ADD, |
lagergren@211 | 2535 | new Type[] { |
lagergren@211 | 2536 | lhsType, |
lagergren@211 | 2537 | rhsType, |
lagergren@211 | 2538 | }, |
lagergren@211 | 2539 | Type.OBJECT).getInitialName(), |
lagergren@211 | 2540 | Type.OBJECT, |
lagergren@211 | 2541 | Request.ADD); |
lagergren@211 | 2542 | } else { |
lagergren@211 | 2543 | method.add(); |
lagergren@211 | 2544 | } |
jlaskey@3 | 2545 | } |
jlaskey@3 | 2546 | |
jlaskey@3 | 2547 | @Override |
jlaskey@3 | 2548 | protected void evaluate() { |
jlaskey@3 | 2549 | super.evaluate(); |
jlaskey@3 | 2550 | } |
jlaskey@3 | 2551 | }.store(); |
jlaskey@3 | 2552 | |
lagergren@211 | 2553 | return false; |
jlaskey@3 | 2554 | } |
jlaskey@3 | 2555 | |
jlaskey@3 | 2556 | @Override |
lagergren@211 | 2557 | public boolean enterASSIGN_BIT_AND(final BinaryNode binaryNode) { |
jlaskey@3 | 2558 | new AssignOp(Type.INT, binaryNode) { |
jlaskey@3 | 2559 | @Override |
jlaskey@3 | 2560 | protected void op() { |
jlaskey@3 | 2561 | method.and(); |
jlaskey@3 | 2562 | } |
jlaskey@3 | 2563 | }.store(); |
jlaskey@3 | 2564 | |
lagergren@211 | 2565 | return false; |
jlaskey@3 | 2566 | } |
jlaskey@3 | 2567 | |
jlaskey@3 | 2568 | @Override |
lagergren@211 | 2569 | public boolean enterASSIGN_BIT_OR(final BinaryNode binaryNode) { |
jlaskey@3 | 2570 | new AssignOp(Type.INT, binaryNode) { |
jlaskey@3 | 2571 | @Override |
jlaskey@3 | 2572 | protected void op() { |
jlaskey@3 | 2573 | method.or(); |
jlaskey@3 | 2574 | } |
jlaskey@3 | 2575 | }.store(); |
jlaskey@3 | 2576 | |
lagergren@211 | 2577 | return false; |
jlaskey@3 | 2578 | } |
jlaskey@3 | 2579 | |
jlaskey@3 | 2580 | @Override |
lagergren@211 | 2581 | public boolean enterASSIGN_BIT_XOR(final BinaryNode binaryNode) { |
jlaskey@3 | 2582 | new AssignOp(Type.INT, binaryNode) { |
jlaskey@3 | 2583 | @Override |
jlaskey@3 | 2584 | protected void op() { |
jlaskey@3 | 2585 | method.xor(); |
jlaskey@3 | 2586 | } |
jlaskey@3 | 2587 | }.store(); |
jlaskey@3 | 2588 | |
lagergren@211 | 2589 | return false; |
jlaskey@3 | 2590 | } |
jlaskey@3 | 2591 | |
jlaskey@3 | 2592 | @Override |
lagergren@211 | 2593 | public boolean enterASSIGN_DIV(final BinaryNode binaryNode) { |
jlaskey@3 | 2594 | new AssignOp(binaryNode) { |
jlaskey@3 | 2595 | @Override |
jlaskey@3 | 2596 | protected void op() { |
jlaskey@3 | 2597 | method.div(); |
jlaskey@3 | 2598 | } |
jlaskey@3 | 2599 | }.store(); |
jlaskey@3 | 2600 | |
lagergren@211 | 2601 | return false; |
jlaskey@3 | 2602 | } |
jlaskey@3 | 2603 | |
jlaskey@3 | 2604 | @Override |
lagergren@211 | 2605 | public boolean enterASSIGN_MOD(final BinaryNode binaryNode) { |
jlaskey@3 | 2606 | new AssignOp(binaryNode) { |
jlaskey@3 | 2607 | @Override |
jlaskey@3 | 2608 | protected void op() { |
jlaskey@3 | 2609 | method.rem(); |
jlaskey@3 | 2610 | } |
jlaskey@3 | 2611 | }.store(); |
jlaskey@3 | 2612 | |
lagergren@211 | 2613 | return false; |
jlaskey@3 | 2614 | } |
jlaskey@3 | 2615 | |
jlaskey@3 | 2616 | @Override |
lagergren@211 | 2617 | public boolean enterASSIGN_MUL(final BinaryNode binaryNode) { |
jlaskey@3 | 2618 | new AssignOp(binaryNode) { |
jlaskey@3 | 2619 | @Override |
jlaskey@3 | 2620 | protected void op() { |
jlaskey@3 | 2621 | method.mul(); |
jlaskey@3 | 2622 | } |
jlaskey@3 | 2623 | }.store(); |
jlaskey@3 | 2624 | |
lagergren@211 | 2625 | return false; |
jlaskey@3 | 2626 | } |
jlaskey@3 | 2627 | |
jlaskey@3 | 2628 | @Override |
lagergren@211 | 2629 | public boolean enterASSIGN_SAR(final BinaryNode binaryNode) { |
jlaskey@3 | 2630 | new AssignOp(Type.INT, binaryNode) { |
jlaskey@3 | 2631 | @Override |
jlaskey@3 | 2632 | protected void op() { |
jlaskey@3 | 2633 | method.sar(); |
jlaskey@3 | 2634 | } |
jlaskey@3 | 2635 | }.store(); |
jlaskey@3 | 2636 | |
lagergren@211 | 2637 | return false; |
jlaskey@3 | 2638 | } |
jlaskey@3 | 2639 | |
jlaskey@3 | 2640 | @Override |
lagergren@211 | 2641 | public boolean enterASSIGN_SHL(final BinaryNode binaryNode) { |
jlaskey@3 | 2642 | new AssignOp(Type.INT, binaryNode) { |
jlaskey@3 | 2643 | @Override |
jlaskey@3 | 2644 | protected void op() { |
jlaskey@3 | 2645 | method.shl(); |
jlaskey@3 | 2646 | } |
jlaskey@3 | 2647 | }.store(); |
jlaskey@3 | 2648 | |
lagergren@211 | 2649 | return false; |
jlaskey@3 | 2650 | } |
jlaskey@3 | 2651 | |
jlaskey@3 | 2652 | @Override |
lagergren@211 | 2653 | public boolean enterASSIGN_SHR(final BinaryNode binaryNode) { |
jlaskey@3 | 2654 | new AssignOp(Type.INT, binaryNode) { |
jlaskey@3 | 2655 | @Override |
jlaskey@3 | 2656 | protected void op() { |
jlaskey@3 | 2657 | method.shr(); |
hannesw@219 | 2658 | method.convert(Type.LONG).load(JSType.MAX_UINT).and(); |
jlaskey@3 | 2659 | } |
jlaskey@3 | 2660 | }.store(); |
jlaskey@3 | 2661 | |
lagergren@211 | 2662 | return false; |
jlaskey@3 | 2663 | } |
jlaskey@3 | 2664 | |
jlaskey@3 | 2665 | @Override |
lagergren@211 | 2666 | public boolean enterASSIGN_SUB(final BinaryNode binaryNode) { |
jlaskey@3 | 2667 | new AssignOp(binaryNode) { |
jlaskey@3 | 2668 | @Override |
jlaskey@3 | 2669 | protected void op() { |
jlaskey@3 | 2670 | method.sub(); |
jlaskey@3 | 2671 | } |
jlaskey@3 | 2672 | }.store(); |
jlaskey@3 | 2673 | |
lagergren@211 | 2674 | return false; |
jlaskey@3 | 2675 | } |
jlaskey@3 | 2676 | |
jlaskey@3 | 2677 | /** |
jlaskey@3 | 2678 | * Helper class for binary arithmetic ops |
jlaskey@3 | 2679 | */ |
jlaskey@3 | 2680 | private abstract class BinaryArith { |
jlaskey@3 | 2681 | |
jlaskey@3 | 2682 | protected abstract void op(); |
jlaskey@3 | 2683 | |
jlaskey@3 | 2684 | protected void evaluate(final BinaryNode node) { |
lagergren@605 | 2685 | loadBinaryOperands(node); |
jlaskey@3 | 2686 | op(); |
jlaskey@3 | 2687 | method.store(node.getSymbol()); |
jlaskey@3 | 2688 | } |
jlaskey@3 | 2689 | } |
jlaskey@3 | 2690 | |
jlaskey@3 | 2691 | @Override |
lagergren@211 | 2692 | public boolean enterBIT_AND(final BinaryNode binaryNode) { |
jlaskey@3 | 2693 | new BinaryArith() { |
jlaskey@3 | 2694 | @Override |
jlaskey@3 | 2695 | protected void op() { |
jlaskey@3 | 2696 | method.and(); |
jlaskey@3 | 2697 | } |
jlaskey@3 | 2698 | }.evaluate(binaryNode); |
jlaskey@3 | 2699 | |
lagergren@211 | 2700 | return false; |
jlaskey@3 | 2701 | } |
jlaskey@3 | 2702 | |
jlaskey@3 | 2703 | @Override |
lagergren@211 | 2704 | public boolean enterBIT_OR(final BinaryNode binaryNode) { |
jlaskey@3 | 2705 | new BinaryArith() { |
jlaskey@3 | 2706 | @Override |
jlaskey@3 | 2707 | protected void op() { |
jlaskey@3 | 2708 | method.or(); |
jlaskey@3 | 2709 | } |
jlaskey@3 | 2710 | }.evaluate(binaryNode); |
jlaskey@3 | 2711 | |
lagergren@211 | 2712 | return false; |
jlaskey@3 | 2713 | } |
jlaskey@3 | 2714 | |
jlaskey@3 | 2715 | @Override |
lagergren@211 | 2716 | public boolean enterBIT_XOR(final BinaryNode binaryNode) { |
jlaskey@3 | 2717 | new BinaryArith() { |
jlaskey@3 | 2718 | @Override |
jlaskey@3 | 2719 | protected void op() { |
jlaskey@3 | 2720 | method.xor(); |
jlaskey@3 | 2721 | } |
jlaskey@3 | 2722 | }.evaluate(binaryNode); |
jlaskey@3 | 2723 | |
lagergren@211 | 2724 | return false; |
jlaskey@3 | 2725 | } |
jlaskey@3 | 2726 | |
lagergren@211 | 2727 | private boolean enterComma(final BinaryNode binaryNode) { |
attila@430 | 2728 | final Expression lhs = binaryNode.lhs(); |
attila@430 | 2729 | final Expression rhs = binaryNode.rhs(); |
jlaskey@3 | 2730 | |
jlaskey@3 | 2731 | load(lhs); |
jlaskey@3 | 2732 | load(rhs); |
jlaskey@3 | 2733 | method.store(binaryNode.getSymbol()); |
jlaskey@3 | 2734 | |
lagergren@211 | 2735 | return false; |
jlaskey@3 | 2736 | } |
jlaskey@3 | 2737 | |
jlaskey@3 | 2738 | @Override |
lagergren@211 | 2739 | public boolean enterCOMMARIGHT(final BinaryNode binaryNode) { |
jlaskey@3 | 2740 | return enterComma(binaryNode); |
jlaskey@3 | 2741 | } |
jlaskey@3 | 2742 | |
jlaskey@3 | 2743 | @Override |
lagergren@211 | 2744 | public boolean enterCOMMALEFT(final BinaryNode binaryNode) { |
jlaskey@3 | 2745 | return enterComma(binaryNode); |
jlaskey@3 | 2746 | } |
jlaskey@3 | 2747 | |
jlaskey@3 | 2748 | @Override |
lagergren@211 | 2749 | public boolean enterDIV(final BinaryNode binaryNode) { |
jlaskey@3 | 2750 | new BinaryArith() { |
jlaskey@3 | 2751 | @Override |
jlaskey@3 | 2752 | protected void op() { |
jlaskey@3 | 2753 | method.div(); |
jlaskey@3 | 2754 | } |
jlaskey@3 | 2755 | }.evaluate(binaryNode); |
jlaskey@3 | 2756 | |
lagergren@211 | 2757 | return false; |
jlaskey@3 | 2758 | } |
jlaskey@3 | 2759 | |
attila@430 | 2760 | private boolean enterCmp(final Expression lhs, final Expression rhs, final Condition cond, final Type type, final Symbol symbol) { |
jlaskey@3 | 2761 | final Type lhsType = lhs.getType(); |
jlaskey@3 | 2762 | final Type rhsType = rhs.getType(); |
jlaskey@3 | 2763 | |
jlaskey@3 | 2764 | final Type widest = Type.widest(lhsType, rhsType); |
jlaskey@3 | 2765 | assert widest.isNumeric() || widest.isBoolean() : widest; |
jlaskey@3 | 2766 | |
lagergren@605 | 2767 | loadBinaryOperands(lhs, rhs, widest); |
jlaskey@3 | 2768 | final Label trueLabel = new Label("trueLabel"); |
jlaskey@3 | 2769 | final Label afterLabel = new Label("skip"); |
jlaskey@3 | 2770 | |
jlaskey@3 | 2771 | method.conditionalJump(cond, trueLabel); |
jlaskey@3 | 2772 | |
jlaskey@3 | 2773 | method.load(Boolean.FALSE); |
jlaskey@3 | 2774 | method._goto(afterLabel); |
jlaskey@3 | 2775 | method.label(trueLabel); |
jlaskey@3 | 2776 | method.load(Boolean.TRUE); |
jlaskey@3 | 2777 | method.label(afterLabel); |
jlaskey@3 | 2778 | |
jlaskey@3 | 2779 | method.convert(type); |
jlaskey@3 | 2780 | method.store(symbol); |
jlaskey@3 | 2781 | |
lagergren@211 | 2782 | return false; |
jlaskey@3 | 2783 | } |
jlaskey@3 | 2784 | |
lagergren@211 | 2785 | private boolean enterCmp(final BinaryNode binaryNode, final Condition cond) { |
jlaskey@3 | 2786 | return enterCmp(binaryNode.lhs(), binaryNode.rhs(), cond, binaryNode.getType(), binaryNode.getSymbol()); |
jlaskey@3 | 2787 | } |
jlaskey@3 | 2788 | |
jlaskey@3 | 2789 | @Override |
lagergren@211 | 2790 | public boolean enterEQ(final BinaryNode binaryNode) { |
jlaskey@3 | 2791 | return enterCmp(binaryNode, Condition.EQ); |
jlaskey@3 | 2792 | } |
jlaskey@3 | 2793 | |
jlaskey@3 | 2794 | @Override |
lagergren@211 | 2795 | public boolean enterEQ_STRICT(final BinaryNode binaryNode) { |
jlaskey@3 | 2796 | return enterCmp(binaryNode, Condition.EQ); |
jlaskey@3 | 2797 | } |
jlaskey@3 | 2798 | |
jlaskey@3 | 2799 | @Override |
lagergren@211 | 2800 | public boolean enterGE(final BinaryNode binaryNode) { |
jlaskey@3 | 2801 | return enterCmp(binaryNode, Condition.GE); |
jlaskey@3 | 2802 | } |
jlaskey@3 | 2803 | |
jlaskey@3 | 2804 | @Override |
lagergren@211 | 2805 | public boolean enterGT(final BinaryNode binaryNode) { |
jlaskey@3 | 2806 | return enterCmp(binaryNode, Condition.GT); |
jlaskey@3 | 2807 | } |
jlaskey@3 | 2808 | |
jlaskey@3 | 2809 | @Override |
lagergren@211 | 2810 | public boolean enterLE(final BinaryNode binaryNode) { |
jlaskey@3 | 2811 | return enterCmp(binaryNode, Condition.LE); |
jlaskey@3 | 2812 | } |
jlaskey@3 | 2813 | |
jlaskey@3 | 2814 | @Override |
lagergren@211 | 2815 | public boolean enterLT(final BinaryNode binaryNode) { |
jlaskey@3 | 2816 | return enterCmp(binaryNode, Condition.LT); |
jlaskey@3 | 2817 | } |
jlaskey@3 | 2818 | |
jlaskey@3 | 2819 | @Override |
lagergren@211 | 2820 | public boolean enterMOD(final BinaryNode binaryNode) { |
jlaskey@3 | 2821 | new BinaryArith() { |
jlaskey@3 | 2822 | @Override |
jlaskey@3 | 2823 | protected void op() { |
jlaskey@3 | 2824 | method.rem(); |
jlaskey@3 | 2825 | } |
jlaskey@3 | 2826 | }.evaluate(binaryNode); |
jlaskey@3 | 2827 | |
lagergren@211 | 2828 | return false; |
jlaskey@3 | 2829 | } |
jlaskey@3 | 2830 | |
jlaskey@3 | 2831 | @Override |
lagergren@211 | 2832 | public boolean enterMUL(final BinaryNode binaryNode) { |
jlaskey@3 | 2833 | new BinaryArith() { |
jlaskey@3 | 2834 | @Override |
jlaskey@3 | 2835 | protected void op() { |
jlaskey@3 | 2836 | method.mul(); |
jlaskey@3 | 2837 | } |
jlaskey@3 | 2838 | }.evaluate(binaryNode); |
jlaskey@3 | 2839 | |
lagergren@211 | 2840 | return false; |
jlaskey@3 | 2841 | } |
jlaskey@3 | 2842 | |
jlaskey@3 | 2843 | @Override |
lagergren@211 | 2844 | public boolean enterNE(final BinaryNode binaryNode) { |
jlaskey@3 | 2845 | return enterCmp(binaryNode, Condition.NE); |
jlaskey@3 | 2846 | } |
jlaskey@3 | 2847 | |
jlaskey@3 | 2848 | @Override |
lagergren@211 | 2849 | public boolean enterNE_STRICT(final BinaryNode binaryNode) { |
jlaskey@3 | 2850 | return enterCmp(binaryNode, Condition.NE); |
jlaskey@3 | 2851 | } |
jlaskey@3 | 2852 | |
jlaskey@3 | 2853 | @Override |
lagergren@211 | 2854 | public boolean enterOR(final BinaryNode binaryNode) { |
jlaskey@3 | 2855 | return enterAND_OR(binaryNode); |
jlaskey@3 | 2856 | } |
jlaskey@3 | 2857 | |
jlaskey@3 | 2858 | @Override |
lagergren@211 | 2859 | public boolean enterSAR(final BinaryNode binaryNode) { |
jlaskey@3 | 2860 | new BinaryArith() { |
jlaskey@3 | 2861 | @Override |
jlaskey@3 | 2862 | protected void op() { |
jlaskey@3 | 2863 | method.sar(); |
jlaskey@3 | 2864 | } |
jlaskey@3 | 2865 | }.evaluate(binaryNode); |
jlaskey@3 | 2866 | |
lagergren@211 | 2867 | return false; |
jlaskey@3 | 2868 | } |
jlaskey@3 | 2869 | |
jlaskey@3 | 2870 | @Override |
lagergren@211 | 2871 | public boolean enterSHL(final BinaryNode binaryNode) { |
jlaskey@3 | 2872 | new BinaryArith() { |
jlaskey@3 | 2873 | @Override |
jlaskey@3 | 2874 | protected void op() { |
jlaskey@3 | 2875 | method.shl(); |
jlaskey@3 | 2876 | } |
jlaskey@3 | 2877 | }.evaluate(binaryNode); |
jlaskey@3 | 2878 | |
lagergren@211 | 2879 | return false; |
jlaskey@3 | 2880 | } |
jlaskey@3 | 2881 | |
jlaskey@3 | 2882 | @Override |
lagergren@211 | 2883 | public boolean enterSHR(final BinaryNode binaryNode) { |
jlaskey@3 | 2884 | new BinaryArith() { |
jlaskey@3 | 2885 | @Override |
lagergren@605 | 2886 | protected void evaluate(final BinaryNode node) { |
lagergren@605 | 2887 | loadBinaryOperands(node.lhs(), node.rhs(), Type.INT); |
lagergren@605 | 2888 | op(); |
lagergren@605 | 2889 | method.store(node.getSymbol()); |
lagergren@605 | 2890 | } |
lagergren@605 | 2891 | @Override |
jlaskey@3 | 2892 | protected void op() { |
jlaskey@3 | 2893 | method.shr(); |
hannesw@219 | 2894 | method.convert(Type.LONG).load(JSType.MAX_UINT).and(); |
jlaskey@3 | 2895 | } |
jlaskey@3 | 2896 | }.evaluate(binaryNode); |
jlaskey@3 | 2897 | |
lagergren@211 | 2898 | return false; |
jlaskey@3 | 2899 | } |
jlaskey@3 | 2900 | |
jlaskey@3 | 2901 | @Override |
lagergren@211 | 2902 | public boolean enterSUB(final BinaryNode binaryNode) { |
jlaskey@3 | 2903 | new BinaryArith() { |
jlaskey@3 | 2904 | @Override |
jlaskey@3 | 2905 | protected void op() { |
jlaskey@3 | 2906 | method.sub(); |
jlaskey@3 | 2907 | } |
jlaskey@3 | 2908 | }.evaluate(binaryNode); |
jlaskey@3 | 2909 | |
lagergren@211 | 2910 | return false; |
jlaskey@3 | 2911 | } |
jlaskey@3 | 2912 | |
jlaskey@3 | 2913 | @Override |
lagergren@211 | 2914 | public boolean enterTernaryNode(final TernaryNode ternaryNode) { |
attila@430 | 2915 | final Expression test = ternaryNode.getTest(); |
attila@430 | 2916 | final Expression trueExpr = ternaryNode.getTrueExpression(); |
attila@430 | 2917 | final Expression falseExpr = ternaryNode.getFalseExpression(); |
jlaskey@3 | 2918 | |
jlaskey@3 | 2919 | final Symbol symbol = ternaryNode.getSymbol(); |
jlaskey@3 | 2920 | final Label falseLabel = new Label("ternary_false"); |
jlaskey@3 | 2921 | final Label exitLabel = new Label("ternary_exit"); |
jlaskey@3 | 2922 | |
sundar@602 | 2923 | Type widest = Type.widest(ternaryNode.getType(), Type.widest(trueExpr.getType(), falseExpr.getType())); |
attila@430 | 2924 | if (trueExpr.getType().isArray() || falseExpr.getType().isArray()) { //loadArray creates a Java array type on the stack, calls global allocate, which creates a native array type |
lagergren@66 | 2925 | widest = Type.OBJECT; |
lagergren@66 | 2926 | } |
jlaskey@3 | 2927 | |
lagergren@605 | 2928 | load(test, Type.BOOLEAN); |
jlaskey@3 | 2929 | // we still keep the conversion here as the AccessSpecializer can have separated the types, e.g. var y = x ? x=55 : 17 |
jlaskey@3 | 2930 | // will left as (Object)x=55 : (Object)17 by Lower. Then the first term can be {I}x=55 of type int, which breaks the |
jlaskey@3 | 2931 | // symmetry for the temporary slot for this TernaryNode. This is evidence that we assign types and explicit conversions |
sundar@602 | 2932 | // too early, or Apply the AccessSpecializer too late. We are mostly probably looking for a separate type pass to |
jlaskey@3 | 2933 | // do this property. Then we never need any conversions in CodeGenerator |
jlaskey@3 | 2934 | method.ifeq(falseLabel); |
lagergren@605 | 2935 | load(trueExpr, widest); |
jlaskey@3 | 2936 | method._goto(exitLabel); |
jlaskey@3 | 2937 | method.label(falseLabel); |
lagergren@605 | 2938 | load(falseExpr, widest); |
jlaskey@3 | 2939 | method.label(exitLabel); |
jlaskey@3 | 2940 | method.store(symbol); |
jlaskey@3 | 2941 | |
lagergren@211 | 2942 | return false; |
jlaskey@3 | 2943 | } |
jlaskey@3 | 2944 | |
jlaskey@3 | 2945 | /** |
jlaskey@3 | 2946 | * Generate all shared scope calls generated during codegen. |
jlaskey@3 | 2947 | */ |
jlaskey@3 | 2948 | protected void generateScopeCalls() { |
lagergren@290 | 2949 | for (final SharedScopeCall scopeAccess : lc.getScopeCalls()) { |
jlaskey@3 | 2950 | scopeAccess.generateScopeCall(); |
jlaskey@3 | 2951 | } |
jlaskey@3 | 2952 | } |
jlaskey@3 | 2953 | |
jlaskey@3 | 2954 | /** |
jlaskey@3 | 2955 | * Debug code used to print symbols |
jlaskey@3 | 2956 | * |
jlaskey@3 | 2957 | * @param block the block we are in |
jlaskey@3 | 2958 | * @param ident identifier for block or function where applicable |
jlaskey@3 | 2959 | */ |
lagergren@253 | 2960 | @SuppressWarnings("resource") |
jlaskey@3 | 2961 | private void printSymbols(final Block block, final String ident) { |
sundar@118 | 2962 | if (!compiler.getEnv()._print_symbols) { |
jlaskey@3 | 2963 | return; |
jlaskey@3 | 2964 | } |
jlaskey@3 | 2965 | |
sundar@118 | 2966 | final PrintWriter out = compiler.getEnv().getErr(); |
jlaskey@3 | 2967 | out.println("[BLOCK in '" + ident + "']"); |
jlaskey@3 | 2968 | if (!block.printSymbols(out)) { |
jlaskey@3 | 2969 | out.println("<no symbols>"); |
jlaskey@3 | 2970 | } |
jlaskey@3 | 2971 | out.println(); |
jlaskey@3 | 2972 | } |
jlaskey@3 | 2973 | |
jlaskey@3 | 2974 | |
jlaskey@3 | 2975 | /** |
jlaskey@3 | 2976 | * The difference between a store and a self modifying store is that |
jlaskey@3 | 2977 | * the latter may load part of the target on the stack, e.g. the base |
jlaskey@3 | 2978 | * of an AccessNode or the base and index of an IndexNode. These are used |
jlaskey@3 | 2979 | * both as target and as an extra source. Previously it was problematic |
jlaskey@3 | 2980 | * for self modifying stores if the target/lhs didn't belong to one |
jlaskey@3 | 2981 | * of three trivial categories: IdentNode, AcessNodes, IndexNodes. In that |
jlaskey@3 | 2982 | * case it was evaluated and tagged as "resolved", which meant at the second |
jlaskey@3 | 2983 | * time the lhs of this store was read (e.g. in a = a (second) + b for a += b, |
jlaskey@3 | 2984 | * it would be evaluated to a nop in the scope and cause stack underflow |
jlaskey@3 | 2985 | * |
jlaskey@3 | 2986 | * see NASHORN-703 |
jlaskey@3 | 2987 | * |
jlaskey@3 | 2988 | * @param <T> |
jlaskey@3 | 2989 | */ |
attila@430 | 2990 | private abstract class SelfModifyingStore<T extends Expression> extends Store<T> { |
attila@430 | 2991 | protected SelfModifyingStore(final T assignNode, final Expression target) { |
jlaskey@3 | 2992 | super(assignNode, target); |
jlaskey@3 | 2993 | } |
jlaskey@3 | 2994 | |
jlaskey@3 | 2995 | @Override |
jlaskey@3 | 2996 | protected boolean isSelfModifying() { |
jlaskey@3 | 2997 | return true; |
jlaskey@3 | 2998 | } |
jlaskey@3 | 2999 | } |
jlaskey@3 | 3000 | |
jlaskey@3 | 3001 | /** |
jlaskey@3 | 3002 | * Helper class to generate stores |
jlaskey@3 | 3003 | */ |
attila@430 | 3004 | private abstract class Store<T extends Expression> { |
jlaskey@3 | 3005 | |
jlaskey@3 | 3006 | /** An assignment node, e.g. x += y */ |
jlaskey@3 | 3007 | protected final T assignNode; |
jlaskey@3 | 3008 | |
jlaskey@3 | 3009 | /** The target node to store to, e.g. x */ |
attila@430 | 3010 | private final Expression target; |
jlaskey@3 | 3011 | |
jlaskey@3 | 3012 | /** How deep on the stack do the arguments go if this generates an indy call */ |
jlaskey@3 | 3013 | private int depth; |
jlaskey@3 | 3014 | |
jlaskey@3 | 3015 | /** If we have too many arguments, we need temporary storage, this is stored in 'quick' */ |
jlaskey@3 | 3016 | private Symbol quick; |
jlaskey@3 | 3017 | |
jlaskey@3 | 3018 | /** |
jlaskey@3 | 3019 | * Constructor |
jlaskey@3 | 3020 | * |
jlaskey@3 | 3021 | * @param assignNode the node representing the whole assignment |
jlaskey@3 | 3022 | * @param target the target node of the assignment (destination) |
jlaskey@3 | 3023 | */ |
attila@430 | 3024 | protected Store(final T assignNode, final Expression target) { |
jlaskey@3 | 3025 | this.assignNode = assignNode; |
jlaskey@3 | 3026 | this.target = target; |
jlaskey@3 | 3027 | } |
jlaskey@3 | 3028 | |
jlaskey@3 | 3029 | /** |
jlaskey@3 | 3030 | * Constructor |
jlaskey@3 | 3031 | * |
jlaskey@3 | 3032 | * @param assignNode the node representing the whole assignment |
jlaskey@3 | 3033 | */ |
jlaskey@3 | 3034 | protected Store(final T assignNode) { |
jlaskey@3 | 3035 | this(assignNode, assignNode); |
jlaskey@3 | 3036 | } |
jlaskey@3 | 3037 | |
jlaskey@3 | 3038 | /** |
jlaskey@3 | 3039 | * Is this a self modifying store operation, e.g. *= or ++ |
jlaskey@3 | 3040 | * @return true if self modifying store |
jlaskey@3 | 3041 | */ |
jlaskey@3 | 3042 | protected boolean isSelfModifying() { |
jlaskey@3 | 3043 | return false; |
jlaskey@3 | 3044 | } |
jlaskey@3 | 3045 | |
jlaskey@3 | 3046 | private void prologue() { |
jlaskey@3 | 3047 | final Symbol targetSymbol = target.getSymbol(); |
lagergren@290 | 3048 | final Symbol scopeSymbol = lc.getCurrentFunction().compilerConstant(SCOPE); |
jlaskey@3 | 3049 | |
jlaskey@3 | 3050 | /** |
jlaskey@3 | 3051 | * This loads the parts of the target, e.g base and index. they are kept |
jlaskey@3 | 3052 | * on the stack throughout the store and used at the end to execute it |
jlaskey@3 | 3053 | */ |
jlaskey@3 | 3054 | |
lagergren@290 | 3055 | target.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { |
jlaskey@3 | 3056 | @Override |
lagergren@211 | 3057 | public boolean enterIdentNode(final IdentNode node) { |
jlaskey@3 | 3058 | if (targetSymbol.isScope()) { |
jlaskey@3 | 3059 | method.load(scopeSymbol); |
jlaskey@3 | 3060 | depth++; |
jlaskey@3 | 3061 | } |
lagergren@211 | 3062 | return false; |
jlaskey@3 | 3063 | } |
jlaskey@3 | 3064 | |
jlaskey@3 | 3065 | private void enterBaseNode() { |
jlaskey@3 | 3066 | assert target instanceof BaseNode : "error - base node " + target + " must be instanceof BaseNode"; |
attila@430 | 3067 | final BaseNode baseNode = (BaseNode)target; |
attila@430 | 3068 | final Expression base = baseNode.getBase(); |
jlaskey@3 | 3069 | |
lagergren@605 | 3070 | load(base, Type.OBJECT); |
jlaskey@3 | 3071 | depth += Type.OBJECT.getSlots(); |
jlaskey@3 | 3072 | |
jlaskey@3 | 3073 | if (isSelfModifying()) { |
jlaskey@3 | 3074 | method.dup(); |
jlaskey@3 | 3075 | } |
jlaskey@3 | 3076 | } |
jlaskey@3 | 3077 | |
jlaskey@3 | 3078 | @Override |
lagergren@211 | 3079 | public boolean enterAccessNode(final AccessNode node) { |
jlaskey@3 | 3080 | enterBaseNode(); |
lagergren@211 | 3081 | return false; |
jlaskey@3 | 3082 | } |
jlaskey@3 | 3083 | |
jlaskey@3 | 3084 | @Override |
lagergren@211 | 3085 | public boolean enterIndexNode(final IndexNode node) { |
jlaskey@3 | 3086 | enterBaseNode(); |
jlaskey@3 | 3087 | |
attila@430 | 3088 | final Expression index = node.getIndex(); |
jlaskey@3 | 3089 | if (!index.getType().isNumeric()) { |
lagergren@605 | 3090 | // could be boolean here as well |
lagergren@605 | 3091 | load(index, Type.OBJECT); |
lagergren@605 | 3092 | } else { |
lagergren@605 | 3093 | load(index); |
jlaskey@3 | 3094 | } |
jlaskey@3 | 3095 | depth += index.getType().getSlots(); |
jlaskey@3 | 3096 | |
jlaskey@3 | 3097 | if (isSelfModifying()) { |
jlaskey@3 | 3098 | //convert "base base index" to "base index base index" |
jlaskey@3 | 3099 | method.dup(1); |
jlaskey@3 | 3100 | } |
jlaskey@3 | 3101 | |
lagergren@211 | 3102 | return false; |
jlaskey@3 | 3103 | } |
jlaskey@3 | 3104 | |
jlaskey@3 | 3105 | }); |
jlaskey@3 | 3106 | } |
jlaskey@3 | 3107 | |
jlaskey@3 | 3108 | private Symbol quickSymbol(final Type type) { |
lagergren@211 | 3109 | return quickSymbol(type, QUICK_PREFIX.symbolName()); |
jlaskey@3 | 3110 | } |
jlaskey@3 | 3111 | |
jlaskey@3 | 3112 | /** |
jlaskey@3 | 3113 | * Quick symbol generates an extra local variable, always using the same |
jlaskey@3 | 3114 | * slot, one that is available after the end of the frame. |
jlaskey@3 | 3115 | * |
jlaskey@3 | 3116 | * @param type the type of the symbol |
jlaskey@3 | 3117 | * @param prefix the prefix for the variable name for the symbol |
jlaskey@3 | 3118 | * |
jlaskey@3 | 3119 | * @return the quick symbol |
jlaskey@3 | 3120 | */ |
jlaskey@3 | 3121 | private Symbol quickSymbol(final Type type, final String prefix) { |
lagergren@290 | 3122 | final String name = lc.getCurrentFunction().uniqueName(prefix); |
lagergren@211 | 3123 | final Symbol symbol = new Symbol(name, IS_TEMP | IS_INTERNAL); |
jlaskey@3 | 3124 | |
jlaskey@3 | 3125 | symbol.setType(type); |
lagergren@290 | 3126 | |
lagergren@290 | 3127 | symbol.setSlot(lc.quickSlot(symbol)); |
jlaskey@3 | 3128 | |
jlaskey@3 | 3129 | return symbol; |
jlaskey@3 | 3130 | } |
jlaskey@3 | 3131 | |
jlaskey@3 | 3132 | // store the result that "lives on" after the op, e.g. "i" in i++ postfix. |
jlaskey@3 | 3133 | protected void storeNonDiscard() { |
lagergren@290 | 3134 | if (lc.getCurrentDiscard() == assignNode) { |
lagergren@211 | 3135 | assert assignNode.isAssignment(); |
lagergren@290 | 3136 | lc.popDiscard(); |
jlaskey@3 | 3137 | return; |
jlaskey@3 | 3138 | } |
jlaskey@3 | 3139 | |
jlaskey@3 | 3140 | final Symbol symbol = assignNode.getSymbol(); |
jlaskey@3 | 3141 | if (symbol.hasSlot()) { |
jlaskey@3 | 3142 | method.dup().store(symbol); |
jlaskey@3 | 3143 | return; |
jlaskey@3 | 3144 | } |
jlaskey@3 | 3145 | |
jlaskey@3 | 3146 | if (method.dup(depth) == null) { |
jlaskey@3 | 3147 | method.dup(); |
jlaskey@3 | 3148 | this.quick = quickSymbol(method.peekType()); |
jlaskey@3 | 3149 | method.store(quick); |
jlaskey@3 | 3150 | } |
jlaskey@3 | 3151 | } |
jlaskey@3 | 3152 | |
jlaskey@3 | 3153 | private void epilogue() { |
jlaskey@3 | 3154 | /** |
jlaskey@3 | 3155 | * Take the original target args from the stack and use them |
jlaskey@3 | 3156 | * together with the value to be stored to emit the store code |
lagergren@57 | 3157 | * |
lagergren@57 | 3158 | * The case that targetSymbol is in scope (!hasSlot) and we actually |
lagergren@57 | 3159 | * need to do a conversion on non-equivalent types exists, but is |
lagergren@57 | 3160 | * very rare. See for example test/script/basic/access-specializer.js |
jlaskey@3 | 3161 | */ |
lagergren@290 | 3162 | target.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { |
jlaskey@3 | 3163 | @Override |
lagergren@211 | 3164 | protected boolean enterDefault(Node node) { |
attila@62 | 3165 | throw new AssertionError("Unexpected node " + node + " in store epilogue"); |
lagergren@66 | 3166 | } |
attila@62 | 3167 | |
attila@62 | 3168 | @Override |
lagergren@211 | 3169 | public boolean enterIdentNode(final IdentNode node) { |
attila@62 | 3170 | final Symbol symbol = node.getSymbol(); |
sundar@82 | 3171 | assert symbol != null; |
jlaskey@3 | 3172 | if (symbol.isScope()) { |
attila@144 | 3173 | if (isFastScope(symbol)) { |
lagergren@605 | 3174 | storeFastScopeVar(symbol, CALLSITE_SCOPE | getCallSiteFlags()); |
jlaskey@3 | 3175 | } else { |
lagergren@605 | 3176 | method.dynamicSet(node.getName(), CALLSITE_SCOPE | getCallSiteFlags()); |
jlaskey@3 | 3177 | } |
jlaskey@3 | 3178 | } else { |
lagergren@605 | 3179 | method.convert(node.getType()); |
attila@65 | 3180 | method.store(symbol); |
jlaskey@3 | 3181 | } |
lagergren@211 | 3182 | return false; |
jlaskey@3 | 3183 | |
jlaskey@3 | 3184 | } |
jlaskey@3 | 3185 | |
jlaskey@3 | 3186 | @Override |
lagergren@211 | 3187 | public boolean enterAccessNode(final AccessNode node) { |
lagergren@605 | 3188 | method.dynamicSet(node.getProperty().getName(), getCallSiteFlags()); |
lagergren@211 | 3189 | return false; |
jlaskey@3 | 3190 | } |
jlaskey@3 | 3191 | |
jlaskey@3 | 3192 | @Override |
lagergren@211 | 3193 | public boolean enterIndexNode(final IndexNode node) { |
jlaskey@3 | 3194 | method.dynamicSetIndex(getCallSiteFlags()); |
lagergren@211 | 3195 | return false; |
jlaskey@3 | 3196 | } |
jlaskey@3 | 3197 | }); |
jlaskey@3 | 3198 | |
jlaskey@3 | 3199 | |
jlaskey@3 | 3200 | // whatever is on the stack now is the final answer |
jlaskey@3 | 3201 | } |
jlaskey@3 | 3202 | |
jlaskey@3 | 3203 | protected abstract void evaluate(); |
jlaskey@3 | 3204 | |
jlaskey@3 | 3205 | void store() { |
jlaskey@3 | 3206 | prologue(); |
jlaskey@3 | 3207 | evaluate(); // leaves an operation of whatever the operationType was on the stack |
jlaskey@3 | 3208 | storeNonDiscard(); |
jlaskey@3 | 3209 | epilogue(); |
jlaskey@3 | 3210 | if (quick != null) { |
jlaskey@3 | 3211 | method.load(quick); |
jlaskey@3 | 3212 | } |
jlaskey@3 | 3213 | } |
jlaskey@3 | 3214 | } |
jlaskey@3 | 3215 | |
lagergren@211 | 3216 | private void newFunctionObject(final FunctionNode functionNode, final FunctionNode originalFunctionNode) { |
lagergren@211 | 3217 | assert lc.peek() == functionNode; |
lagergren@211 | 3218 | // We don't emit a ScriptFunction on stack for: |
lagergren@211 | 3219 | // 1. the outermost compiled function (as there's no code being generated in its outer context that'd need it |
lagergren@211 | 3220 | // as a callee), and |
lagergren@211 | 3221 | // 2. for functions that are immediately called upon definition and they don't need a callee, e.g. (function(){})(). |
lagergren@211 | 3222 | // Such immediately-called functions are invoked using INVOKESTATIC (see enterFunctionNode() of the embedded |
lagergren@211 | 3223 | // visitor of enterCallNode() for details), and if they don't need a callee, they don't have it on their |
lagergren@211 | 3224 | // static method's parameter list. |
lagergren@253 | 3225 | if (lc.getOutermostFunction() == functionNode || |
lagergren@211 | 3226 | (!functionNode.needsCallee()) && lc.isFunctionDefinedInCurrentCall(originalFunctionNode)) { |
lagergren@211 | 3227 | return; |
lagergren@211 | 3228 | } |
lagergren@211 | 3229 | |
hannesw@415 | 3230 | // Generate the object class and property map in case this function is ever used as constructor |
hannesw@415 | 3231 | final String className = SCRIPTFUNCTION_IMPL_OBJECT; |
hannesw@415 | 3232 | final int fieldCount = ObjectClassGenerator.getPaddedFieldCount(functionNode.countThisProperties()); |
hannesw@415 | 3233 | final String allocatorClassName = Compiler.binaryName(ObjectClassGenerator.getClassName(fieldCount)); |
hannesw@828 | 3234 | final PropertyMap allocatorMap = PropertyMap.newMap(null, allocatorClassName, 0, fieldCount, 0); |
hannesw@415 | 3235 | |
hannesw@415 | 3236 | method._new(className).dup(); |
hannesw@415 | 3237 | loadConstant(new RecompilableScriptFunctionData(functionNode, compiler.getCodeInstaller(), allocatorClassName, allocatorMap)); |
hannesw@415 | 3238 | |
hannesw@415 | 3239 | if (functionNode.isLazy() || functionNode.needsParentScope()) { |
hannesw@415 | 3240 | method.loadCompilerConstant(SCOPE); |
hannesw@415 | 3241 | } else { |
hannesw@415 | 3242 | method.loadNull(); |
hannesw@415 | 3243 | } |
hannesw@415 | 3244 | method.invoke(constructorNoLookup(className, RecompilableScriptFunctionData.class, ScriptObject.class)); |
lagergren@89 | 3245 | } |
lagergren@89 | 3246 | |
sundar@414 | 3247 | // calls on Global class. |
jlaskey@3 | 3248 | private MethodEmitter globalInstance() { |
lagergren@96 | 3249 | return method.invokestatic(GLOBAL_OBJECT, "instance", "()L" + GLOBAL_OBJECT + ';'); |
jlaskey@3 | 3250 | } |
jlaskey@3 | 3251 | |
jlaskey@3 | 3252 | private MethodEmitter globalObjectPrototype() { |
lagergren@96 | 3253 | return method.invokestatic(GLOBAL_OBJECT, "objectPrototype", methodDescriptor(ScriptObject.class)); |
jlaskey@3 | 3254 | } |
jlaskey@3 | 3255 | |
jlaskey@3 | 3256 | private MethodEmitter globalAllocateArguments() { |
lagergren@96 | 3257 | return method.invokestatic(GLOBAL_OBJECT, "allocateArguments", methodDescriptor(ScriptObject.class, Object[].class, Object.class, int.class)); |
jlaskey@3 | 3258 | } |
jlaskey@3 | 3259 | |
jlaskey@3 | 3260 | private MethodEmitter globalNewRegExp() { |
lagergren@96 | 3261 | return method.invokestatic(GLOBAL_OBJECT, "newRegExp", methodDescriptor(Object.class, String.class, String.class)); |
jlaskey@3 | 3262 | } |
jlaskey@3 | 3263 | |
jlaskey@3 | 3264 | private MethodEmitter globalRegExpCopy() { |
lagergren@96 | 3265 | return method.invokestatic(GLOBAL_OBJECT, "regExpCopy", methodDescriptor(Object.class, Object.class)); |
jlaskey@3 | 3266 | } |
jlaskey@3 | 3267 | |
jlaskey@3 | 3268 | private MethodEmitter globalAllocateArray(final ArrayType type) { |
jlaskey@3 | 3269 | //make sure the native array is treated as an array type |
lagergren@96 | 3270 | return method.invokestatic(GLOBAL_OBJECT, "allocate", "(" + type.getDescriptor() + ")Ljdk/nashorn/internal/objects/NativeArray;"); |
jlaskey@3 | 3271 | } |
jlaskey@3 | 3272 | |
jlaskey@3 | 3273 | private MethodEmitter globalIsEval() { |
lagergren@96 | 3274 | return method.invokestatic(GLOBAL_OBJECT, "isEval", methodDescriptor(boolean.class, Object.class)); |
jlaskey@3 | 3275 | } |
jlaskey@3 | 3276 | |
sundar@565 | 3277 | private MethodEmitter globalCheckObjectCoercible() { |
sundar@565 | 3278 | return method.invokestatic(GLOBAL_OBJECT, "checkObjectCoercible", methodDescriptor(void.class, Object.class)); |
sundar@565 | 3279 | } |
sundar@565 | 3280 | |
jlaskey@3 | 3281 | private MethodEmitter globalDirectEval() { |
lagergren@96 | 3282 | return method.invokestatic(GLOBAL_OBJECT, "directEval", |
jlaskey@3 | 3283 | methodDescriptor(Object.class, Object.class, Object.class, Object.class, Object.class, Object.class)); |
jlaskey@3 | 3284 | } |
jlaskey@3 | 3285 | } |