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