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