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.CompilerConstants.ARGUMENTS; 29 import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE; 30 import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS; 31 import static jdk.nashorn.internal.codegen.CompilerConstants.DEFAULT_SCRIPT_NAME; 32 import static jdk.nashorn.internal.codegen.CompilerConstants.LAZY; 33 import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN; 34 import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; 35 import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE; 36 import static jdk.nashorn.internal.codegen.CompilerConstants.THIS; 37 import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS; 38 39 import java.io.File; 40 import java.lang.reflect.Field; 41 import java.security.AccessController; 42 import java.security.PrivilegedActionException; 43 import java.security.PrivilegedExceptionAction; 44 import java.util.Arrays; 45 import java.util.Collections; 46 import java.util.Comparator; 47 import java.util.EnumSet; 48 import java.util.HashMap; 49 import java.util.LinkedHashMap; 50 import java.util.LinkedList; 51 import java.util.List; 52 import java.util.Map; 53 import java.util.Map.Entry; 54 import java.util.Set; 55 import java.util.TreeSet; 56 import java.util.logging.Level; 57 import jdk.internal.dynalink.support.NameCodec; 58 import jdk.nashorn.internal.codegen.ClassEmitter.Flag; 59 import jdk.nashorn.internal.codegen.types.Type; 60 import jdk.nashorn.internal.ir.FunctionNode; 61 import jdk.nashorn.internal.ir.FunctionNode.CompilationState; 62 import jdk.nashorn.internal.ir.TemporarySymbols; 63 import jdk.nashorn.internal.ir.debug.ClassHistogramElement; 64 import jdk.nashorn.internal.ir.debug.ObjectSizeCalculator; 65 import jdk.nashorn.internal.runtime.CodeInstaller; 66 import jdk.nashorn.internal.runtime.DebugLogger; 67 import jdk.nashorn.internal.runtime.ScriptEnvironment; 68 import jdk.nashorn.internal.runtime.Source; 69 import jdk.nashorn.internal.runtime.Timing; 70 import jdk.nashorn.internal.runtime.options.Options; 71 72 /** 73 * Responsible for converting JavaScripts to java byte code. Main entry 74 * point for code generator. The compiler may also install classes given some 75 * predefined Code installation policy, given to it at construction time. 76 * @see CodeInstaller 77 */ 78 public final class Compiler { 79 80 /** Name of the scripts package */ 81 public static final String SCRIPTS_PACKAGE = "jdk/nashorn/internal/scripts"; 82 83 /** Name of the objects package */ 84 public static final String OBJECTS_PACKAGE = "jdk/nashorn/internal/objects"; 85 86 private Source source; 87 88 private String sourceName; 89 90 private final Map<String, byte[]> bytecode; 91 92 private final Set<CompileUnit> compileUnits; 93 94 private final ConstantData constantData; 95 96 private final CompilationSequence sequence; 97 98 private final ScriptEnvironment env; 99 100 private String scriptName; 101 102 private boolean strict; 103 104 private final CodeInstaller<ScriptEnvironment> installer; 105 106 private final TemporarySymbols temporarySymbols = new TemporarySymbols(); 107 108 /** logger for compiler, trampolines, splits and related code generation events 109 * that affect classes */ 110 public static final DebugLogger LOG = new DebugLogger("compiler"); 111 112 /** 113 * This array contains names that need to be reserved at the start 114 * of a compile, to avoid conflict with variable names later introduced. 115 * See {@link CompilerConstants} for special names used for structures 116 * during a compile. 117 */ 118 private static String[] RESERVED_NAMES = { 119 SCOPE.symbolName(), 120 THIS.symbolName(), 121 RETURN.symbolName(), 122 CALLEE.symbolName(), 123 VARARGS.symbolName(), 124 ARGUMENTS.symbolName() 125 }; 126 127 /** 128 * This class makes it possible to do your own compilation sequence 129 * from the code generation package. There are predefined compilation 130 * sequences already 131 */ 132 @SuppressWarnings("serial") 133 static class CompilationSequence extends LinkedList<CompilationPhase> { 134 135 CompilationSequence(final CompilationPhase... phases) { 136 super(Arrays.asList(phases)); 137 } 138 139 CompilationSequence(final CompilationSequence sequence) { 140 this(sequence.toArray(new CompilationPhase[sequence.size()])); 141 } 142 143 CompilationSequence insertAfter(final CompilationPhase phase, final CompilationPhase newPhase) { 144 final CompilationSequence newSeq = new CompilationSequence(); 145 for (final CompilationPhase elem : this) { 146 newSeq.add(phase); 147 if (elem.equals(phase)) { 148 newSeq.add(newPhase); 149 } 150 } 151 assert newSeq.contains(newPhase); 152 return newSeq; 153 } 154 155 CompilationSequence insertBefore(final CompilationPhase phase, final CompilationPhase newPhase) { 156 final CompilationSequence newSeq = new CompilationSequence(); 157 for (final CompilationPhase elem : this) { 158 if (elem.equals(phase)) { 159 newSeq.add(newPhase); 160 } 161 newSeq.add(phase); 162 } 163 assert newSeq.contains(newPhase); 164 return newSeq; 165 } 166 167 CompilationSequence insertFirst(final CompilationPhase phase) { 168 final CompilationSequence newSeq = new CompilationSequence(this); 169 newSeq.addFirst(phase); 170 return newSeq; 171 } 172 173 CompilationSequence insertLast(final CompilationPhase phase) { 174 final CompilationSequence newSeq = new CompilationSequence(this); 175 newSeq.addLast(phase); 176 return newSeq; 177 } 178 } 179 180 /** 181 * Environment information known to the compile, e.g. params 182 */ 183 public static class Hints { 184 private final Type[] paramTypes; 185 186 /** singleton empty hints */ 187 public static final Hints EMPTY = new Hints(); 188 189 private Hints() { 190 this.paramTypes = null; 191 } 192 193 /** 194 * Constructor 195 * @param paramTypes known parameter types for this callsite 196 */ 197 public Hints(final Type[] paramTypes) { 198 this.paramTypes = paramTypes; 199 } 200 201 /** 202 * Get the parameter type for this parameter position, or 203 * null if now known 204 * @param pos position 205 * @return parameter type for this callsite if known 206 */ 207 public Type getParameterType(final int pos) { 208 if (paramTypes != null && pos < paramTypes.length) { 209 return paramTypes[pos]; 210 } 211 return null; 212 } 213 } 214 215 /** 216 * Standard (non-lazy) compilation, that basically will take an entire script 217 * and JIT it at once. This can lead to long startup time and fewer type 218 * specializations 219 */ 220 final static CompilationSequence SEQUENCE_EAGER = new CompilationSequence( 221 CompilationPhase.CONSTANT_FOLDING_PHASE, 222 CompilationPhase.LOWERING_PHASE, 223 CompilationPhase.ATTRIBUTION_PHASE, 224 CompilationPhase.RANGE_ANALYSIS_PHASE, 225 CompilationPhase.SPLITTING_PHASE, 226 CompilationPhase.TYPE_FINALIZATION_PHASE, 227 CompilationPhase.BYTECODE_GENERATION_PHASE); 228 229 final static CompilationSequence SEQUENCE_LAZY = 230 SEQUENCE_EAGER.insertFirst(CompilationPhase.LAZY_INITIALIZATION_PHASE); 231 232 private static CompilationSequence sequence(final boolean lazy) { 233 return lazy ? SEQUENCE_LAZY : SEQUENCE_EAGER; 234 } 235 236 boolean isLazy() { 237 return sequence == SEQUENCE_LAZY; 238 } 239 240 private static String lazyTag(final FunctionNode functionNode) { 241 if (functionNode.isLazy()) { 242 return '$' + LAZY.symbolName() + '$' + functionNode.getName(); 243 } 244 return ""; 245 } 246 247 /** 248 * Constructor 249 * 250 * @param env script environment 251 * @param installer code installer 252 * @param sequence {@link Compiler.CompilationSequence} of {@link CompilationPhase}s to apply as this compilation 253 * @param strict should this compilation use strict mode semantics 254 */ 255 //TODO support an array of FunctionNodes for batch lazy compilation 256 Compiler(final ScriptEnvironment env, final CodeInstaller<ScriptEnvironment> installer, final CompilationSequence sequence, final boolean strict) { 257 this.env = env; 258 this.sequence = sequence; 259 this.installer = installer; 260 this.constantData = new ConstantData(); 261 this.compileUnits = new TreeSet<>(); 262 this.bytecode = new LinkedHashMap<>(); 263 } 264 265 private void initCompiler(final FunctionNode functionNode) { 266 this.strict = strict || functionNode.isStrict(); 267 final StringBuilder sb = new StringBuilder(); 268 sb.append(functionNode.uniqueName(DEFAULT_SCRIPT_NAME.symbolName() + lazyTag(functionNode))). 269 append('$'). 270 append(safeSourceName(functionNode.getSource())); 271 this.source = functionNode.getSource(); 272 this.sourceName = functionNode.getSourceName(); 273 this.scriptName = sb.toString(); 274 } 275 276 /** 277 * Constructor 278 * 279 * @param installer code installer 280 * @param strict should this compilation use strict mode semantics 281 */ 282 public Compiler(final CodeInstaller<ScriptEnvironment> installer, final boolean strict) { 283 this(installer.getOwner(), installer, sequence(installer.getOwner()._lazy_compilation), strict); 284 } 285 286 /** 287 * Constructor - compilation will use the same strict semantics as in script environment 288 * 289 * @param installer code installer 290 */ 291 public Compiler(final CodeInstaller<ScriptEnvironment> installer) { 292 this(installer.getOwner(), installer, sequence(installer.getOwner()._lazy_compilation), installer.getOwner()._strict); 293 } 294 295 /** 296 * Constructor - compilation needs no installer, but uses a script environment 297 * Used in "compile only" scenarios 298 * @param env a script environment 299 */ 300 public Compiler(final ScriptEnvironment env) { 301 this(env, null, sequence(env._lazy_compilation), env._strict); 302 } 303 304 private static void printMemoryUsage(final String phaseName, final FunctionNode functionNode) { 305 LOG.info(phaseName + " finished. Doing IR size calculation..."); 306 307 final ObjectSizeCalculator osc = new ObjectSizeCalculator(ObjectSizeCalculator.getEffectiveMemoryLayoutSpecification()); 308 osc.calculateObjectSize(functionNode); 309 310 final List<ClassHistogramElement> list = osc.getClassHistogram(); 311 312 final StringBuilder sb = new StringBuilder(); 313 final long totalSize = osc.calculateObjectSize(functionNode); 314 sb.append(phaseName).append(" Total size = ").append(totalSize / 1024 / 1024).append("MB"); 315 LOG.info(sb); 316 317 Collections.sort(list, new Comparator<ClassHistogramElement>() { 318 @Override 319 public int compare(ClassHistogramElement o1, ClassHistogramElement o2) { 320 final long diff = o1.getBytes() - o2.getBytes(); 321 if (diff < 0) { 322 return 1; 323 } else if (diff > 0) { 324 return -1; 325 } else { 326 return 0; 327 } 328 } 329 }); 330 for (final ClassHistogramElement e : list) { 331 final String line = String.format(" %-48s %10d bytes (%8d instances)", e.getClazz(), e.getBytes(), e.getInstances()); 332 LOG.info(line); 333 if (e.getBytes() < totalSize / 200) { 334 LOG.info(" ..."); 335 break; // never mind, so little memory anyway 336 } 337 } 338 } 339 340 /** 341 * Execute the compilation this Compiler was created with 342 * @param functionNode function node to compile from its current state 343 * @throws CompilationException if something goes wrong 344 * @return function node that results from code transforms 345 */ 346 public FunctionNode compile(final FunctionNode functionNode) throws CompilationException { 347 FunctionNode newFunctionNode = functionNode; 348 349 initCompiler(newFunctionNode); //TODO move this state into functionnode? 350 351 for (final String reservedName : RESERVED_NAMES) { 352 newFunctionNode.uniqueName(reservedName); 353 } 354 355 final boolean fine = !LOG.levelAbove(Level.FINE); 356 final boolean info = !LOG.levelAbove(Level.INFO); 357 358 long time = 0L; 359 360 for (final CompilationPhase phase : sequence) { 361 newFunctionNode = phase.apply(this, newFunctionNode); 362 363 if (env._print_mem_usage) { 364 printMemoryUsage(phase.toString(), newFunctionNode); 365 } 366 367 final long duration = Timing.isEnabled() ? (phase.getEndTime() - phase.getStartTime()) : 0L; 368 time += duration; 369 370 if (fine) { 371 final StringBuilder sb = new StringBuilder(); 372 373 sb.append(phase.toString()). 374 append(" done for function '"). 375 append(newFunctionNode.getName()). 376 append('\''); 377 378 if (duration > 0L) { 379 sb.append(" in "). 380 append(duration). 381 append(" ms "); 382 } 383 384 LOG.fine(sb); 385 } 386 } 387 388 if (info) { 389 final StringBuilder sb = new StringBuilder(); 390 sb.append("Compile job for '"). 391 append(newFunctionNode.getSource()). 392 append(':'). 393 append(newFunctionNode.getName()). 394 append("' finished"); 395 396 if (time > 0L) { 397 sb.append(" in "). 398 append(time). 399 append(" ms"); 400 } 401 402 LOG.info(sb); 403 } 404 405 return newFunctionNode; 406 } 407 408 private Class<?> install(final String className, final byte[] code) { 409 LOG.fine("Installing class ", className); 410 411 final Class<?> clazz = installer.install(Compiler.binaryName(className), code); 412 413 try { 414 final Object[] constants = getConstantData().toArray(); 415 // Need doPrivileged because these fields are private 416 AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() { 417 @Override 418 public Void run() throws Exception { 419 //use reflection to write source and constants table to installed classes 420 final Field sourceField = clazz.getDeclaredField(SOURCE.symbolName()); 421 final Field constantsField = clazz.getDeclaredField(CONSTANTS.symbolName()); 422 sourceField.setAccessible(true); 423 constantsField.setAccessible(true); 424 sourceField.set(null, source); 425 constantsField.set(null, constants); 426 return null; 427 } 428 }); 429 } catch (final PrivilegedActionException e) { 430 throw new RuntimeException(e); 431 } 432 433 return clazz; 434 } 435 436 /** 437 * Install compiled classes into a given loader 438 * @param functionNode function node to install - must be in {@link CompilationState#EMITTED} state 439 * @return root script class - if there are several compile units they will also be installed 440 */ 441 public Class<?> install(final FunctionNode functionNode) { 442 final long t0 = Timing.isEnabled() ? System.currentTimeMillis() : 0L; 443 444 assert functionNode.hasState(CompilationState.EMITTED) : functionNode.getName() + " has no bytecode and cannot be installed"; 445 446 final Map<String, Class<?>> installedClasses = new HashMap<>(); 447 448 final String rootClassName = firstCompileUnitName(); 449 final byte[] rootByteCode = bytecode.get(rootClassName); 450 final Class<?> rootClass = install(rootClassName, rootByteCode); 451 452 int length = rootByteCode.length; 453 454 installedClasses.put(rootClassName, rootClass); 455 456 for (final Entry<String, byte[]> entry : bytecode.entrySet()) { 457 final String className = entry.getKey(); 458 if (className.equals(rootClassName)) { 459 continue; 460 } 461 final byte[] code = entry.getValue(); 462 length += code.length; 463 464 installedClasses.put(className, install(className, code)); 465 } 466 467 for (final CompileUnit unit : compileUnits) { 468 unit.setCode(installedClasses.get(unit.getUnitClassName())); 469 } 470 471 final StringBuilder sb; 472 if (LOG.isEnabled()) { 473 sb = new StringBuilder(); 474 sb.append("Installed class '"). 475 append(rootClass.getSimpleName()). 476 append('\''). 477 append(" bytes="). 478 append(length). 479 append('.'); 480 if (bytecode.size() > 1) { 481 sb.append(' ').append(bytecode.size()).append(" compile units."); 482 } 483 } else { 484 sb = null; 485 } 486 487 if (Timing.isEnabled()) { 488 final long duration = System.currentTimeMillis() - t0; 489 Timing.accumulateTime("[Code Installation]", duration); 490 if (sb != null) { 491 sb.append(" Install time: ").append(duration).append(" ms"); 492 } 493 } 494 495 if (sb != null) { 496 LOG.fine(sb); 497 } 498 499 return rootClass; 500 } 501 502 Set<CompileUnit> getCompileUnits() { 503 return compileUnits; 504 } 505 506 boolean getStrictMode() { 507 return strict; 508 } 509 510 void setStrictMode(final boolean strict) { 511 this.strict = strict; 512 } 513 514 ConstantData getConstantData() { 515 return constantData; 516 } 517 518 CodeInstaller<ScriptEnvironment> getCodeInstaller() { 519 return installer; 520 } 521 522 TemporarySymbols getTemporarySymbols() { 523 return temporarySymbols; 524 } 525 526 void addClass(final String name, final byte[] code) { 527 bytecode.put(name, code); 528 } 529 530 ScriptEnvironment getEnv() { 531 return this.env; 532 } 533 534 private String safeSourceName(final Source src) { 535 String baseName = new File(src.getName()).getName(); 536 537 final int index = baseName.lastIndexOf(".js"); 538 if (index != -1) { 539 baseName = baseName.substring(0, index); 540 } 541 542 baseName = baseName.replace('.', '_').replace('-', '_'); 543 if (! env._loader_per_compile) { 544 baseName = baseName + installer.getUniqueScriptId(); 545 } 546 final String mangled = NameCodec.encode(baseName); 547 548 return mangled != null ? mangled : baseName; 549 } 550 551 private int nextCompileUnitIndex() { 552 return compileUnits.size() + 1; 553 } 554 555 String firstCompileUnitName() { 556 return SCRIPTS_PACKAGE + '/' + scriptName; 557 } 558 559 private String nextCompileUnitName() { 560 return firstCompileUnitName() + '$' + nextCompileUnitIndex(); 561 } 562 563 CompileUnit addCompileUnit(final long initialWeight) { 564 return addCompileUnit(nextCompileUnitName(), initialWeight); 565 } 566 567 CompileUnit addCompileUnit(final String unitClassName) { 568 return addCompileUnit(unitClassName, 0L); 569 } 570 571 private CompileUnit addCompileUnit(final String unitClassName, final long initialWeight) { 572 final CompileUnit compileUnit = initCompileUnit(unitClassName, initialWeight); 573 compileUnits.add(compileUnit); 574 LOG.fine("Added compile unit ", compileUnit); 575 return compileUnit; 576 } 577 578 private CompileUnit initCompileUnit(final String unitClassName, final long initialWeight) { 579 final ClassEmitter classEmitter = new ClassEmitter(env, sourceName, unitClassName, strict); 580 final CompileUnit compileUnit = new CompileUnit(unitClassName, classEmitter, initialWeight); 581 582 classEmitter.begin(); 583 584 final MethodEmitter initMethod = classEmitter.init(EnumSet.of(Flag.PRIVATE)); 585 initMethod.begin(); 586 initMethod.load(Type.OBJECT, 0); 587 initMethod.newInstance(jdk.nashorn.internal.scripts.JS.class); 588 initMethod.returnVoid(); 589 initMethod.end(); 590 591 return compileUnit; 592 } 593 594 CompileUnit findUnit(final long weight) { 595 for (final CompileUnit unit : compileUnits) { 596 if (unit.canHold(weight)) { 597 unit.addWeight(weight); 598 return unit; 599 } 600 } 601 602 return addCompileUnit(weight); 603 } 604 605 /** 606 * Convert a package/class name to a binary name. 607 * 608 * @param name Package/class name. 609 * @return Binary name. 610 */ 611 public static String binaryName(final String name) { 612 return name.replace('/', '.'); 613 } 614 615 /** 616 * Should we use integers for arithmetic operations as well? 617 * TODO: We currently generate no overflow checks so this is 618 * disabled 619 * 620 * @return true if arithmetic operations should not widen integer 621 * operands by default. 622 */ 623 static boolean shouldUseIntegerArithmetic() { 624 return USE_INT_ARITH; 625 } 626 627 private static final boolean USE_INT_ARITH; 628 629 static { 630 USE_INT_ARITH = Options.getBooleanProperty("nashorn.compiler.intarithmetic"); 631 assert !USE_INT_ARITH : "Integer arithmetic is not enabled"; 632 } 633 }