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