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