Thu, 14 Mar 2013 14:49:55 +0100
8009982: Lazy execution bugfix. Added lazy sunspider unit test. Added mandreel to compile-octane test. Fixed warnings
Reviewed-by: sundar, jlaskey
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 */
26 package jdk.nashorn.internal.codegen;
28 import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS;
29 import static jdk.nashorn.internal.codegen.CompilerConstants.DEFAULT_SCRIPT_NAME;
30 import static jdk.nashorn.internal.codegen.CompilerConstants.LAZY;
31 import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
32 import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE;
33 import static jdk.nashorn.internal.codegen.CompilerConstants.THIS;
35 import java.io.File;
36 import java.lang.reflect.Field;
37 import java.security.AccessController;
38 import java.security.PrivilegedActionException;
39 import java.security.PrivilegedExceptionAction;
40 import java.util.Arrays;
41 import java.util.EnumSet;
42 import java.util.HashMap;
43 import java.util.HashSet;
44 import java.util.LinkedList;
45 import java.util.Map;
46 import java.util.Map.Entry;
47 import java.util.Set;
48 import java.util.logging.Level;
50 import jdk.internal.dynalink.support.NameCodec;
51 import jdk.nashorn.internal.codegen.ClassEmitter.Flag;
52 import jdk.nashorn.internal.codegen.types.Type;
53 import jdk.nashorn.internal.ir.FunctionNode;
54 import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
55 import jdk.nashorn.internal.ir.Node;
56 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
57 import jdk.nashorn.internal.runtime.CodeInstaller;
58 import jdk.nashorn.internal.runtime.DebugLogger;
59 import jdk.nashorn.internal.runtime.ScriptEnvironment;
60 import jdk.nashorn.internal.runtime.Source;
61 import jdk.nashorn.internal.runtime.Timing;
62 import jdk.nashorn.internal.runtime.options.Options;
64 /**
65 * Responsible for converting JavaScripts to java byte code. Main entry
66 * point for code generator. The compiler may also install classes given some
67 * predefined Code installation policy, given to it at construction time.
68 * @see CodeInstaller
69 */
70 public final class Compiler {
72 /** Name of the scripts package */
73 public static final String SCRIPTS_PACKAGE = "jdk/nashorn/internal/scripts";
75 /** Name of the objects package */
76 public static final String OBJECTS_PACKAGE = "jdk/nashorn/internal/objects";
78 private final Map<String, byte[]> bytecode;
80 private final Set<CompileUnit> compileUnits;
82 private final ConstantData constantData;
84 private final FunctionNode functionNode;
86 private final CompilationSequence sequence;
88 private final ScriptEnvironment env;
90 private final String scriptName;
92 private boolean strict;
94 private CodeInstaller<ScriptEnvironment> installer;
96 /** logger for compiler, trampolines, splits and related code generation events
97 * that affect classes */
98 public static final DebugLogger LOG = new DebugLogger("compiler");
100 /**
101 * This array contains names that need to be reserved at the start
102 * of a compile, to avoid conflict with variable names later introduced.
103 * See {@link CompilerConstants} for special names used for structures
104 * during a compile.
105 */
106 private static String[] RESERVED_NAMES = {
107 SCOPE.tag(),
108 THIS.tag()
109 };
111 /**
112 * This class makes it possible to do your own compilation sequence
113 * from the code generation package. There are predefined compilation
114 * sequences already
115 */
116 @SuppressWarnings("serial")
117 static class CompilationSequence extends LinkedList<CompilationPhase> {
119 CompilationSequence(final CompilationPhase... phases) {
120 super(Arrays.asList(phases));
121 }
123 CompilationSequence(final CompilationSequence sequence) {
124 this(sequence.toArray(new CompilationPhase[sequence.size()]));
125 }
127 CompilationSequence insertAfter(final CompilationPhase phase, final CompilationPhase newPhase) {
128 final CompilationSequence newSeq = new CompilationSequence();
129 for (final CompilationPhase elem : this) {
130 newSeq.add(phase);
131 if (elem.equals(phase)) {
132 newSeq.add(newPhase);
133 }
134 }
135 assert newSeq.contains(newPhase);
136 return newSeq;
137 }
139 CompilationSequence insertBefore(final CompilationPhase phase, final CompilationPhase newPhase) {
140 final CompilationSequence newSeq = new CompilationSequence();
141 for (final CompilationPhase elem : this) {
142 if (elem.equals(phase)) {
143 newSeq.add(newPhase);
144 }
145 newSeq.add(phase);
146 }
147 assert newSeq.contains(newPhase);
148 return newSeq;
149 }
151 CompilationSequence insertFirst(final CompilationPhase phase) {
152 final CompilationSequence newSeq = new CompilationSequence(this);
153 newSeq.addFirst(phase);
154 return newSeq;
155 }
157 CompilationSequence insertLast(final CompilationPhase phase) {
158 final CompilationSequence newSeq = new CompilationSequence(this);
159 newSeq.addLast(phase);
160 return newSeq;
161 }
162 }
164 /**
165 * Standard (non-lazy) compilation, that basically will take an entire script
166 * and JIT it at once. This can lead to long startup time and fewer type
167 * specializations
168 */
169 final static CompilationSequence SEQUENCE_EAGER = new CompilationSequence(
170 CompilationPhase.CONSTANT_FOLDING_PHASE,
171 CompilationPhase.LOWERING_PHASE,
172 CompilationPhase.ATTRIBUTION_PHASE,
173 CompilationPhase.SPLITTING_PHASE,
174 CompilationPhase.TYPE_FINALIZATION_PHASE,
175 CompilationPhase.BYTECODE_GENERATION_PHASE);
177 final static CompilationSequence SEQUENCE_LAZY =
178 SEQUENCE_EAGER.insertFirst(CompilationPhase.LAZY_INITIALIZATION_PHASE);
180 private static CompilationSequence sequence(final boolean lazy) {
181 return lazy ? SEQUENCE_LAZY : SEQUENCE_EAGER;
182 }
184 boolean isLazy() {
185 return sequence == SEQUENCE_LAZY;
186 }
188 private static String lazyTag(final FunctionNode functionNode) {
189 if (functionNode.isLazy()) {
190 return '$' + LAZY.tag() + '$' + functionNode.getName();
191 }
192 return "";
193 }
195 /**
196 * Constructor
197 *
198 * @param installer code installer
199 * @param functionNode function node (in any available {@link CompilationState}) to compile
200 * @param sequence {@link Compiler#CompilationSequence} of {@link CompilationPhase}s to apply as this compilation
201 * @param strict should this compilation use strict mode semantics
202 */
203 //TODO support an array of FunctionNodes for batch lazy compilation
204 Compiler(final ScriptEnvironment env, final CodeInstaller<ScriptEnvironment> installer, final FunctionNode functionNode, final CompilationSequence sequence, final boolean strict) {
205 this.env = env;
206 this.functionNode = functionNode;
207 this.sequence = sequence;
208 this.installer = installer;
209 this.strict = strict || functionNode.isStrictMode();
210 this.constantData = new ConstantData();
211 this.compileUnits = new HashSet<>();
212 this.bytecode = new HashMap<>();
214 final StringBuilder sb = new StringBuilder();
215 sb.append(functionNode.uniqueName(DEFAULT_SCRIPT_NAME.tag() + lazyTag(functionNode))).
216 append('$').
217 append(safeSourceName(functionNode.getSource()));
219 this.scriptName = sb.toString();
220 }
222 /**
223 * Constructor
224 *
225 * @param installer code installer
226 * @param functionNode function node (in any available {@link CompilationState}) to compile
227 * @param strict should this compilation use strict mode semantics
228 */
229 public Compiler(final CodeInstaller<ScriptEnvironment> installer, final FunctionNode functionNode, final boolean strict) {
230 this(installer.getOwner(), installer, functionNode, sequence(installer.getOwner()._lazy_compilation), strict);
231 }
233 /**
234 * Constructor - compilation will use the same strict semantics as in script environment
235 *
236 * @param installer code installer
237 * @param functionNode function node (in any available {@link CompilationState}) to compile
238 */
239 public Compiler(final CodeInstaller<ScriptEnvironment> installer, final FunctionNode functionNode) {
240 this(installer.getOwner(), installer, functionNode, sequence(installer.getOwner()._lazy_compilation), installer.getOwner()._strict);
241 }
243 /**
244 * Constructor - compilation needs no installer, but uses a script environment
245 * Used in "compile only" scenarios
246 * @param env a script environment
247 * @param functionNode functionNode to compile
248 */
249 public Compiler(final ScriptEnvironment env, final FunctionNode functionNode) {
250 this(env, null, functionNode, sequence(env._lazy_compilation), env._strict);
251 }
253 /**
254 * Execute the compilation this Compiler was created with
255 * @params param types if known, for specialization
256 * @throws CompilationException if something goes wrong
257 * @return this compiler, for possible chaining
258 */
259 public Compiler compile() throws CompilationException {
260 return compile(null);
261 }
263 /**
264 * Execute the compilation this Compiler was created with
265 * @param paramTypes param types if known, for specialization
266 * @throws CompilationException if something goes wrong
267 * @return this compiler, for possible chaining
268 */
269 public Compiler compile(final Class<?> paramTypes) throws CompilationException {
270 for (final String reservedName : RESERVED_NAMES) {
271 functionNode.uniqueName(reservedName);
272 }
274 final boolean fine = !LOG.levelAbove(Level.FINE);
275 final boolean info = !LOG.levelAbove(Level.INFO);
277 long time = 0L;
279 for (final CompilationPhase phase : sequence) {
280 phase.apply(this, functionNode);
282 final long duration = Timing.isEnabled() ? (phase.getEndTime() - phase.getStartTime()) : 0L;
283 time += duration;
285 if (fine) {
286 final StringBuilder sb = new StringBuilder();
288 sb.append(phase.toString()).
289 append(" done for function '").
290 append(functionNode.getName()).
291 append('\'');
293 if (duration > 0L) {
294 sb.append(" in ").
295 append(duration).
296 append(" ms ");
297 }
299 LOG.fine(sb.toString());
300 }
301 }
303 if (info) {
304 final StringBuilder sb = new StringBuilder();
305 sb.append("Compile job for '").
306 append(functionNode.getName()).
307 append("' finished");
309 if (time > 0L) {
310 sb.append(" in ").
311 append(time).
312 append(" ms");
313 }
315 LOG.info(sb.toString());
316 }
318 return this;
319 }
321 private Class<?> install(final String className, final byte[] code) {
322 LOG.fine("Installing class " + className);
324 final Class<?> clazz = installer.install(Compiler.binaryName(className), code);
326 try {
327 final Source source = getSource();
328 final Object[] constants = getConstantData().toArray();
329 // Need doPrivileged because these fields are private
330 AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
331 @Override
332 public Void run() throws Exception {
333 //use reflection to write source and constants table to installed classes
334 final Field sourceField = clazz.getDeclaredField(SOURCE.tag());
335 final Field constantsField = clazz.getDeclaredField(CONSTANTS.tag());
336 sourceField.setAccessible(true);
337 constantsField.setAccessible(true);
338 sourceField.set(null, source);
339 constantsField.set(null, constants);
340 return null;
341 }
342 });
343 } catch (final PrivilegedActionException e) {
344 throw new RuntimeException(e);
345 }
347 return clazz;
348 }
350 /**
351 * Install compiled classes into a given loader
352 * @return root script class - if there are several compile units they will also be installed
353 */
354 public Class<?> install() {
355 final long t0 = Timing.isEnabled() ? System.currentTimeMillis() : 0L;
357 assert functionNode.hasState(CompilationState.EMITTED) : functionNode.getName() + " has no bytecode and cannot be installed";
359 final Map<String, Class<?>> installedClasses = new HashMap<>();
361 final String rootClassName = firstCompileUnitName();
362 final byte[] rootByteCode = bytecode.get(rootClassName);
363 final Class<?> rootClass = install(rootClassName, rootByteCode);
365 int length = rootByteCode.length;
367 installedClasses.put(rootClassName, rootClass);
369 for (final Entry<String, byte[]> entry : bytecode.entrySet()) {
370 final String className = entry.getKey();
371 if (className.equals(rootClassName)) {
372 continue;
373 }
374 final byte[] code = entry.getValue();
375 length += code.length;
377 installedClasses.put(className, install(className, code));
378 }
380 for (final CompileUnit unit : compileUnits) {
381 unit.setCode(installedClasses.get(unit.getUnitClassName()));
382 }
384 functionNode.accept(new NodeVisitor() {
385 @Override
386 public Node enter(final FunctionNode node) {
387 if (node.isLazy()) {
388 return null;
389 }
390 node.setState(CompilationState.INSTALLED);
391 return node;
392 }
393 });
395 final StringBuilder sb;
396 if (LOG.isEnabled()) {
397 sb = new StringBuilder();
398 sb.append("Installed class '").
399 append(rootClass.getSimpleName()).
400 append('\'').
401 append(" bytes=").
402 append(length).
403 append('.');
404 if (bytecode.size() > 1) {
405 sb.append(' ').append(bytecode.size()).append(" compile units.");
406 }
407 } else {
408 sb = null;
409 }
411 if (Timing.isEnabled()) {
412 final long duration = System.currentTimeMillis() - t0;
413 Timing.accumulateTime("[Code Installation]", duration);
414 if (sb != null) {
415 sb.append(" Install time: ").append(duration).append(" ms");
416 }
417 }
419 if (sb != null) {
420 LOG.info(sb.toString());
421 }
423 return rootClass;
424 }
426 Set<CompileUnit> getCompileUnits() {
427 return compileUnits;
428 }
430 boolean getStrictMode() {
431 return strict;
432 }
434 void setStrictMode(final boolean strict) {
435 this.strict = strict;
436 }
438 FunctionNode getFunctionNode() {
439 return functionNode;
440 }
442 ConstantData getConstantData() {
443 return constantData;
444 }
446 CodeInstaller<ScriptEnvironment> getCodeInstaller() {
447 return installer;
448 }
450 Source getSource() {
451 return functionNode.getSource();
452 }
454 void addClass(final String name, final byte[] code) {
455 bytecode.put(name, code);
456 }
458 ScriptEnvironment getEnv() {
459 return this.env;
460 }
462 private static String safeSourceName(final Source source) {
463 String baseName = new File(source.getName()).getName();
465 final int index = baseName.lastIndexOf(".js");
466 if (index != -1) {
467 baseName = baseName.substring(0, index);
468 }
470 baseName = baseName.replace('.', '_').replace('-', '_');
471 final String mangled = NameCodec.encode(baseName);
473 return mangled != null ? mangled : baseName;
474 }
476 private int nextCompileUnitIndex() {
477 return compileUnits.size() + 1;
478 }
480 String firstCompileUnitName() {
481 return SCRIPTS_PACKAGE + '/' + scriptName;
482 }
484 private String nextCompileUnitName() {
485 return firstCompileUnitName() + '$' + nextCompileUnitIndex();
486 }
488 CompileUnit addCompileUnit(final long initialWeight) {
489 return addCompileUnit(nextCompileUnitName(), initialWeight);
490 }
492 CompileUnit addCompileUnit(final String unitClassName) {
493 return addCompileUnit(unitClassName, 0L);
494 }
496 private CompileUnit addCompileUnit(final String unitClassName, final long initialWeight) {
497 final CompileUnit compileUnit = initCompileUnit(unitClassName, initialWeight);
498 compileUnits.add(compileUnit);
499 LOG.fine("Added compile unit " + compileUnit);
500 return compileUnit;
501 }
503 private CompileUnit initCompileUnit(final String unitClassName, final long initialWeight) {
504 final ClassEmitter classEmitter = new ClassEmitter(env, functionNode.getSource().getName(), unitClassName, strict);
505 final CompileUnit compileUnit = new CompileUnit(unitClassName, classEmitter, initialWeight);
507 classEmitter.begin();
509 final MethodEmitter initMethod = classEmitter.init(EnumSet.of(Flag.PRIVATE));
510 initMethod.begin();
511 initMethod.load(Type.OBJECT, 0);
512 initMethod.newInstance(jdk.nashorn.internal.scripts.JS.class);
513 initMethod.returnVoid();
514 initMethod.end();
516 return compileUnit;
517 }
519 CompileUnit findUnit(final long weight) {
520 for (final CompileUnit unit : compileUnits) {
521 if (unit.canHold(weight)) {
522 unit.addWeight(weight);
523 return unit;
524 }
525 }
527 return addCompileUnit(weight);
528 }
530 /**
531 * Convert a package/class name to a binary name.
532 *
533 * @param name Package/class name.
534 * @return Binary name.
535 */
536 public static String binaryName(final String name) {
537 return name.replace('/', '.');
538 }
540 /**
541 * Should we use integers for arithmetic operations as well?
542 * TODO: We currently generate no overflow checks so this is
543 * disabled
544 *
545 * @return true if arithmetic operations should not widen integer
546 * operands by default.
547 */
548 static boolean shouldUseIntegerArithmetic() {
549 return USE_INT_ARITH;
550 }
552 private static final boolean USE_INT_ARITH;
554 static {
555 USE_INT_ARITH = Options.getBooleanProperty("nashorn.compiler.intarithmetic");
556 assert !USE_INT_ARITH : "Integer arithmetic is not enabled";
557 }
560 }