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