1 /*
   2  * Copyright (c) 2015, 2016, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package jdk.vm.ci.hotspot;
  24 
  25 import static jdk.vm.ci.common.InitTimer.timer;
  26 
  27 import java.io.IOException;
  28 import java.io.OutputStream;
  29 import java.io.PrintStream;
  30 import java.util.Collections;
  31 import java.util.HashMap;
  32 import java.util.List;
  33 import java.util.Map;
  34 import java.util.Objects;
  35 import java.util.ServiceLoader;
  36 import java.util.TreeMap;
  37 
  38 import jdk.internal.misc.VM;
  39 import jdk.vm.ci.code.Architecture;
  40 import jdk.vm.ci.code.CompilationRequestResult;
  41 import jdk.vm.ci.code.CompiledCode;
  42 import jdk.vm.ci.code.InstalledCode;
  43 import jdk.vm.ci.common.InitTimer;
  44 import jdk.vm.ci.common.JVMCIError;
  45 import jdk.vm.ci.hotspot.HotSpotJVMCICompilerFactory.CompilationLevel;
  46 import jdk.vm.ci.meta.JavaKind;
  47 import jdk.vm.ci.meta.JavaType;
  48 import jdk.vm.ci.meta.ResolvedJavaType;
  49 import jdk.vm.ci.runtime.JVMCI;
  50 import jdk.vm.ci.runtime.JVMCIBackend;
  51 import jdk.vm.ci.runtime.JVMCICompiler;
  52 import jdk.vm.ci.runtime.JVMCICompilerFactory;
  53 import jdk.vm.ci.services.JVMCIServiceLocator;
  54 
  55 /**
  56  * HotSpot implementation of a JVMCI runtime.
  57  *
  58  * The initialization of this class is very fragile since it's initialized both through
  59  * {@link JVMCI#initialize()} or through calling {@link HotSpotJVMCIRuntime#runtime()} and
  60  * {@link HotSpotJVMCIRuntime#runtime()} is also called by {@link JVMCI#initialize()}. So this class
  61  * can't have a static initializer and any required initialization must be done as part of
  62  * {@link #runtime()}. This allows the initialization to funnel back through
  63  * {@link JVMCI#initialize()} without deadlocking.
  64  */
  65 public final class HotSpotJVMCIRuntime implements HotSpotJVMCIRuntimeProvider {
  66 
  67     @SuppressWarnings("try")
  68     static class DelayedInit {
  69         private static final HotSpotJVMCIRuntime instance;
  70 
  71         static {
  72             try (InitTimer t = timer("HotSpotJVMCIRuntime.<init>")) {
  73                 instance = new HotSpotJVMCIRuntime();
  74             }
  75         }
  76     }
  77 
  78     /**
  79      * Gets the singleton {@link HotSpotJVMCIRuntime} object.
  80      */
  81     public static HotSpotJVMCIRuntime runtime() {
  82         JVMCI.initialize();
  83         return DelayedInit.instance;
  84     }
  85 
  86     /**
  87      * A list of all supported JVMCI options.
  88      */
  89     public enum Option {
  90         // @formatter:off
  91         Compiler(String.class, null, "Selects the system compiler. This must match the getCompilerName() value returned " +
  92                                      "by a jdk.vm.ci.runtime.JVMCICompilerFactory provider. " +
  93                                      "An empty string or the value \"null\" selects a compiler " +
  94                                      "that will raise an exception upon receiving a compilation request."),
  95         // Note: The following one is not used (see InitTimer.ENABLED). It is added here
  96         // so that -XX:+JVMCIPrintProperties shows the option.
  97         InitTimer(Boolean.class, false, "Specifies if initialization timing is enabled."),
  98         PrintConfig(Boolean.class, false, "Prints VM configuration available via JVMCI."),
  99         TraceMethodDataFilter(String.class, null,
 100                         "Enables tracing of profiling info when read by JVMCI.",
 101                         "Empty value: trace all methods",
 102                         "Non-empty value: trace methods whose fully qualified name contains the value."),
 103         UseProfilingInformation(Boolean.class, true, "");
 104         // @formatter:on
 105 
 106         /**
 107          * The prefix for system properties that are JVMCI options.
 108          */
 109         private static final String JVMCI_OPTION_PROPERTY_PREFIX = "jvmci.";
 110 
 111         /**
 112          * Marker for uninitialized flags.
 113          */
 114         private static final String UNINITIALIZED = "UNINITIALIZED";
 115 
 116         private final Class<?> type;
 117         private Object value;
 118         private final Object defaultValue;
 119         private boolean isDefault;
 120         private final String[] helpLines;
 121 
 122         Option(Class<?> type, Object defaultValue, String... helpLines) {
 123             assert Character.isUpperCase(name().charAt(0)) : "Option name must start with upper-case letter: " + name();
 124             this.type = type;
 125             this.value = UNINITIALIZED;
 126             this.defaultValue = defaultValue;
 127             this.helpLines = helpLines;
 128         }
 129 
 130         @SuppressFBWarnings(value = "ES_COMPARING_STRINGS_WITH_EQ", justification = "sentinel must be String since it's a static final in an enum")
 131         private Object getValue() {
 132             if (value == UNINITIALIZED) {
 133                 String propertyValue = VM.getSavedProperty(getPropertyName());
 134                 if (propertyValue == null) {
 135                     this.value = defaultValue;
 136                     this.isDefault = true;
 137                 } else {
 138                     if (type == Boolean.class) {
 139                         this.value = Boolean.parseBoolean(propertyValue);
 140                     } else if (type == String.class) {
 141                         this.value = propertyValue;
 142                     } else {
 143                         throw new JVMCIError("Unexpected option type " + type);
 144                     }
 145                     this.isDefault = false;
 146                 }
 147                 // Saved properties should not be interned - let's be sure
 148                 assert value != UNINITIALIZED;
 149             }
 150             return value;
 151         }
 152 
 153         /**
 154          * Gets the name of system property from which this option gets its value.
 155          */
 156         public String getPropertyName() {
 157             return JVMCI_OPTION_PROPERTY_PREFIX + name();
 158         }
 159 
 160         /**
 161          * Returns the option's value as boolean.
 162          *
 163          * @return option's value
 164          */
 165         public boolean getBoolean() {
 166             return (boolean) getValue();
 167         }
 168 
 169         /**
 170          * Returns the option's value as String.
 171          *
 172          * @return option's value
 173          */
 174         public String getString() {
 175             return (String) getValue();
 176         }
 177 
 178         private static final int PROPERTY_LINE_WIDTH = 80;
 179         private static final int PROPERTY_HELP_INDENT = 10;
 180 
 181         /**
 182          * Prints a description of the properties used to configure shared JVMCI code.
 183          *
 184          * @param out stream to print to
 185          */
 186         public static void printProperties(PrintStream out) {
 187             out.println("[JVMCI properties]");
 188             Option[] values = values();
 189             for (Option option : values) {
 190                 Object value = option.getValue();
 191                 if (value instanceof String) {
 192                     value = '"' + String.valueOf(value) + '"';
 193                 }
 194 
 195                 String name = option.getPropertyName();
 196                 String assign = option.isDefault ? "=" : ":=";
 197                 String typeName = option.type.getSimpleName();
 198                 String linePrefix = String.format("%s %s %s ", name, assign, value);
 199                 int typeStartPos = PROPERTY_LINE_WIDTH - typeName.length();
 200                 int linePad = typeStartPos - linePrefix.length();
 201                 if (linePad > 0) {
 202                     out.printf("%s%-" + linePad + "s[%s]%n", linePrefix, "", typeName);
 203                 } else {
 204                     out.printf("%s[%s]%n", linePrefix, typeName);
 205                 }
 206                 for (String line : option.helpLines) {
 207                     out.printf("%" + PROPERTY_HELP_INDENT + "s%s%n", "", line);
 208                 }
 209             }
 210         }
 211     }
 212 
 213     public static HotSpotJVMCIBackendFactory findFactory(String architecture) {
 214         for (HotSpotJVMCIBackendFactory factory : ServiceLoader.load(HotSpotJVMCIBackendFactory.class, ClassLoader.getSystemClassLoader())) {
 215             if (factory.getArchitecture().equalsIgnoreCase(architecture)) {
 216                 return factory;
 217             }
 218         }
 219 
 220         throw new JVMCIError("No JVMCI runtime available for the %s architecture", architecture);
 221     }
 222 
 223     /**
 224      * Gets the kind of a word value on the {@linkplain #getHostJVMCIBackend() host} backend.
 225      */
 226     public static JavaKind getHostWordKind() {
 227         return runtime().getHostJVMCIBackend().getCodeCache().getTarget().wordJavaKind;
 228     }
 229 
 230     protected final CompilerToVM compilerToVm;
 231 
 232     protected final HotSpotVMConfigStore configStore;
 233     protected final HotSpotVMConfig config;
 234     private final JVMCIBackend hostBackend;
 235 
 236     private final JVMCICompilerFactory compilerFactory;
 237     private final HotSpotJVMCICompilerFactory hsCompilerFactory;
 238     private volatile JVMCICompiler compiler;
 239     protected final HotSpotJVMCIMetaAccessContext metaAccessContext;
 240 
 241     /**
 242      * Stores the result of {@link HotSpotJVMCICompilerFactory#getCompilationLevelAdjustment} so
 243      * that it can be read from the VM.
 244      */
 245     @SuppressWarnings("unused") private final int compilationLevelAdjustment;
 246 
 247     private final Map<Class<? extends Architecture>, JVMCIBackend> backends = new HashMap<>();
 248 
 249     private volatile List<HotSpotVMEventListener> vmEventListeners;
 250 
 251     private Iterable<HotSpotVMEventListener> getVmEventListeners() {
 252         if (vmEventListeners == null) {
 253             synchronized (this) {
 254                 if (vmEventListeners == null) {
 255                     vmEventListeners = JVMCIServiceLocator.getProviders(HotSpotVMEventListener.class);
 256                 }
 257             }
 258         }
 259         return vmEventListeners;
 260     }
 261 
 262     /**
 263      * Stores the result of {@link HotSpotJVMCICompilerFactory#getTrivialPrefixes()} so that it can
 264      * be read from the VM.
 265      */
 266     @SuppressWarnings("unused") private final String[] trivialPrefixes;
 267 
 268     @SuppressWarnings("try")
 269     private HotSpotJVMCIRuntime() {
 270         compilerToVm = new CompilerToVM();
 271 
 272         try (InitTimer t = timer("HotSpotVMConfig<init>")) {
 273             configStore = new HotSpotVMConfigStore(compilerToVm);
 274             config = new HotSpotVMConfig(configStore);
 275         }
 276 
 277         String hostArchitecture = config.getHostArchitectureName();
 278 
 279         HotSpotJVMCIBackendFactory factory;
 280         try (InitTimer t = timer("find factory:", hostArchitecture)) {
 281             factory = findFactory(hostArchitecture);
 282         }
 283 
 284         try (InitTimer t = timer("create JVMCI backend:", hostArchitecture)) {
 285             hostBackend = registerBackend(factory.createJVMCIBackend(this, null));
 286         }
 287 
 288         metaAccessContext = new HotSpotJVMCIMetaAccessContext();
 289 
 290         compilerFactory = HotSpotJVMCICompilerConfig.getCompilerFactory();
 291         if (compilerFactory instanceof HotSpotJVMCICompilerFactory) {
 292             hsCompilerFactory = (HotSpotJVMCICompilerFactory) compilerFactory;
 293             trivialPrefixes = hsCompilerFactory.getTrivialPrefixes();
 294             switch (hsCompilerFactory.getCompilationLevelAdjustment()) {
 295                 case None:
 296                     compilationLevelAdjustment = config.compLevelAdjustmentNone;
 297                     break;
 298                 case ByHolder:
 299                     compilationLevelAdjustment = config.compLevelAdjustmentByHolder;
 300                     break;
 301                 case ByFullSignature:
 302                     compilationLevelAdjustment = config.compLevelAdjustmentByFullSignature;
 303                     break;
 304                 default:
 305                     compilationLevelAdjustment = config.compLevelAdjustmentNone;
 306                     break;
 307             }
 308         } else {
 309             hsCompilerFactory = null;
 310             trivialPrefixes = null;
 311             compilationLevelAdjustment = config.compLevelAdjustmentNone;
 312         }
 313 
 314         if (config.getFlag("JVMCIPrintProperties", Boolean.class)) {
 315             PrintStream out = new PrintStream(getLogStream());
 316             Option.printProperties(out);
 317             compilerFactory.printProperties(out);
 318             System.exit(0);
 319         }
 320 
 321         if (Option.PrintConfig.getBoolean()) {
 322             printConfig(configStore, compilerToVm);
 323         }
 324     }
 325 
 326     private JVMCIBackend registerBackend(JVMCIBackend backend) {
 327         Class<? extends Architecture> arch = backend.getCodeCache().getTarget().arch.getClass();
 328         JVMCIBackend oldValue = backends.put(arch, backend);
 329         assert oldValue == null : "cannot overwrite existing backend for architecture " + arch.getSimpleName();
 330         return backend;
 331     }
 332 
 333     public ResolvedJavaType fromClass(Class<?> javaClass) {
 334         return metaAccessContext.fromClass(javaClass);
 335     }
 336 
 337     public HotSpotVMConfigStore getConfigStore() {
 338         return configStore;
 339     }
 340 
 341     public HotSpotVMConfig getConfig() {
 342         return config;
 343     }
 344 
 345     public CompilerToVM getCompilerToVM() {
 346         return compilerToVm;
 347     }
 348 
 349     public JVMCICompiler getCompiler() {
 350         if (compiler == null) {
 351             synchronized (this) {
 352                 if (compiler == null) {
 353                     compiler = compilerFactory.createCompiler(this);
 354                 }
 355             }
 356         }
 357         return compiler;
 358     }
 359 
 360     public JavaType lookupType(String name, HotSpotResolvedObjectType accessingType, boolean resolve) {
 361         Objects.requireNonNull(accessingType, "cannot resolve type without an accessing class");
 362         // If the name represents a primitive type we can short-circuit the lookup.
 363         if (name.length() == 1) {
 364             JavaKind kind = JavaKind.fromPrimitiveOrVoidTypeChar(name.charAt(0));
 365             return fromClass(kind.toJavaClass());
 366         }
 367 
 368         // Resolve non-primitive types in the VM.
 369         HotSpotResolvedObjectTypeImpl hsAccessingType = (HotSpotResolvedObjectTypeImpl) accessingType;
 370         try {
 371             final HotSpotResolvedObjectTypeImpl klass = compilerToVm.lookupType(name, hsAccessingType.mirror(), resolve);
 372 
 373             if (klass == null) {
 374                 assert resolve == false;
 375                 return HotSpotUnresolvedJavaType.create(this, name);
 376             }
 377             return klass;
 378         } catch (ClassNotFoundException e) {
 379             throw (NoClassDefFoundError) new NoClassDefFoundError().initCause(e);
 380         }
 381     }
 382 
 383     public JVMCIBackend getHostJVMCIBackend() {
 384         return hostBackend;
 385     }
 386 
 387     public <T extends Architecture> JVMCIBackend getJVMCIBackend(Class<T> arch) {
 388         assert arch != Architecture.class;
 389         return backends.get(arch);
 390     }
 391 
 392     public Map<Class<? extends Architecture>, JVMCIBackend> getJVMCIBackends() {
 393         return Collections.unmodifiableMap(backends);
 394     }
 395 
 396     /**
 397      * Called from the VM.
 398      */
 399     @SuppressWarnings({"unused"})
 400     private int adjustCompilationLevel(Class<?> declaringClass, String name, String signature, boolean isOsr, int level) {
 401         CompilationLevel curLevel;
 402         if (level == config.compilationLevelNone) {
 403             curLevel = CompilationLevel.None;
 404         } else if (level == config.compilationLevelSimple) {
 405             curLevel = CompilationLevel.Simple;
 406         } else if (level == config.compilationLevelLimitedProfile) {
 407             curLevel = CompilationLevel.LimitedProfile;
 408         } else if (level == config.compilationLevelFullProfile) {
 409             curLevel = CompilationLevel.FullProfile;
 410         } else if (level == config.compilationLevelFullOptimization) {
 411             curLevel = CompilationLevel.FullOptimization;
 412         } else {
 413             throw JVMCIError.shouldNotReachHere();
 414         }
 415 
 416         switch (hsCompilerFactory.adjustCompilationLevel(declaringClass, name, signature, isOsr, curLevel)) {
 417             case None:
 418                 return config.compilationLevelNone;
 419             case Simple:
 420                 return config.compilationLevelSimple;
 421             case LimitedProfile:
 422                 return config.compilationLevelLimitedProfile;
 423             case FullProfile:
 424                 return config.compilationLevelFullProfile;
 425             case FullOptimization:
 426                 return config.compilationLevelFullOptimization;
 427             default:
 428                 return level;
 429         }
 430     }
 431 
 432     /**
 433      * Called from the VM.
 434      */
 435     @SuppressWarnings({"unused"})
 436     private HotSpotCompilationRequestResult compileMethod(HotSpotResolvedJavaMethod method, int entryBCI, long jvmciEnv, int id) {
 437         CompilationRequestResult result = getCompiler().compileMethod(new HotSpotCompilationRequest(method, entryBCI, jvmciEnv, id));
 438         assert result != null : "compileMethod must always return something";
 439         HotSpotCompilationRequestResult hsResult;
 440         if (result instanceof HotSpotCompilationRequestResult) {
 441             hsResult = (HotSpotCompilationRequestResult) result;
 442         } else {
 443             Object failure = result.getFailure();
 444             if (failure != null) {
 445                 boolean retry = false; // Be conservative with unknown compiler
 446                 hsResult = HotSpotCompilationRequestResult.failure(failure.toString(), retry);
 447             } else {
 448                 int inlinedBytecodes = -1;
 449                 hsResult = HotSpotCompilationRequestResult.success(inlinedBytecodes);
 450             }
 451         }
 452 
 453         return hsResult;
 454     }
 455 
 456     /**
 457      * Shuts down the runtime.
 458      *
 459      * Called from the VM.
 460      */
 461     @SuppressWarnings({"unused"})
 462     private void shutdown() throws Exception {
 463         for (HotSpotVMEventListener vmEventListener : getVmEventListeners()) {
 464             vmEventListener.notifyShutdown();
 465         }
 466     }
 467 
 468     /**
 469      * Notify on completion of a bootstrap.
 470      *
 471      * Called from the VM.
 472      */
 473     @SuppressWarnings({"unused"})
 474     private void bootstrapFinished() throws Exception {
 475         for (HotSpotVMEventListener vmEventListener : getVmEventListeners()) {
 476             vmEventListener.notifyBootstrapFinished();
 477         }
 478     }
 479 
 480     /**
 481      * Notify on successful install into the CodeCache.
 482      *
 483      * @param hotSpotCodeCacheProvider
 484      * @param installedCode
 485      * @param compiledCode
 486      */
 487     void notifyInstall(HotSpotCodeCacheProvider hotSpotCodeCacheProvider, InstalledCode installedCode, CompiledCode compiledCode) {
 488         for (HotSpotVMEventListener vmEventListener : getVmEventListeners()) {
 489             vmEventListener.notifyInstall(hotSpotCodeCacheProvider, installedCode, compiledCode);
 490         }
 491     }
 492 
 493     @SuppressFBWarnings(value = "DM_DEFAULT_ENCODING", justification = "no localization here please!")
 494     private static void printConfigLine(CompilerToVM vm, String format, Object... args) {
 495         String line = String.format(format, args);
 496         byte[] lineBytes = line.getBytes();
 497         vm.writeDebugOutput(lineBytes, 0, lineBytes.length);
 498         vm.flushDebugOutput();
 499     }
 500 
 501     private static void printConfig(HotSpotVMConfigStore store, CompilerToVM vm) {
 502         TreeMap<String, VMField> fields = new TreeMap<>(store.getFields());
 503         for (VMField field : fields.values()) {
 504             if (!field.isStatic()) {
 505                 printConfigLine(vm, "[vmconfig:instance field] %s %s {offset=%d[0x%x]}%n", field.type, field.name, field.offset, field.offset);
 506             } else {
 507                 String value = field.value == null ? "null" : field.value instanceof Boolean ? field.value.toString() : String.format("%d[0x%x]", field.value, field.value);
 508                 printConfigLine(vm, "[vmconfig:static field] %s %s = %s {address=0x%x}%n", field.type, field.name, value, field.address);
 509             }
 510         }
 511         TreeMap<String, VMFlag> flags = new TreeMap<>(store.getFlags());
 512         for (VMFlag flag : flags.values()) {
 513             printConfigLine(vm, "[vmconfig:flag] %s %s = %s%n", flag.type, flag.name, flag.value);
 514         }
 515         TreeMap<String, Long> addresses = new TreeMap<>(store.getAddresses());
 516         for (Map.Entry<String, Long> e : addresses.entrySet()) {
 517             printConfigLine(vm, "[vmconfig:address] %s = %d[0x%x]%n", e.getKey(), e.getValue(), e.getValue());
 518         }
 519         TreeMap<String, Long> constants = new TreeMap<>(store.getConstants());
 520         for (Map.Entry<String, Long> e : constants.entrySet()) {
 521             printConfigLine(vm, "[vmconfig:constant] %s = %d[0x%x]%n", e.getKey(), e.getValue(), e.getValue());
 522         }
 523         for (VMIntrinsicMethod e : store.getIntrinsics()) {
 524             printConfigLine(vm, "[vmconfig:intrinsic] %d = %s.%s %s%n", e.id, e.declaringClass, e.name, e.descriptor);
 525         }
 526     }
 527 
 528     public OutputStream getLogStream() {
 529         return new OutputStream() {
 530 
 531             @Override
 532             public void write(byte[] b, int off, int len) throws IOException {
 533                 if (b == null) {
 534                     throw new NullPointerException();
 535                 } else if (off < 0 || off > b.length || len < 0 || (off + len) > b.length || (off + len) < 0) {
 536                     throw new IndexOutOfBoundsException();
 537                 } else if (len == 0) {
 538                     return;
 539                 }
 540                 compilerToVm.writeDebugOutput(b, off, len);
 541             }
 542 
 543             @Override
 544             public void write(int b) throws IOException {
 545                 write(new byte[]{(byte) b}, 0, 1);
 546             }
 547 
 548             @Override
 549             public void flush() throws IOException {
 550                 compilerToVm.flushDebugOutput();
 551             }
 552         };
 553     }
 554 
 555     /**
 556      * Collects the current values of all JVMCI benchmark counters, summed up over all threads.
 557      */
 558     public long[] collectCounters() {
 559         return compilerToVm.collectCounters();
 560     }
 561 }