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