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 method._new(ECMAException.class).dup(); 2027 2028 final Source source = lc.getCurrentFunction().getSource(); 2029 2030 final Expression expression = throwNode.getExpression(); 2031 final int position = throwNode.position(); 2032 final int line = throwNode.getLineNumber(); 2033 final int column = source.getColumn(position); 2034 2035 load(expression, Type.OBJECT); 2036 2037 method.load(source.getName()); 2038 method.load(line); 2039 method.load(column); 2040 method.invoke(ECMAException.THROW_INIT); 2041 2042 method.athrow(); 2043 2044 return false; 2045 } 2046 2047 @Override 2048 public boolean enterTryNode(final TryNode tryNode) { 2049 lineNumber(tryNode); 2050 2051 final Block body = tryNode.getBody(); 2052 final List<Block> catchBlocks = tryNode.getCatchBlocks(); 2053 final Symbol symbol = tryNode.getException(); 2054 final Label entry = new Label("try"); 2055 final Label recovery = new Label("catch"); 2056 final Label exit = tryNode.getExit(); 2057 final Label skip = new Label("skip"); 2058 2059 method.label(entry); 2060 2061 body.accept(this); 2062 2063 if (!body.hasTerminalFlags()) { 2064 method._goto(skip); 2065 } 2066 2067 method.label(exit); 2068 2069 method._catch(recovery); 2070 method.store(symbol); 2071 2072 for (int i = 0; i < catchBlocks.size(); i++) { 2073 final Block catchBlock = catchBlocks.get(i); 2074 2075 //TODO this is very ugly - try not to call enter/leave methods directly 2076 //better to use the implicit lexical context scoping given by the visitor's 2077 //accept method. 2078 lc.push(catchBlock); 2079 enterBlock(catchBlock); 2080 2081 final CatchNode catchNode = (CatchNode)catchBlocks.get(i).getStatements().get(0); 2082 final IdentNode exception = catchNode.getException(); 2083 final Expression exceptionCondition = catchNode.getExceptionCondition(); 2084 final Block catchBody = catchNode.getBody(); 2085 2086 new Store<IdentNode>(exception) { 2087 @Override 2088 protected void storeNonDiscard() { 2089 return; 2090 } 2091 2092 @Override 2093 protected void evaluate() { 2094 if (catchNode.isSyntheticRethrow()) { 2095 method.load(symbol); 2096 return; 2097 } 2098 /* 2099 * If caught object is an instance of ECMAException, then 2100 * bind obj.thrown to the script catch var. Or else bind the 2101 * caught object itself to the script catch var. 2102 */ 2103 final Label notEcmaException = new Label("no_ecma_exception"); 2104 method.load(symbol).dup()._instanceof(ECMAException.class).ifeq(notEcmaException); 2105 method.checkcast(ECMAException.class); //TODO is this necessary? 2106 method.getField(ECMAException.THROWN); 2107 method.label(notEcmaException); 2108 } 2109 }.store(); 2110 2111 final Label next; 2112 2113 if (exceptionCondition != null) { 2114 next = new Label("next"); 2115 load(exceptionCondition, Type.BOOLEAN).ifeq(next); 2116 } else { 2117 next = null; 2118 } 2119 2120 catchBody.accept(this); 2121 2122 if (i + 1 != catchBlocks.size() && !catchBody.hasTerminalFlags()) { 2123 method._goto(skip); 2124 } 2125 2126 if (next != null) { 2127 if (i + 1 == catchBlocks.size()) { 2128 // no next catch block - rethrow if condition failed 2129 method._goto(skip); 2130 method.label(next); 2131 method.load(symbol).athrow(); 2132 } else { 2133 method.label(next); 2134 } 2135 } 2136 2137 leaveBlock(catchBlock); 2138 lc.pop(catchBlock); 2139 } 2140 2141 method.label(skip); 2142 method._try(entry, exit, recovery, Throwable.class); 2143 2144 // Finally body is always inlined elsewhere so it doesn't need to be emitted 2145 2146 return false; 2147 } 2148 2149 @Override 2150 public boolean enterVarNode(final VarNode varNode) { 2151 2152 final Expression init = varNode.getInit(); 2153 2154 if (init == null) { 2155 return false; 2156 } 2157 2158 lineNumber(varNode); 2159 2160 final IdentNode identNode = varNode.getName(); 2161 final Symbol identSymbol = identNode.getSymbol(); 2162 assert identSymbol != null : "variable node " + varNode + " requires a name with a symbol"; 2163 2164 assert method != null; 2165 2166 final boolean needsScope = identSymbol.isScope(); 2167 if (needsScope) { 2168 method.loadCompilerConstant(SCOPE); 2169 } 2170 2171 if (needsScope) { 2172 load(init); 2173 int flags = CALLSITE_SCOPE | getCallSiteFlags(); 2174 if (isFastScope(identSymbol)) { 2175 storeFastScopeVar(identSymbol, flags); 2176 } else { 2177 method.dynamicSet(identNode.getName(), flags); 2178 } 2179 } else { 2180 load(init, identNode.getType()); 2181 method.store(identSymbol); 2182 } 2183 2184 return false; 2185 } 2186 2187 @Override 2188 public boolean enterWhileNode(final WhileNode whileNode) { 2189 final Expression test = whileNode.getTest(); 2190 final Block body = whileNode.getBody(); 2191 final Label breakLabel = whileNode.getBreakLabel(); 2192 final Label continueLabel = whileNode.getContinueLabel(); 2193 final boolean isDoWhile = whileNode.isDoWhile(); 2194 final Label loopLabel = new Label("loop"); 2195 2196 if (!isDoWhile) { 2197 method._goto(continueLabel); 2198 } 2199 2200 method.label(loopLabel); 2201 body.accept(this); 2202 if (!whileNode.isTerminal()) { 2203 method.label(continueLabel); 2204 lineNumber(whileNode); 2205 new BranchOptimizer(this, method).execute(test, loopLabel, true); 2206 method.label(breakLabel); 2207 } 2208 2209 return false; 2210 } 2211 2212 private void closeWith() { 2213 if (method.hasScope()) { 2214 method.loadCompilerConstant(SCOPE); 2215 method.invoke(ScriptRuntime.CLOSE_WITH); 2216 method.storeCompilerConstant(SCOPE); 2217 } 2218 } 2219 2220 @Override 2221 public boolean enterWithNode(final WithNode withNode) { 2222 final Expression expression = withNode.getExpression(); 2223 final Node body = withNode.getBody(); 2224 2225 // It is possible to have a "pathological" case where the with block does not reference *any* identifiers. It's 2226 // pointless, but legal. In that case, if nothing else in the method forced the assignment of a slot to the 2227 // scope object, its' possible that it won't have a slot assigned. In this case we'll only evaluate expression 2228 // for its side effect and visit the body, and not bother opening and closing a WithObject. 2229 final boolean hasScope = method.hasScope(); 2230 2231 final Label tryLabel; 2232 if (hasScope) { 2233 tryLabel = new Label("with_try"); 2234 method.label(tryLabel); 2235 method.loadCompilerConstant(SCOPE); 2236 } else { 2237 tryLabel = null; 2238 } 2239 2240 load(expression, Type.OBJECT); 2241 2242 if (hasScope) { 2243 // Construct a WithObject if we have a scope 2244 method.invoke(ScriptRuntime.OPEN_WITH); 2245 method.storeCompilerConstant(SCOPE); 2246 } else { 2247 // We just loaded the expression for its side effect and to check 2248 // for null or undefined value. 2249 globalCheckObjectCoercible(); 2250 } 2251 2252 2253 // Always process body 2254 body.accept(this); 2255 2256 if (hasScope) { 2257 // Ensure we always close the WithObject 2258 final Label endLabel = new Label("with_end"); 2259 final Label catchLabel = new Label("with_catch"); 2260 final Label exitLabel = new Label("with_exit"); 2261 2262 if (!body.isTerminal()) { 2263 closeWith(); 2264 method._goto(exitLabel); 2265 } 2266 2267 method.label(endLabel); 2268 2269 method._catch(catchLabel); 2270 closeWith(); 2271 method.athrow(); 2272 2273 method.label(exitLabel); 2274 2275 method._try(tryLabel, endLabel, catchLabel); 2276 } 2277 return false; 2278 } 2279 2280 @Override 2281 public boolean enterADD(final UnaryNode unaryNode) { 2282 load(unaryNode.rhs(), unaryNode.getType()); 2283 assert unaryNode.getType().isNumeric(); 2284 method.store(unaryNode.getSymbol()); 2285 return false; 2286 } 2287 2288 @Override 2289 public boolean enterBIT_NOT(final UnaryNode unaryNode) { 2290 load(unaryNode.rhs(), Type.INT).load(-1).xor().store(unaryNode.getSymbol()); 2291 return false; 2292 } 2293 2294 @Override 2295 public boolean enterDECINC(final UnaryNode unaryNode) { 2296 final Expression rhs = unaryNode.rhs(); 2297 final Type type = unaryNode.getType(); 2298 final TokenType tokenType = unaryNode.tokenType(); 2299 final boolean isPostfix = tokenType == TokenType.DECPOSTFIX || tokenType == TokenType.INCPOSTFIX; 2300 final boolean isIncrement = tokenType == TokenType.INCPREFIX || tokenType == TokenType.INCPOSTFIX; 2301 2302 assert !type.isObject(); 2303 2304 new SelfModifyingStore<UnaryNode>(unaryNode, rhs) { 2305 2306 @Override 2307 protected void evaluate() { 2308 load(rhs, type, true); 2309 if (!isPostfix) { 2310 if (type.isInteger()) { 2311 method.load(isIncrement ? 1 : -1); 2312 } else if (type.isLong()) { 2313 method.load(isIncrement ? 1L : -1L); 2314 } else { 2315 method.load(isIncrement ? 1.0 : -1.0); 2316 } 2317 method.add(); 2318 } 2319 } 2320 2321 @Override 2322 protected void storeNonDiscard() { 2323 super.storeNonDiscard(); 2324 if (isPostfix) { 2325 if (type.isInteger()) { 2326 method.load(isIncrement ? 1 : -1); 2327 } else if (type.isLong()) { 2328 method.load(isIncrement ? 1L : 1L); 2329 } else { 2330 method.load(isIncrement ? 1.0 : -1.0); 2331 } 2332 method.add(); 2333 } 2334 } 2335 }.store(); 2336 2337 return false; 2338 } 2339 2340 @Override 2341 public boolean enterDISCARD(final UnaryNode unaryNode) { 2342 final Expression rhs = unaryNode.rhs(); 2343 2344 lc.pushDiscard(rhs); 2345 load(rhs); 2346 2347 if (lc.getCurrentDiscard() == rhs) { 2348 assert !rhs.isAssignment(); 2349 method.pop(); 2350 lc.popDiscard(); 2351 } 2352 2353 return false; 2354 } 2355 2356 @Override 2357 public boolean enterNEW(final UnaryNode unaryNode) { 2358 final CallNode callNode = (CallNode)unaryNode.rhs(); 2359 final List<Expression> args = callNode.getArgs(); 2360 2361 // Load function reference. 2362 load(callNode.getFunction(), Type.OBJECT); // must detect type error 2363 2364 method.dynamicNew(1 + loadArgs(args), getCallSiteFlags()); 2365 method.store(unaryNode.getSymbol()); 2366 2367 return false; 2368 } 2369 2370 @Override 2371 public boolean enterNOT(final UnaryNode unaryNode) { 2372 final Expression rhs = unaryNode.rhs(); 2373 2374 load(rhs, Type.BOOLEAN); 2375 2376 final Label trueLabel = new Label("true"); 2377 final Label afterLabel = new Label("after"); 2378 2379 method.ifne(trueLabel); 2380 method.load(true); 2381 method._goto(afterLabel); 2382 method.label(trueLabel); 2383 method.load(false); 2384 method.label(afterLabel); 2385 method.store(unaryNode.getSymbol()); 2386 2387 return false; 2388 } 2389 2390 @Override 2391 public boolean enterSUB(final UnaryNode unaryNode) { 2392 assert unaryNode.getType().isNumeric(); 2393 load(unaryNode.rhs(), unaryNode.getType()).neg().store(unaryNode.getSymbol()); 2394 return false; 2395 } 2396 2397 @Override 2398 public boolean enterVOID(final UnaryNode unaryNode) { 2399 load(unaryNode.rhs()).pop(); 2400 method.loadUndefined(Type.OBJECT); 2401 2402 return false; 2403 } 2404 2405 private void enterNumericAdd(final Expression lhs, final Expression rhs, final Type type, final Symbol symbol) { 2406 loadBinaryOperands(lhs, rhs, type); 2407 method.add(); //if the symbol is optimistic, it always needs to be written, not on the stack? 2408 method.store(symbol); 2409 } 2410 2411 @Override 2412 public boolean enterADD(final BinaryNode binaryNode) { 2413 final Expression lhs = binaryNode.lhs(); 2414 final Expression rhs = binaryNode.rhs(); 2415 2416 final Type type = binaryNode.getType(); 2417 if (type.isNumeric()) { 2418 enterNumericAdd(lhs, rhs, type, binaryNode.getSymbol()); 2419 } else { 2420 loadBinaryOperands(binaryNode); 2421 method.add(); 2422 method.store(binaryNode.getSymbol()); 2423 } 2424 2425 return false; 2426 } 2427 2428 private boolean enterAND_OR(final BinaryNode binaryNode) { 2429 final Expression lhs = binaryNode.lhs(); 2430 final Expression rhs = binaryNode.rhs(); 2431 2432 final Label skip = new Label("skip"); 2433 2434 load(lhs, Type.OBJECT).dup().convert(Type.BOOLEAN); 2435 2436 if (binaryNode.tokenType() == TokenType.AND) { 2437 method.ifeq(skip); 2438 } else { 2439 method.ifne(skip); 2440 } 2441 2442 method.pop(); 2443 load(rhs, Type.OBJECT); 2444 method.label(skip); 2445 method.store(binaryNode.getSymbol()); 2446 2447 return false; 2448 } 2449 2450 @Override 2451 public boolean enterAND(final BinaryNode binaryNode) { 2452 return enterAND_OR(binaryNode); 2453 } 2454 2455 @Override 2456 public boolean enterASSIGN(final BinaryNode binaryNode) { 2457 final Expression lhs = binaryNode.lhs(); 2458 final Expression rhs = binaryNode.rhs(); 2459 2460 final Type lhsType = lhs.getType(); 2461 final Type rhsType = rhs.getType(); 2462 2463 if (!lhsType.isEquivalentTo(rhsType)) { 2464 //this is OK if scoped, only locals are wrong 2465 } 2466 2467 new Store<BinaryNode>(binaryNode, lhs) { 2468 @Override 2469 protected void evaluate() { 2470 if ((lhs instanceof IdentNode) && !lhs.getSymbol().isScope()) { 2471 load(rhs, lhsType); 2472 } else { 2473 load(rhs); 2474 } 2475 } 2476 }.store(); 2477 2478 return false; 2479 } 2480 2481 /** 2482 * Helper class for assignment ops, e.g. *=, += and so on.. 2483 */ 2484 private abstract class AssignOp extends SelfModifyingStore<BinaryNode> { 2485 2486 /** The type of the resulting operation */ 2487 private final Type opType; 2488 2489 /** 2490 * Constructor 2491 * 2492 * @param node the assign op node 2493 */ 2494 AssignOp(final BinaryNode node) { 2495 this(node.getType(), node); 2496 } 2497 2498 /** 2499 * Constructor 2500 * 2501 * @param opType type of the computation - overriding the type of the node 2502 * @param node the assign op node 2503 */ 2504 AssignOp(final Type opType, final BinaryNode node) { 2505 super(node, node.lhs()); 2506 this.opType = opType; 2507 } 2508 2509 protected abstract void op(); 2510 2511 @Override 2512 protected void evaluate() { 2513 loadBinaryOperands(assignNode.lhs(), assignNode.rhs(), opType, true); 2514 op(); 2515 method.convert(assignNode.getType()); 2516 } 2517 } 2518 2519 @Override 2520 public boolean enterASSIGN_ADD(final BinaryNode binaryNode) { 2521 assert RuntimeNode.Request.ADD.canSpecialize(); 2522 final Type lhsType = binaryNode.lhs().getType(); 2523 final Type rhsType = binaryNode.rhs().getType(); 2524 final boolean specialize = binaryNode.getType() == Type.OBJECT; 2525 2526 new AssignOp(binaryNode) { 2527 2528 @Override 2529 protected void op() { 2530 if (specialize) { 2531 method.dynamicRuntimeCall( 2532 new SpecializedRuntimeNode( 2533 Request.ADD, 2534 new Type[] { 2535 lhsType, 2536 rhsType, 2537 }, 2538 Type.OBJECT).getInitialName(), 2539 Type.OBJECT, 2540 Request.ADD); 2541 } else { 2542 method.add(); 2543 } 2544 } 2545 2546 @Override 2547 protected void evaluate() { 2548 super.evaluate(); 2549 } 2550 }.store(); 2551 2552 return false; 2553 } 2554 2555 @Override 2556 public boolean enterASSIGN_BIT_AND(final BinaryNode binaryNode) { 2557 new AssignOp(Type.INT, binaryNode) { 2558 @Override 2559 protected void op() { 2560 method.and(); 2561 } 2562 }.store(); 2563 2564 return false; 2565 } 2566 2567 @Override 2568 public boolean enterASSIGN_BIT_OR(final BinaryNode binaryNode) { 2569 new AssignOp(Type.INT, binaryNode) { 2570 @Override 2571 protected void op() { 2572 method.or(); 2573 } 2574 }.store(); 2575 2576 return false; 2577 } 2578 2579 @Override 2580 public boolean enterASSIGN_BIT_XOR(final BinaryNode binaryNode) { 2581 new AssignOp(Type.INT, binaryNode) { 2582 @Override 2583 protected void op() { 2584 method.xor(); 2585 } 2586 }.store(); 2587 2588 return false; 2589 } 2590 2591 @Override 2592 public boolean enterASSIGN_DIV(final BinaryNode binaryNode) { 2593 new AssignOp(binaryNode) { 2594 @Override 2595 protected void op() { 2596 method.div(); 2597 } 2598 }.store(); 2599 2600 return false; 2601 } 2602 2603 @Override 2604 public boolean enterASSIGN_MOD(final BinaryNode binaryNode) { 2605 new AssignOp(binaryNode) { 2606 @Override 2607 protected void op() { 2608 method.rem(); 2609 } 2610 }.store(); 2611 2612 return false; 2613 } 2614 2615 @Override 2616 public boolean enterASSIGN_MUL(final BinaryNode binaryNode) { 2617 new AssignOp(binaryNode) { 2618 @Override 2619 protected void op() { 2620 method.mul(); 2621 } 2622 }.store(); 2623 2624 return false; 2625 } 2626 2627 @Override 2628 public boolean enterASSIGN_SAR(final BinaryNode binaryNode) { 2629 new AssignOp(Type.INT, binaryNode) { 2630 @Override 2631 protected void op() { 2632 method.sar(); 2633 } 2634 }.store(); 2635 2636 return false; 2637 } 2638 2639 @Override 2640 public boolean enterASSIGN_SHL(final BinaryNode binaryNode) { 2641 new AssignOp(Type.INT, binaryNode) { 2642 @Override 2643 protected void op() { 2644 method.shl(); 2645 } 2646 }.store(); 2647 2648 return false; 2649 } 2650 2651 @Override 2652 public boolean enterASSIGN_SHR(final BinaryNode binaryNode) { 2653 new AssignOp(Type.INT, binaryNode) { 2654 @Override 2655 protected void op() { 2656 method.shr(); 2657 method.convert(Type.LONG).load(JSType.MAX_UINT).and(); 2658 } 2659 }.store(); 2660 2661 return false; 2662 } 2663 2664 @Override 2665 public boolean enterASSIGN_SUB(final BinaryNode binaryNode) { 2666 new AssignOp(binaryNode) { 2667 @Override 2668 protected void op() { 2669 method.sub(); 2670 } 2671 }.store(); 2672 2673 return false; 2674 } 2675 2676 /** 2677 * Helper class for binary arithmetic ops 2678 */ 2679 private abstract class BinaryArith { 2680 2681 protected abstract void op(); 2682 2683 protected void evaluate(final BinaryNode node) { 2684 loadBinaryOperands(node); 2685 op(); 2686 method.store(node.getSymbol()); 2687 } 2688 } 2689 2690 @Override 2691 public boolean enterBIT_AND(final BinaryNode binaryNode) { 2692 new BinaryArith() { 2693 @Override 2694 protected void op() { 2695 method.and(); 2696 } 2697 }.evaluate(binaryNode); 2698 2699 return false; 2700 } 2701 2702 @Override 2703 public boolean enterBIT_OR(final BinaryNode binaryNode) { 2704 new BinaryArith() { 2705 @Override 2706 protected void op() { 2707 method.or(); 2708 } 2709 }.evaluate(binaryNode); 2710 2711 return false; 2712 } 2713 2714 @Override 2715 public boolean enterBIT_XOR(final BinaryNode binaryNode) { 2716 new BinaryArith() { 2717 @Override 2718 protected void op() { 2719 method.xor(); 2720 } 2721 }.evaluate(binaryNode); 2722 2723 return false; 2724 } 2725 2726 private boolean enterComma(final BinaryNode binaryNode) { 2727 final Expression lhs = binaryNode.lhs(); 2728 final Expression rhs = binaryNode.rhs(); 2729 2730 load(lhs); 2731 load(rhs); 2732 method.store(binaryNode.getSymbol()); 2733 2734 return false; 2735 } 2736 2737 @Override 2738 public boolean enterCOMMARIGHT(final BinaryNode binaryNode) { 2739 return enterComma(binaryNode); 2740 } 2741 2742 @Override 2743 public boolean enterCOMMALEFT(final BinaryNode binaryNode) { 2744 return enterComma(binaryNode); 2745 } 2746 2747 @Override 2748 public boolean enterDIV(final BinaryNode binaryNode) { 2749 new BinaryArith() { 2750 @Override 2751 protected void op() { 2752 method.div(); 2753 } 2754 }.evaluate(binaryNode); 2755 2756 return false; 2757 } 2758 2759 private boolean enterCmp(final Expression lhs, final Expression rhs, final Condition cond, final Type type, final Symbol symbol) { 2760 final Type lhsType = lhs.getType(); 2761 final Type rhsType = rhs.getType(); 2762 2763 final Type widest = Type.widest(lhsType, rhsType); 2764 assert widest.isNumeric() || widest.isBoolean() : widest; 2765 2766 loadBinaryOperands(lhs, rhs, widest); 2767 final Label trueLabel = new Label("trueLabel"); 2768 final Label afterLabel = new Label("skip"); 2769 2770 method.conditionalJump(cond, trueLabel); 2771 2772 method.load(Boolean.FALSE); 2773 method._goto(afterLabel); 2774 method.label(trueLabel); 2775 method.load(Boolean.TRUE); 2776 method.label(afterLabel); 2777 2778 method.convert(type); 2779 method.store(symbol); 2780 2781 return false; 2782 } 2783 2784 private boolean enterCmp(final BinaryNode binaryNode, final Condition cond) { 2785 return enterCmp(binaryNode.lhs(), binaryNode.rhs(), cond, binaryNode.getType(), binaryNode.getSymbol()); 2786 } 2787 2788 @Override 2789 public boolean enterEQ(final BinaryNode binaryNode) { 2790 return enterCmp(binaryNode, Condition.EQ); 2791 } 2792 2793 @Override 2794 public boolean enterEQ_STRICT(final BinaryNode binaryNode) { 2795 return enterCmp(binaryNode, Condition.EQ); 2796 } 2797 2798 @Override 2799 public boolean enterGE(final BinaryNode binaryNode) { 2800 return enterCmp(binaryNode, Condition.GE); 2801 } 2802 2803 @Override 2804 public boolean enterGT(final BinaryNode binaryNode) { 2805 return enterCmp(binaryNode, Condition.GT); 2806 } 2807 2808 @Override 2809 public boolean enterLE(final BinaryNode binaryNode) { 2810 return enterCmp(binaryNode, Condition.LE); 2811 } 2812 2813 @Override 2814 public boolean enterLT(final BinaryNode binaryNode) { 2815 return enterCmp(binaryNode, Condition.LT); 2816 } 2817 2818 @Override 2819 public boolean enterMOD(final BinaryNode binaryNode) { 2820 new BinaryArith() { 2821 @Override 2822 protected void op() { 2823 method.rem(); 2824 } 2825 }.evaluate(binaryNode); 2826 2827 return false; 2828 } 2829 2830 @Override 2831 public boolean enterMUL(final BinaryNode binaryNode) { 2832 new BinaryArith() { 2833 @Override 2834 protected void op() { 2835 method.mul(); 2836 } 2837 }.evaluate(binaryNode); 2838 2839 return false; 2840 } 2841 2842 @Override 2843 public boolean enterNE(final BinaryNode binaryNode) { 2844 return enterCmp(binaryNode, Condition.NE); 2845 } 2846 2847 @Override 2848 public boolean enterNE_STRICT(final BinaryNode binaryNode) { 2849 return enterCmp(binaryNode, Condition.NE); 2850 } 2851 2852 @Override 2853 public boolean enterOR(final BinaryNode binaryNode) { 2854 return enterAND_OR(binaryNode); 2855 } 2856 2857 @Override 2858 public boolean enterSAR(final BinaryNode binaryNode) { 2859 new BinaryArith() { 2860 @Override 2861 protected void op() { 2862 method.sar(); 2863 } 2864 }.evaluate(binaryNode); 2865 2866 return false; 2867 } 2868 2869 @Override 2870 public boolean enterSHL(final BinaryNode binaryNode) { 2871 new BinaryArith() { 2872 @Override 2873 protected void op() { 2874 method.shl(); 2875 } 2876 }.evaluate(binaryNode); 2877 2878 return false; 2879 } 2880 2881 @Override 2882 public boolean enterSHR(final BinaryNode binaryNode) { 2883 new BinaryArith() { 2884 @Override 2885 protected void evaluate(final BinaryNode node) { 2886 loadBinaryOperands(node.lhs(), node.rhs(), Type.INT); 2887 op(); 2888 method.store(node.getSymbol()); 2889 } 2890 @Override 2891 protected void op() { 2892 method.shr(); 2893 method.convert(Type.LONG).load(JSType.MAX_UINT).and(); 2894 } 2895 }.evaluate(binaryNode); 2896 2897 return false; 2898 } 2899 2900 @Override 2901 public boolean enterSUB(final BinaryNode binaryNode) { 2902 new BinaryArith() { 2903 @Override 2904 protected void op() { 2905 method.sub(); 2906 } 2907 }.evaluate(binaryNode); 2908 2909 return false; 2910 } 2911 2912 @Override 2913 public boolean enterTernaryNode(final TernaryNode ternaryNode) { 2914 final Expression test = ternaryNode.getTest(); 2915 final Expression trueExpr = ternaryNode.getTrueExpression(); 2916 final Expression falseExpr = ternaryNode.getFalseExpression(); 2917 2918 final Symbol symbol = ternaryNode.getSymbol(); 2919 final Label falseLabel = new Label("ternary_false"); 2920 final Label exitLabel = new Label("ternary_exit"); 2921 2922 Type widest = Type.widest(ternaryNode.getType(), Type.widest(trueExpr.getType(), falseExpr.getType())); 2923 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 2924 widest = Type.OBJECT; 2925 } 2926 2927 load(test, Type.BOOLEAN); 2928 // we still keep the conversion here as the AccessSpecializer can have separated the types, e.g. var y = x ? x=55 : 17 2929 // 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 2930 // symmetry for the temporary slot for this TernaryNode. This is evidence that we assign types and explicit conversions 2931 // too early, or Apply the AccessSpecializer too late. We are mostly probably looking for a separate type pass to 2932 // do this property. Then we never need any conversions in CodeGenerator 2933 method.ifeq(falseLabel); 2934 load(trueExpr, widest); 2935 method._goto(exitLabel); 2936 method.label(falseLabel); 2937 load(falseExpr, widest); 2938 method.label(exitLabel); 2939 method.store(symbol); 2940 2941 return false; 2942 } 2943 2944 /** 2945 * Generate all shared scope calls generated during codegen. 2946 */ 2947 protected void generateScopeCalls() { 2948 for (final SharedScopeCall scopeAccess : lc.getScopeCalls()) { 2949 scopeAccess.generateScopeCall(); 2950 } 2951 } 2952 2953 /** 2954 * Debug code used to print symbols 2955 * 2956 * @param block the block we are in 2957 * @param ident identifier for block or function where applicable 2958 */ 2959 @SuppressWarnings("resource") 2960 private void printSymbols(final Block block, final String ident) { 2961 if (!compiler.getEnv()._print_symbols) { 2962 return; 2963 } 2964 2965 final PrintWriter out = compiler.getEnv().getErr(); 2966 out.println("[BLOCK in '" + ident + "']"); 2967 if (!block.printSymbols(out)) { 2968 out.println("<no symbols>"); 2969 } 2970 out.println(); 2971 } 2972 2973 2974 /** 2975 * The difference between a store and a self modifying store is that 2976 * the latter may load part of the target on the stack, e.g. the base 2977 * of an AccessNode or the base and index of an IndexNode. These are used 2978 * both as target and as an extra source. Previously it was problematic 2979 * for self modifying stores if the target/lhs didn't belong to one 2980 * of three trivial categories: IdentNode, AcessNodes, IndexNodes. In that 2981 * case it was evaluated and tagged as "resolved", which meant at the second 2982 * time the lhs of this store was read (e.g. in a = a (second) + b for a += b, 2983 * it would be evaluated to a nop in the scope and cause stack underflow 2984 * 2985 * see NASHORN-703 2986 * 2987 * @param <T> 2988 */ 2989 private abstract class SelfModifyingStore<T extends Expression> extends Store<T> { 2990 protected SelfModifyingStore(final T assignNode, final Expression target) { 2991 super(assignNode, target); 2992 } 2993 2994 @Override 2995 protected boolean isSelfModifying() { 2996 return true; 2997 } 2998 } 2999 3000 /** 3001 * Helper class to generate stores 3002 */ 3003 private abstract class Store<T extends Expression> { 3004 3005 /** An assignment node, e.g. x += y */ 3006 protected final T assignNode; 3007 3008 /** The target node to store to, e.g. x */ 3009 private final Expression target; 3010 3011 /** How deep on the stack do the arguments go if this generates an indy call */ 3012 private int depth; 3013 3014 /** If we have too many arguments, we need temporary storage, this is stored in 'quick' */ 3015 private Symbol quick; 3016 3017 /** 3018 * Constructor 3019 * 3020 * @param assignNode the node representing the whole assignment 3021 * @param target the target node of the assignment (destination) 3022 */ 3023 protected Store(final T assignNode, final Expression target) { 3024 this.assignNode = assignNode; 3025 this.target = target; 3026 } 3027 3028 /** 3029 * Constructor 3030 * 3031 * @param assignNode the node representing the whole assignment 3032 */ 3033 protected Store(final T assignNode) { 3034 this(assignNode, assignNode); 3035 } 3036 3037 /** 3038 * Is this a self modifying store operation, e.g. *= or ++ 3039 * @return true if self modifying store 3040 */ 3041 protected boolean isSelfModifying() { 3042 return false; 3043 } 3044 3045 private void prologue() { 3046 final Symbol targetSymbol = target.getSymbol(); 3047 final Symbol scopeSymbol = lc.getCurrentFunction().compilerConstant(SCOPE); 3048 3049 /** 3050 * This loads the parts of the target, e.g base and index. they are kept 3051 * on the stack throughout the store and used at the end to execute it 3052 */ 3053 3054 target.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { 3055 @Override 3056 public boolean enterIdentNode(final IdentNode node) { 3057 if (targetSymbol.isScope()) { 3058 method.load(scopeSymbol); 3059 depth++; 3060 } 3061 return false; 3062 } 3063 3064 private void enterBaseNode() { 3065 assert target instanceof BaseNode : "error - base node " + target + " must be instanceof BaseNode"; 3066 final BaseNode baseNode = (BaseNode)target; 3067 final Expression base = baseNode.getBase(); 3068 3069 load(base, Type.OBJECT); 3070 depth += Type.OBJECT.getSlots(); 3071 3072 if (isSelfModifying()) { 3073 method.dup(); 3074 } 3075 } 3076 3077 @Override 3078 public boolean enterAccessNode(final AccessNode node) { 3079 enterBaseNode(); 3080 return false; 3081 } 3082 3083 @Override 3084 public boolean enterIndexNode(final IndexNode node) { 3085 enterBaseNode(); 3086 3087 final Expression index = node.getIndex(); 3088 if (!index.getType().isNumeric()) { 3089 // could be boolean here as well 3090 load(index, Type.OBJECT); 3091 } else { 3092 load(index); 3093 } 3094 depth += index.getType().getSlots(); 3095 3096 if (isSelfModifying()) { 3097 //convert "base base index" to "base index base index" 3098 method.dup(1); 3099 } 3100 3101 return false; 3102 } 3103 3104 }); 3105 } 3106 3107 private Symbol quickSymbol(final Type type) { 3108 return quickSymbol(type, QUICK_PREFIX.symbolName()); 3109 } 3110 3111 /** 3112 * Quick symbol generates an extra local variable, always using the same 3113 * slot, one that is available after the end of the frame. 3114 * 3115 * @param type the type of the symbol 3116 * @param prefix the prefix for the variable name for the symbol 3117 * 3118 * @return the quick symbol 3119 */ 3120 private Symbol quickSymbol(final Type type, final String prefix) { 3121 final String name = lc.getCurrentFunction().uniqueName(prefix); 3122 final Symbol symbol = new Symbol(name, IS_TEMP | IS_INTERNAL); 3123 3124 symbol.setType(type); 3125 3126 symbol.setSlot(lc.quickSlot(symbol)); 3127 3128 return symbol; 3129 } 3130 3131 // store the result that "lives on" after the op, e.g. "i" in i++ postfix. 3132 protected void storeNonDiscard() { 3133 if (lc.getCurrentDiscard() == assignNode) { 3134 assert assignNode.isAssignment(); 3135 lc.popDiscard(); 3136 return; 3137 } 3138 3139 final Symbol symbol = assignNode.getSymbol(); 3140 if (symbol.hasSlot()) { 3141 method.dup().store(symbol); 3142 return; 3143 } 3144 3145 if (method.dup(depth) == null) { 3146 method.dup(); 3147 this.quick = quickSymbol(method.peekType()); 3148 method.store(quick); 3149 } 3150 } 3151 3152 private void epilogue() { 3153 /** 3154 * Take the original target args from the stack and use them 3155 * together with the value to be stored to emit the store code 3156 * 3157 * The case that targetSymbol is in scope (!hasSlot) and we actually 3158 * need to do a conversion on non-equivalent types exists, but is 3159 * very rare. See for example test/script/basic/access-specializer.js 3160 */ 3161 target.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { 3162 @Override 3163 protected boolean enterDefault(Node node) { 3164 throw new AssertionError("Unexpected node " + node + " in store epilogue"); 3165 } 3166 3167 @Override 3168 public boolean enterIdentNode(final IdentNode node) { 3169 final Symbol symbol = node.getSymbol(); 3170 assert symbol != null; 3171 if (symbol.isScope()) { 3172 if (isFastScope(symbol)) { 3173 storeFastScopeVar(symbol, CALLSITE_SCOPE | getCallSiteFlags()); 3174 } else { 3175 method.dynamicSet(node.getName(), CALLSITE_SCOPE | getCallSiteFlags()); 3176 } 3177 } else { 3178 method.convert(node.getType()); 3179 method.store(symbol); 3180 } 3181 return false; 3182 3183 } 3184 3185 @Override 3186 public boolean enterAccessNode(final AccessNode node) { 3187 method.dynamicSet(node.getProperty().getName(), getCallSiteFlags()); 3188 return false; 3189 } 3190 3191 @Override 3192 public boolean enterIndexNode(final IndexNode node) { 3193 method.dynamicSetIndex(getCallSiteFlags()); 3194 return false; 3195 } 3196 }); 3197 3198 3199 // whatever is on the stack now is the final answer 3200 } 3201 3202 protected abstract void evaluate(); 3203 3204 void store() { 3205 prologue(); 3206 evaluate(); // leaves an operation of whatever the operationType was on the stack 3207 storeNonDiscard(); 3208 epilogue(); 3209 if (quick != null) { 3210 method.load(quick); 3211 } 3212 } 3213 } 3214 3215 private void newFunctionObject(final FunctionNode functionNode, final FunctionNode originalFunctionNode) { 3216 assert lc.peek() == functionNode; 3217 // We don't emit a ScriptFunction on stack for: 3218 // 1. the outermost compiled function (as there's no code being generated in its outer context that'd need it 3219 // as a callee), and 3220 // 2. for functions that are immediately called upon definition and they don't need a callee, e.g. (function(){})(). 3221 // Such immediately-called functions are invoked using INVOKESTATIC (see enterFunctionNode() of the embedded 3222 // visitor of enterCallNode() for details), and if they don't need a callee, they don't have it on their 3223 // static method's parameter list. 3224 if (lc.getOutermostFunction() == functionNode || 3225 (!functionNode.needsCallee()) && lc.isFunctionDefinedInCurrentCall(originalFunctionNode)) { 3226 return; 3227 } 3228 3229 // Generate the object class and property map in case this function is ever used as constructor 3230 final String className = SCRIPTFUNCTION_IMPL_OBJECT; 3231 final int fieldCount = ObjectClassGenerator.getPaddedFieldCount(functionNode.countThisProperties()); 3232 final String allocatorClassName = Compiler.binaryName(ObjectClassGenerator.getClassName(fieldCount)); 3233 final PropertyMap allocatorMap = PropertyMap.newMap(null, 0, fieldCount, 0); 3234 3235 method._new(className).dup(); 3236 loadConstant(new RecompilableScriptFunctionData(functionNode, compiler.getCodeInstaller(), allocatorClassName, allocatorMap)); 3237 3238 if (functionNode.isLazy() || functionNode.needsParentScope()) { 3239 method.loadCompilerConstant(SCOPE); 3240 } else { 3241 method.loadNull(); 3242 } 3243 method.invoke(constructorNoLookup(className, RecompilableScriptFunctionData.class, ScriptObject.class)); 3244 } 3245 3246 // calls on Global class. 3247 private MethodEmitter globalInstance() { 3248 return method.invokestatic(GLOBAL_OBJECT, "instance", "()L" + GLOBAL_OBJECT + ';'); 3249 } 3250 3251 private MethodEmitter globalObjectPrototype() { 3252 return method.invokestatic(GLOBAL_OBJECT, "objectPrototype", methodDescriptor(ScriptObject.class)); 3253 } 3254 3255 private MethodEmitter globalAllocateArguments() { 3256 return method.invokestatic(GLOBAL_OBJECT, "allocateArguments", methodDescriptor(ScriptObject.class, Object[].class, Object.class, int.class)); 3257 } 3258 3259 private MethodEmitter globalNewRegExp() { 3260 return method.invokestatic(GLOBAL_OBJECT, "newRegExp", methodDescriptor(Object.class, String.class, String.class)); 3261 } 3262 3263 private MethodEmitter globalRegExpCopy() { 3264 return method.invokestatic(GLOBAL_OBJECT, "regExpCopy", methodDescriptor(Object.class, Object.class)); 3265 } 3266 3267 private MethodEmitter globalAllocateArray(final ArrayType type) { 3268 //make sure the native array is treated as an array type 3269 return method.invokestatic(GLOBAL_OBJECT, "allocate", "(" + type.getDescriptor() + ")Ljdk/nashorn/internal/objects/NativeArray;"); 3270 } 3271 3272 private MethodEmitter globalIsEval() { 3273 return method.invokestatic(GLOBAL_OBJECT, "isEval", methodDescriptor(boolean.class, Object.class)); 3274 } 3275 3276 private MethodEmitter globalCheckObjectCoercible() { 3277 return method.invokestatic(GLOBAL_OBJECT, "checkObjectCoercible", methodDescriptor(void.class, Object.class)); 3278 } 3279 3280 private MethodEmitter globalDirectEval() { 3281 return method.invokestatic(GLOBAL_OBJECT, "directEval", 3282 methodDescriptor(Object.class, Object.class, Object.class, Object.class, Object.class, Object.class)); 3283 } 3284 }