rev 1901 : 8135251: Use Unsafe.defineAnonymousClass for loading Nashorn script code
Reviewed-by: hannesw, lagergren, sundar
1 /*
2 * Copyright (c) 2010, 2014, 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.runtime.logging.DebugLogger.quote;
29
30 import java.io.PrintWriter;
31 import java.util.HashMap;
32 import java.util.LinkedHashMap;
33 import java.util.Map;
34 import java.util.Map.Entry;
35 import java.util.Set;
36 import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
37 import jdk.nashorn.internal.ir.Block;
38 import jdk.nashorn.internal.ir.FunctionNode;
39 import jdk.nashorn.internal.ir.LiteralNode;
40 import jdk.nashorn.internal.ir.Node;
41 import jdk.nashorn.internal.ir.Symbol;
42 import jdk.nashorn.internal.ir.debug.ASTWriter;
43 import jdk.nashorn.internal.ir.debug.PrintVisitor;
44 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
45 import jdk.nashorn.internal.ir.visitor.SimpleNodeVisitor;
46 import jdk.nashorn.internal.runtime.CodeInstaller;
47 import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
48 import jdk.nashorn.internal.runtime.ScriptEnvironment;
49 import jdk.nashorn.internal.runtime.logging.DebugLogger;
50
51 /**
52 * A compilation phase is a step in the processes of turning a JavaScript
53 * FunctionNode into bytecode. It has an optional return value.
54 */
55 abstract class CompilationPhase {
56
57 private static final class ConstantFoldingPhase extends CompilationPhase {
58 @Override
59 FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
60 return transformFunction(fn, new FoldConstants(compiler));
61 }
62
63 @Override
64 public String toString() {
65 return "'Constant Folding'";
66 }
67 }
68
69 /**
70 * Constant folding pass Simple constant folding that will make elementary
71 * constructs go away
72 */
73 static final CompilationPhase CONSTANT_FOLDING_PHASE = new ConstantFoldingPhase();
74
75 private static final class LoweringPhase extends CompilationPhase {
76 @Override
77 FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
78 return transformFunction(fn, new Lower(compiler));
79 }
80
81 @Override
82 public String toString() {
83 return "'Control Flow Lowering'";
84 }
85 }
86
87 /**
88 * Lower (Control flow pass) Finalizes the control flow. Clones blocks for
89 * finally constructs and similar things. Establishes termination criteria
90 * for nodes Guarantee return instructions to method making sure control
91 * flow cannot fall off the end. Replacing high level nodes with lower such
92 * as runtime nodes where applicable.
93 */
94 static final CompilationPhase LOWERING_PHASE = new LoweringPhase();
95
96 private static final class ApplySpecializationPhase extends CompilationPhase {
97 @Override
98 FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
99 return transformFunction(fn, new ApplySpecialization(compiler));
100 }
101
102 @Override
103 public String toString() {
104 return "'Builtin Replacement'";
105 }
106 };
107
108 /**
109 * Phase used to transform Function.prototype.apply.
110 */
111 static final CompilationPhase APPLY_SPECIALIZATION_PHASE = new ApplySpecializationPhase();
112
113 private static final class SplittingPhase extends CompilationPhase {
114 @Override
115 FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
116 final CompileUnit outermostCompileUnit = compiler.addCompileUnit(0L);
117
118 FunctionNode newFunctionNode;
119
120 //ensure elementTypes, postsets and presets exist for splitter and arraynodes
121 newFunctionNode = transformFunction(fn, new SimpleNodeVisitor() {
122 @Override
123 public LiteralNode<?> leaveLiteralNode(final LiteralNode<?> literalNode) {
124 return literalNode.initialize(lc);
125 }
126 });
127
128 newFunctionNode = new Splitter(compiler, newFunctionNode, outermostCompileUnit).split(newFunctionNode, true);
129 newFunctionNode = transformFunction(newFunctionNode, new SplitIntoFunctions(compiler));
130 assert newFunctionNode.getCompileUnit() == outermostCompileUnit : "fn=" + fn.getName() + ", fn.compileUnit (" + newFunctionNode.getCompileUnit() + ") != " + outermostCompileUnit;
131 assert newFunctionNode.isStrict() == compiler.isStrict() : "functionNode.isStrict() != compiler.isStrict() for " + quote(newFunctionNode.getName());
132
133 return newFunctionNode;
134 }
135
136 @Override
137 public String toString() {
138 return "'Code Splitting'";
139 }
140 };
141
142 /**
143 * Splitter Split the AST into several compile units based on a heuristic size calculation.
144 * Split IR can lead to scope information being changed.
145 */
146 static final CompilationPhase SPLITTING_PHASE = new SplittingPhase();
147
148 private static final class ProgramPointPhase extends CompilationPhase {
149 @Override
150 FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
151 return transformFunction(fn, new ProgramPoints());
152 }
153
154 @Override
155 public String toString() {
156 return "'Program Point Calculation'";
157 }
158 };
159
160 static final CompilationPhase PROGRAM_POINT_PHASE = new ProgramPointPhase();
161
162 private static final class CacheAstPhase extends CompilationPhase {
163 @Override
164 FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
165 if (!compiler.isOnDemandCompilation()) {
166 // Only do this on initial preprocessing of the source code. For on-demand compilations from
167 // source, FindScopeDepths#leaveFunctionNode() calls data.setCachedAst() for the sole function
168 // being compiled.
169 transformFunction(fn, new CacheAst(compiler));
170 }
171 // NOTE: we're returning the original fn as we have destructively modified the cached functions by
172 // removing their bodies. This step is associating FunctionNode objects with
173 // RecompilableScriptFunctionData; it's not really modifying the AST.
174 return fn;
175 }
176
177 @Override
178 public String toString() {
179 return "'Cache ASTs'";
180 }
181 };
182
183 static final CompilationPhase CACHE_AST_PHASE = new CacheAstPhase();
184
185 private static final class SymbolAssignmentPhase extends CompilationPhase {
186 @Override
187 FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
188 return transformFunction(fn, new AssignSymbols(compiler));
189 }
190
191 @Override
192 public String toString() {
193 return "'Symbol Assignment'";
194 }
195 };
196
197 static final CompilationPhase SYMBOL_ASSIGNMENT_PHASE = new SymbolAssignmentPhase();
198
199 private static final class ScopeDepthComputationPhase extends CompilationPhase {
200 @Override
201 FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
202 return transformFunction(fn, new FindScopeDepths(compiler));
203 }
204
205 @Override
206 public String toString() {
207 return "'Scope Depth Computation'";
208 }
209 };
210
211 static final CompilationPhase SCOPE_DEPTH_COMPUTATION_PHASE = new ScopeDepthComputationPhase();
212
213 private static final class DeclareLocalSymbolsPhase extends CompilationPhase {
214 @Override
215 FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
216 // It's not necessary to guard the marking of symbols as locals with this "if" condition for
217 // correctness, it's just an optimization -- runtime type calculation is not used when the compilation
218 // is not an on-demand optimistic compilation, so we can skip locals marking then.
219 if (compiler.useOptimisticTypes() && compiler.isOnDemandCompilation()) {
220 fn.getBody().accept(new SimpleNodeVisitor() {
221 @Override
222 public boolean enterFunctionNode(final FunctionNode functionNode) {
223 // OTOH, we must not declare symbols from nested functions to be locals. As we're doing on-demand
224 // compilation, and we're skipping parsing the function bodies for nested functions, this
225 // basically only means their parameters. It'd be enough to mistakenly declare to be a local a
226 // symbol in the outer function named the same as one of the parameters, though.
227 return false;
228 };
229 @Override
230 public boolean enterBlock(final Block block) {
231 for (final Symbol symbol: block.getSymbols()) {
232 if (!symbol.isScope()) {
233 compiler.declareLocalSymbol(symbol.getName());
234 }
235 }
236 return true;
237 };
238 });
239 }
240 return fn;
241 }
242
243 @Override
244 public String toString() {
245 return "'Local Symbols Declaration'";
246 }
247 };
248
249 static final CompilationPhase DECLARE_LOCAL_SYMBOLS_PHASE = new DeclareLocalSymbolsPhase();
250
251 private static final class OptimisticTypeAssignmentPhase extends CompilationPhase {
252 @Override
253 FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
254 if (compiler.useOptimisticTypes()) {
255 return transformFunction(fn, new OptimisticTypesCalculator(compiler));
256 }
257 return fn;
258 }
259
260 @Override
261 public String toString() {
262 return "'Optimistic Type Assignment'";
263 }
264 }
265
266 static final CompilationPhase OPTIMISTIC_TYPE_ASSIGNMENT_PHASE = new OptimisticTypeAssignmentPhase();
267
268 private static final class LocalVariableTypeCalculationPhase extends CompilationPhase {
269 @Override
270 FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
271 final FunctionNode newFunctionNode = transformFunction(fn, new LocalVariableTypesCalculator(compiler));
272 final ScriptEnvironment senv = compiler.getScriptEnvironment();
273 final PrintWriter err = senv.getErr();
274
275 //TODO separate phase for the debug printouts for abstraction and clarity
276 if (senv._print_lower_ast || fn.getFlag(FunctionNode.IS_PRINT_LOWER_AST)) {
277 err.println("Lower AST for: " + quote(newFunctionNode.getName()));
278 err.println(new ASTWriter(newFunctionNode));
279 }
280
281 if (senv._print_lower_parse || fn.getFlag(FunctionNode.IS_PRINT_LOWER_PARSE)) {
282 err.println("Lower AST for: " + quote(newFunctionNode.getName()));
283 err.println(new PrintVisitor(newFunctionNode));
284 }
285
286 return newFunctionNode;
287 }
288
289 @Override
290 public String toString() {
291 return "'Local Variable Type Calculation'";
292 }
293 };
294
295 static final CompilationPhase LOCAL_VARIABLE_TYPE_CALCULATION_PHASE = new LocalVariableTypeCalculationPhase();
296
297 private static final class ReuseCompileUnitsPhase extends CompilationPhase {
298 @Override
299 FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
300 assert phases.isRestOfCompilation() : "reuse compile units currently only used for Rest-Of methods";
301
302 final Map<CompileUnit, CompileUnit> map = new HashMap<>();
303 final Set<CompileUnit> newUnits = CompileUnit.createCompileUnitSet();
304
305 final DebugLogger log = compiler.getLogger();
306
307 log.fine("Clearing bytecode cache");
308 compiler.clearBytecode();
309
310 for (final CompileUnit oldUnit : compiler.getCompileUnits()) {
311 assert map.get(oldUnit) == null;
312 final CompileUnit newUnit = createNewCompileUnit(compiler, phases);
313 log.fine("Creating new compile unit ", oldUnit, " => ", newUnit);
314 map.put(oldUnit, newUnit);
315 assert newUnit != null;
316 newUnits.add(newUnit);
317 }
318
319 log.fine("Replacing compile units in Compiler...");
320 compiler.replaceCompileUnits(newUnits);
321 log.fine("Done");
322
323 //replace old compile units in function nodes, if any are assigned,
324 //for example by running the splitter on this function node in a previous
325 //partial code generation
326 final FunctionNode newFunctionNode = transformFunction(fn, new ReplaceCompileUnits() {
327 @Override
328 CompileUnit getReplacement(final CompileUnit original) {
329 return map.get(original);
330 }
331
332 @Override
333 public Node leaveDefault(final Node node) {
334 return node.ensureUniqueLabels(lc);
335 }
336 });
337
338 return newFunctionNode;
339 }
340
341 @Override
342 public String toString() {
343 return "'Reuse Compile Units'";
344 }
345 }
346
347 /**
348 * Reuse compile units, if they are already present. We are using the same compiler
349 * to recompile stuff
350 */
351 static final CompilationPhase REUSE_COMPILE_UNITS_PHASE = new ReuseCompileUnitsPhase();
352
353 private static final class ReinitializeCachedPhase extends CompilationPhase {
354 @Override
355 FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
356 final Set<CompileUnit> unitSet = CompileUnit.createCompileUnitSet();
357 final Map<CompileUnit, CompileUnit> unitMap = new HashMap<>();
358
359 // Ensure that the FunctionNode's compile unit is the first in the list of new units. Install phase
360 // will use that as the root class.
361 createCompileUnit(fn.getCompileUnit(), unitSet, unitMap, compiler, phases);
362
363 final FunctionNode newFn = transformFunction(fn, new ReplaceCompileUnits() {
364 @Override
365 CompileUnit getReplacement(final CompileUnit oldUnit) {
366 final CompileUnit existing = unitMap.get(oldUnit);
367 if (existing != null) {
368 return existing;
369 }
370 return createCompileUnit(oldUnit, unitSet, unitMap, compiler, phases);
371 }
372
373 @Override
374 public Node leaveFunctionNode(final FunctionNode fn2) {
375 return super.leaveFunctionNode(
376 // restore flags for deserialized nested function nodes
377 compiler.getScriptFunctionData(fn2.getId()).restoreFlags(lc, fn2));
378 };
379 });
380 compiler.replaceCompileUnits(unitSet);
381 return newFn;
382 }
383
384 private CompileUnit createCompileUnit(final CompileUnit oldUnit, final Set<CompileUnit> unitSet,
385 final Map<CompileUnit, CompileUnit> unitMap, final Compiler compiler, final CompilationPhases phases) {
386 final CompileUnit newUnit = createNewCompileUnit(compiler, phases);
387 unitMap.put(oldUnit, newUnit);
388 unitSet.add(newUnit);
389 return newUnit;
390 }
391
392 @Override
393 public String toString() {
394 return "'Reinitialize cached'";
395 }
396 }
397
398 static final CompilationPhase REINITIALIZE_CACHED = new ReinitializeCachedPhase();
399
400 private static final class BytecodeGenerationPhase extends CompilationPhase {
401 @Override
402 FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
403 final ScriptEnvironment senv = compiler.getScriptEnvironment();
404
405 FunctionNode newFunctionNode = fn;
406
407 //root class is special, as it is bootstrapped from createProgramFunction, thus it's skipped
408 //in CodeGeneration - the rest can be used as a working "is compile unit used" metric
409 fn.getCompileUnit().setUsed();
410
411 compiler.getLogger().fine("Starting bytecode generation for ", quote(fn.getName()), " - restOf=", phases.isRestOfCompilation());
412
413 final CodeGenerator codegen = new CodeGenerator(compiler, phases.isRestOfCompilation() ? compiler.getContinuationEntryPoints() : null);
414
415 try {
416 // Explicitly set BYTECODE_GENERATED here; it can not be set in case of skipping codegen for :program
417 // in the lazy + optimistic world. See CodeGenerator.skipFunction().
418 newFunctionNode = transformFunction(newFunctionNode, codegen);
419 codegen.generateScopeCalls();
420 } catch (final VerifyError e) {
421 if (senv._verify_code || senv._print_code) {
422 senv.getErr().println(e.getClass().getSimpleName() + ": " + e.getMessage());
423 if (senv._dump_on_error) {
424 e.printStackTrace(senv.getErr());
425 }
426 } else {
427 throw e;
428 }
429 } catch (final Throwable e) {
430 // Provide source file and line number being compiled when the assertion occurred
431 throw new AssertionError("Failed generating bytecode for " + fn.getSourceName() + ":" + codegen.getLastLineNumber(), e);
432 }
433
434 for (final CompileUnit compileUnit : compiler.getCompileUnits()) {
435 final ClassEmitter classEmitter = compileUnit.getClassEmitter();
436 classEmitter.end();
437
438 if (!compileUnit.isUsed()) {
439 compiler.getLogger().fine("Skipping unused compile unit ", compileUnit);
440 continue;
441 }
442
443 final byte[] bytecode = classEmitter.toByteArray();
444 assert bytecode != null;
445
446 final String className = compileUnit.getUnitClassName();
447 compiler.addClass(className, bytecode); //classes are only added to the bytecode map if compile unit is used
448
449 CompileUnit.increaseEmitCount();
450
451 // should we verify the generated code?
452 if (senv._verify_code) {
453 compiler.getCodeInstaller().verify(bytecode);
454 }
455
456 DumpBytecode.dumpBytecode(senv, compiler.getLogger(), bytecode, className);
457 }
458
459 return newFunctionNode;
460 }
461
462 @Override
463 public String toString() {
464 return "'Bytecode Generation'";
465 }
466 }
467
468 /**
469 * Bytecode generation:
470 *
471 * Generate the byte code class(es) resulting from the compiled FunctionNode
472 */
473 static final CompilationPhase BYTECODE_GENERATION_PHASE = new BytecodeGenerationPhase();
474
475 private static final class InstallPhase extends CompilationPhase {
476 @Override
477 FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
478 final DebugLogger log = compiler.getLogger();
479
480 final Map<String, Class<?>> installedClasses = new LinkedHashMap<>();
481
482 boolean first = true;
483 Class<?> rootClass = null;
484 long length = 0L;
485
486 final CodeInstaller codeInstaller = compiler.getCodeInstaller();
487 final Map<String, byte[]> bytecode = compiler.getBytecode();
488
489 for (final Entry<String, byte[]> entry : bytecode.entrySet()) {
490 final String className = entry.getKey();
491 //assert !first || className.equals(compiler.getFirstCompileUnit().getUnitClassName()) : "first=" + first + " className=" + className + " != " + compiler.getFirstCompileUnit().getUnitClassName();
492 final byte[] code = entry.getValue();
493 length += code.length;
494
495 final Class<?> clazz = codeInstaller.install(className, code);
496 if (first) {
497 rootClass = clazz;
498 first = false;
499 }
500 installedClasses.put(className, clazz);
501 }
502
503 if (rootClass == null) {
504 throw new CompilationException("Internal compiler error: root class not found!");
505 }
506
507 final Object[] constants = compiler.getConstantData().toArray();
508 codeInstaller.initialize(installedClasses.values(), compiler.getSource(), constants);
509
510 // initialize transient fields on recompilable script function data
511 for (final Object constant: constants) {
512 if (constant instanceof RecompilableScriptFunctionData) {
513 ((RecompilableScriptFunctionData)constant).initTransients(compiler.getSource(), codeInstaller);
514 }
515 }
516
517 // initialize function in the compile units
518 for (final CompileUnit unit : compiler.getCompileUnits()) {
519 if (!unit.isUsed()) {
520 continue;
521 }
522 unit.setCode(installedClasses.get(unit.getUnitClassName()));
523 unit.initializeFunctionsCode();
524 }
525
526 if (log.isEnabled()) {
527 final StringBuilder sb = new StringBuilder();
528
529 sb.append("Installed class '").
530 append(rootClass.getSimpleName()).
531 append('\'').
532 append(" [").
533 append(rootClass.getName()).
534 append(", size=").
535 append(length).
536 append(" bytes, ").
537 append(compiler.getCompileUnits().size()).
538 append(" compile unit(s)]");
539
540 log.fine(sb.toString());
541 }
542
543 return fn.setRootClass(null, rootClass);
544 }
545
546 @Override
547 public String toString() {
548 return "'Class Installation'";
549 }
550 }
551
552 static final CompilationPhase INSTALL_PHASE = new InstallPhase();
553
554 /** start time of transform - used for timing, see {@link jdk.nashorn.internal.runtime.Timing} */
555 private long startTime;
556
557 /** start time of transform - used for timing, see {@link jdk.nashorn.internal.runtime.Timing} */
558 private long endTime;
559
560 /** boolean that is true upon transform completion */
561 private boolean isFinished;
562
563 private CompilationPhase() {}
564
565 /**
566 * Start a compilation phase
567 * @param compiler the compiler to use
568 * @param functionNode function to compile
569 * @return function node
570 */
571 protected FunctionNode begin(final Compiler compiler, final FunctionNode functionNode) {
572 compiler.getLogger().indent();
573 startTime = System.nanoTime();
574
575 return functionNode;
576 }
577
578 /**
579 * End a compilation phase
580 * @param compiler the compiler
581 * @param functionNode function node to compile
582 * @return function node
583 */
584 protected FunctionNode end(final Compiler compiler, final FunctionNode functionNode) {
585 compiler.getLogger().unindent();
586 endTime = System.nanoTime();
587 compiler.getScriptEnvironment()._timing.accumulateTime(toString(), endTime - startTime);
588
589 isFinished = true;
590 return functionNode;
591 }
592
593 boolean isFinished() {
594 return isFinished;
595 }
596
597 long getStartTime() {
598 return startTime;
599 }
600
601 long getEndTime() {
602 return endTime;
603 }
604
605 abstract FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode functionNode) throws CompilationException;
606
607 /**
608 * Apply a transform to a function node, returning the transformed function node. If the transform is not
609 * applicable, an exception is thrown. Every transform requires the function to have a certain number of
610 * states to operate. It can have more states set, but not fewer. The state list, i.e. the constructor
611 * arguments to any of the CompilationPhase enum entries, is a set of REQUIRED states.
612 *
613 * @param compiler compiler
614 * @param phases current complete pipeline of which this phase is one
615 * @param functionNode function node to transform
616 *
617 * @return transformed function node
618 *
619 * @throws CompilationException if function node lacks the state required to run the transform on it
620 */
621 final FunctionNode apply(final Compiler compiler, final CompilationPhases phases, final FunctionNode functionNode) throws CompilationException {
622 assert phases.contains(this);
623
624 return end(compiler, transform(compiler, phases, begin(compiler, functionNode)));
625 }
626
627 private static FunctionNode transformFunction(final FunctionNode fn, final NodeVisitor<?> visitor) {
628 return (FunctionNode) fn.accept(visitor);
629 }
630
631 private static CompileUnit createNewCompileUnit(final Compiler compiler, final CompilationPhases phases) {
632 final StringBuilder sb = new StringBuilder(compiler.nextCompileUnitName());
633 if (phases.isRestOfCompilation()) {
634 sb.append("$restOf");
635 }
636 //it's ok to not copy the initCount, methodCount and clinitCount here, as codegen is what
637 //fills those out anyway. Thus no need for a copy constructor
638 return compiler.createCompileUnit(sb.toString(), 0);
639 }
640 }
--- EOF ---