Fri, 05 Jul 2013 15:10:47 +0200
8019819: scope symbol didn't get a slot in certain cases
Reviewed-by: hannesw, jlaskey, lagergren, sundar
1 package jdk.nashorn.internal.codegen;
3 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.ATTR;
4 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.CONSTANT_FOLDED;
5 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.FINALIZED;
6 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.INITIALIZED;
7 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.LOWERED;
8 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.PARSED;
9 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SPLIT;
11 import java.io.File;
12 import java.io.FileOutputStream;
13 import java.io.IOException;
14 import java.util.ArrayDeque;
15 import java.util.ArrayList;
16 import java.util.Deque;
17 import java.util.EnumSet;
18 import java.util.HashSet;
19 import java.util.List;
20 import java.util.Set;
21 import jdk.nashorn.internal.codegen.types.Range;
22 import jdk.nashorn.internal.codegen.types.Type;
23 import jdk.nashorn.internal.ir.CallNode;
24 import jdk.nashorn.internal.ir.FunctionNode;
25 import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
26 import jdk.nashorn.internal.ir.LexicalContext;
27 import jdk.nashorn.internal.ir.Node;
28 import jdk.nashorn.internal.ir.ReturnNode;
29 import jdk.nashorn.internal.ir.Symbol;
30 import jdk.nashorn.internal.ir.TemporarySymbols;
31 import jdk.nashorn.internal.ir.debug.ASTWriter;
32 import jdk.nashorn.internal.ir.debug.PrintVisitor;
33 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
34 import jdk.nashorn.internal.runtime.ECMAErrors;
35 import jdk.nashorn.internal.runtime.ScriptEnvironment;
36 import jdk.nashorn.internal.runtime.Timing;
38 /**
39 * A compilation phase is a step in the processes of turning a JavaScript
40 * FunctionNode into bytecode. It has an optional return value.
41 */
42 enum CompilationPhase {
44 /*
45 * Lazy initialization - tag all function nodes not the script as lazy as
46 * default policy. The will get trampolines and only be generated when
47 * called
48 */
49 LAZY_INITIALIZATION_PHASE(EnumSet.of(INITIALIZED, PARSED)) {
50 @Override
51 FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
53 /*
54 * For lazy compilation, we might be given a node previously marked
55 * as lazy to compile as the outermost function node in the
56 * compiler. Unmark it so it can be compiled and not cause
57 * recursion. Make sure the return type is unknown so it can be
58 * correctly deduced. Return types are always Objects in Lazy nodes
59 * as we haven't got a change to generate code for them and decude
60 * its parameter specialization
61 *
62 * TODO: in the future specializations from a callsite will be
63 * passed here so we can generate a better non-lazy version of a
64 * function from a trampoline
65 */
67 final FunctionNode outermostFunctionNode = fn;
69 final Set<FunctionNode> neverLazy = new HashSet<>();
70 final Set<FunctionNode> lazy = new HashSet<>();
72 FunctionNode newFunctionNode = outermostFunctionNode;
74 newFunctionNode = (FunctionNode)newFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
75 // self references are done with invokestatic and thus cannot
76 // have trampolines - never lazy
77 @Override
78 public boolean enterCallNode(final CallNode node) {
79 final Node callee = node.getFunction();
80 if (callee instanceof FunctionNode) {
81 neverLazy.add(((FunctionNode)callee));
82 return false;
83 }
84 return true;
85 }
87 //any function that isn't the outermost one must be marked as lazy
88 @Override
89 public boolean enterFunctionNode(final FunctionNode node) {
90 assert compiler.isLazy();
91 lazy.add(node);
92 return true;
93 }
94 });
96 //at least one method is non lazy - the outermost one
97 neverLazy.add(newFunctionNode);
99 for (final FunctionNode node : neverLazy) {
100 Compiler.LOG.fine(
101 "Marking ",
102 node.getName(),
103 " as non lazy, as it's a self reference");
104 lazy.remove(node);
105 }
107 newFunctionNode = (FunctionNode)newFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
108 @Override
109 public Node leaveFunctionNode(final FunctionNode functionNode) {
110 if (lazy.contains(functionNode)) {
111 Compiler.LOG.fine(
112 "Marking ",
113 functionNode.getName(),
114 " as lazy");
115 final FunctionNode parent = lc.getParentFunction(functionNode);
116 assert parent != null;
117 lc.setFlag(parent, FunctionNode.HAS_LAZY_CHILDREN);
118 lc.setBlockNeedsScope(parent.getBody());
119 lc.setFlag(functionNode, FunctionNode.IS_LAZY);
120 return functionNode;
121 }
123 return functionNode.
124 clearFlag(lc, FunctionNode.IS_LAZY).
125 setReturnType(lc, Type.UNKNOWN);
126 }
127 });
129 return newFunctionNode;
130 }
132 @Override
133 public String toString() {
134 return "[Lazy JIT Initialization]";
135 }
136 },
138 /*
139 * Constant folding pass Simple constant folding that will make elementary
140 * constructs go away
141 */
142 CONSTANT_FOLDING_PHASE(EnumSet.of(INITIALIZED, PARSED)) {
143 @Override
144 FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
145 return (FunctionNode)fn.accept(new FoldConstants());
146 }
148 @Override
149 public String toString() {
150 return "[Constant Folding]";
151 }
152 },
154 /*
155 * Lower (Control flow pass) Finalizes the control flow. Clones blocks for
156 * finally constructs and similar things. Establishes termination criteria
157 * for nodes Guarantee return instructions to method making sure control
158 * flow cannot fall off the end. Replacing high level nodes with lower such
159 * as runtime nodes where applicable.
160 */
161 LOWERING_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED)) {
162 @Override
163 FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
164 return (FunctionNode)fn.accept(new Lower());
165 }
167 @Override
168 public String toString() {
169 return "[Control Flow Lowering]";
170 }
171 },
173 /*
174 * Attribution Assign symbols and types to all nodes.
175 */
176 ATTRIBUTION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED)) {
177 @Override
178 FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
179 final TemporarySymbols ts = compiler.getTemporarySymbols();
180 final FunctionNode newFunctionNode = (FunctionNode)enterAttr(fn, ts).accept(new Attr(ts));
181 if (compiler.getEnv()._print_mem_usage) {
182 Compiler.LOG.info("Attr temporary symbol count: " + ts.getTotalSymbolCount());
183 }
184 return newFunctionNode;
185 }
187 /**
188 * Pessimistically set all lazy functions' return types to Object
189 * and the function symbols to object
190 * @param functionNode node where to start iterating
191 */
192 private FunctionNode enterAttr(final FunctionNode functionNode, final TemporarySymbols ts) {
193 return (FunctionNode)functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
194 @Override
195 public Node leaveFunctionNode(final FunctionNode node) {
196 if (node.isLazy()) {
197 FunctionNode newNode = node.setReturnType(lc, Type.OBJECT);
198 return ts.ensureSymbol(lc, Type.OBJECT, newNode);
199 }
200 //node may have a reference here that needs to be nulled if it was referred to by
201 //its outer context, if it is lazy and not attributed
202 return node.setReturnType(lc, Type.UNKNOWN).setSymbol(lc, null);
203 }
204 });
205 }
207 @Override
208 public String toString() {
209 return "[Type Attribution]";
210 }
211 },
213 /*
214 * Range analysis
215 * Conservatively prove that certain variables can be narrower than
216 * the most generic number type
217 */
218 RANGE_ANALYSIS_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR)) {
219 @Override
220 FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
221 if (!compiler.getEnv()._range_analysis) {
222 return fn;
223 }
225 FunctionNode newFunctionNode = (FunctionNode)fn.accept(new RangeAnalyzer());
226 final List<ReturnNode> returns = new ArrayList<>();
228 newFunctionNode = (FunctionNode)newFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
229 private final Deque<ArrayList<ReturnNode>> returnStack = new ArrayDeque<>();
231 @Override
232 public boolean enterFunctionNode(final FunctionNode functionNode) {
233 returnStack.push(new ArrayList<ReturnNode>());
234 return true;
235 }
237 @Override
238 public Node leaveFunctionNode(final FunctionNode functionNode) {
239 Type returnType = Type.UNKNOWN;
240 for (final ReturnNode ret : returnStack.pop()) {
241 if (ret.getExpression() == null) {
242 returnType = Type.OBJECT;
243 break;
244 }
245 returnType = Type.widest(returnType, ret.getExpression().getType());
246 }
247 return functionNode.setReturnType(lc, returnType);
248 }
250 @Override
251 public Node leaveReturnNode(final ReturnNode returnNode) {
252 final ReturnNode result = (ReturnNode)leaveDefault(returnNode);
253 returns.add(result);
254 return result;
255 }
257 @Override
258 public Node leaveDefault(final Node node) {
259 final Symbol symbol = node.getSymbol();
260 if (symbol != null) {
261 final Range range = symbol.getRange();
262 final Type symbolType = symbol.getSymbolType();
263 if (!symbolType.isNumeric()) {
264 return node;
265 }
266 final Type rangeType = range.getType();
267 if (!Type.areEquivalent(symbolType, rangeType) && Type.widest(symbolType, rangeType) == symbolType) { //we can narrow range
268 RangeAnalyzer.LOG.info("[", lc.getCurrentFunction().getName(), "] ", symbol, " can be ", range.getType(), " ", symbol.getRange());
269 return node.setSymbol(lc, symbol.setTypeOverrideShared(range.getType(), compiler.getTemporarySymbols()));
270 }
271 }
272 return node;
273 }
274 });
276 Type returnType = Type.UNKNOWN;
277 for (final ReturnNode node : returns) {
278 if (node.getExpression() != null) {
279 returnType = Type.widest(returnType, node.getExpression().getType());
280 } else {
281 returnType = Type.OBJECT;
282 break;
283 }
284 }
286 return newFunctionNode.setReturnType(null, returnType);
287 }
289 @Override
290 public String toString() {
291 return "[Range Analysis]";
292 }
293 },
296 /*
297 * Splitter Split the AST into several compile units based on a size
298 * heuristic Splitter needs attributed AST for weight calculations (e.g. is
299 * a + b a ScriptRuntime.ADD with call overhead or a dadd with much less).
300 * Split IR can lead to scope information being changed.
301 */
302 SPLITTING_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR)) {
303 @Override
304 FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
305 final CompileUnit outermostCompileUnit = compiler.addCompileUnit(compiler.firstCompileUnitName());
307 final FunctionNode newFunctionNode = new Splitter(compiler, fn, outermostCompileUnit).split(fn);
309 assert newFunctionNode.getCompileUnit() == outermostCompileUnit : "fn.compileUnit (" + newFunctionNode.getCompileUnit() + ") != " + outermostCompileUnit;
311 if (newFunctionNode.isStrict()) {
312 assert compiler.getStrictMode();
313 compiler.setStrictMode(true);
314 }
316 return newFunctionNode;
317 }
319 @Override
320 public String toString() {
321 return "[Code Splitting]";
322 }
323 },
325 /*
326 * FinalizeTypes
327 *
328 * This pass finalizes the types for nodes. If Attr created wider types than
329 * known during the first pass, convert nodes are inserted or access nodes
330 * are specialized where scope accesses.
331 *
332 * Runtime nodes may be removed and primitivized or reintroduced depending
333 * on information that was established in Attr.
334 *
335 * Contract: all variables must have slot assignments and scope assignments
336 * before type finalization.
337 */
338 TYPE_FINALIZATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT)) {
339 @Override
340 FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
341 final ScriptEnvironment env = compiler.getEnv();
343 final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new FinalizeTypes(compiler.getTemporarySymbols()));
345 if (env._print_lower_ast) {
346 env.getErr().println(new ASTWriter(newFunctionNode));
347 }
349 if (env._print_lower_parse) {
350 env.getErr().println(new PrintVisitor(newFunctionNode));
351 }
353 return newFunctionNode;
354 }
356 @Override
357 public String toString() {
358 return "[Type Finalization]";
359 }
360 },
362 /*
363 * Bytecode generation:
364 *
365 * Generate the byte code class(es) resulting from the compiled FunctionNode
366 */
367 BYTECODE_GENERATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT, FINALIZED)) {
368 @Override
369 FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
370 final ScriptEnvironment env = compiler.getEnv();
371 FunctionNode newFunctionNode = fn;
373 try {
374 final CodeGenerator codegen = new CodeGenerator(compiler);
375 newFunctionNode = (FunctionNode)newFunctionNode.accept(codegen);
376 codegen.generateScopeCalls();
377 } catch (final VerifyError e) {
378 if (env._verify_code || env._print_code) {
379 env.getErr().println(e.getClass().getSimpleName() + ": " + e.getMessage());
380 if (env._dump_on_error) {
381 e.printStackTrace(env.getErr());
382 }
383 } else {
384 throw e;
385 }
386 }
388 for (final CompileUnit compileUnit : compiler.getCompileUnits()) {
389 final ClassEmitter classEmitter = compileUnit.getClassEmitter();
390 classEmitter.end();
392 final byte[] bytecode = classEmitter.toByteArray();
393 assert bytecode != null;
395 final String className = compileUnit.getUnitClassName();
397 compiler.addClass(className, bytecode);
399 // should could be printed to stderr for generate class?
400 if (env._print_code) {
401 final StringBuilder sb = new StringBuilder();
402 sb.append("class: " + className).append('\n')
403 .append(ClassEmitter.disassemble(bytecode))
404 .append("=====");
405 env.getErr().println(sb);
406 }
408 // should we verify the generated code?
409 if (env._verify_code) {
410 compiler.getCodeInstaller().verify(bytecode);
411 }
413 // should code be dumped to disk - only valid in compile_only
414 // mode?
415 if (env._dest_dir != null && env._compile_only) {
416 final String fileName = className.replace('.', File.separatorChar) + ".class";
417 final int index = fileName.lastIndexOf(File.separatorChar);
419 if (index != -1) {
420 final File dir = new File(fileName.substring(0, index));
421 try {
422 if (!dir.exists() && !dir.mkdirs()) {
423 throw new IOException(dir.toString());
424 }
425 final File file = new File(env._dest_dir, fileName);
426 try (final FileOutputStream fos = new FileOutputStream(file)) {
427 fos.write(bytecode);
428 }
429 } catch (final IOException e) {
430 Compiler.LOG.warning("Skipping class dump for ",
431 className,
432 ": ",
433 ECMAErrors.getMessage(
434 "io.error.cant.write",
435 dir.toString()));
436 }
437 }
438 }
439 }
441 return newFunctionNode;
442 }
444 @Override
445 public String toString() {
446 return "[Bytecode Generation]";
447 }
448 };
450 private final EnumSet<CompilationState> pre;
451 private long startTime;
452 private long endTime;
453 private boolean isFinished;
455 private CompilationPhase(final EnumSet<CompilationState> pre) {
456 this.pre = pre;
457 }
459 boolean isApplicable(final FunctionNode functionNode) {
460 return functionNode.hasState(pre);
461 }
463 protected FunctionNode begin(final FunctionNode functionNode) {
464 if (pre != null) {
465 // check that everything in pre is present
466 for (final CompilationState state : pre) {
467 assert functionNode.hasState(state);
468 }
469 // check that nothing else is present
470 for (final CompilationState state : CompilationState.values()) {
471 assert !(functionNode.hasState(state) && !pre.contains(state));
472 }
473 }
475 startTime = System.currentTimeMillis();
476 return functionNode;
477 }
479 protected FunctionNode end(final FunctionNode functionNode) {
480 endTime = System.currentTimeMillis();
481 Timing.accumulateTime(toString(), endTime - startTime);
483 isFinished = true;
484 return functionNode;
485 }
487 boolean isFinished() {
488 return isFinished;
489 }
491 long getStartTime() {
492 return startTime;
493 }
495 long getEndTime() {
496 return endTime;
497 }
499 abstract FunctionNode transform(final Compiler compiler, final FunctionNode functionNode) throws CompilationException;
501 final FunctionNode apply(final Compiler compiler, final FunctionNode functionNode) throws CompilationException {
502 if (!isApplicable(functionNode)) {
503 throw new CompilationException("compile phase not applicable: " + this + " to " + functionNode.getName() + " state=" + functionNode.getState());
504 }
505 return end(transform(compiler, begin(functionNode)));
506 }
508 }