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