src/jdk/nashorn/internal/codegen/Compiler.java

Thu, 14 Mar 2013 14:49:55 +0100

author
lagergren
date
Thu, 14 Mar 2013 14:49:55 +0100
changeset 139
390d44ba90cf
parent 137
e15806b9d716
child 144
4be452026847
permissions
-rw-r--r--

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 }

mercurial