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         final HotSpotResolvedObjectTypeImpl klass = compilerToVm.lookupType(name, hsAccessingType.mirror(), resolve);
 371 
 372         if (klass == null) {
 373             assert resolve == false;
 374             return HotSpotUnresolvedJavaType.create(this, name);
 375         }
 376         return klass;
 377     }
 378 
 379     public JVMCIBackend getHostJVMCIBackend() {
 380         return hostBackend;
 381     }
 382 
 383     public <T extends Architecture> JVMCIBackend getJVMCIBackend(Class<T> arch) {
 384         assert arch != Architecture.class;
 385         return backends.get(arch);
 386     }
 387 
 388     public Map<Class<? extends Architecture>, JVMCIBackend> getJVMCIBackends() {
 389         return Collections.unmodifiableMap(backends);
 390     }
 391 
 392     /**
 393      * Called from the VM.
 394      */
 395     @SuppressWarnings({"unused"})
 396     private int adjustCompilationLevel(Class<?> declaringClass, String name, String signature, boolean isOsr, int level) {
 397         CompilationLevel curLevel;
 398         if (level == config.compilationLevelNone) {
 399             curLevel = CompilationLevel.None;
 400         } else if (level == config.compilationLevelSimple) {
 401             curLevel = CompilationLevel.Simple;
 402         } else if (level == config.compilationLevelLimitedProfile) {
 403             curLevel = CompilationLevel.LimitedProfile;
 404         } else if (level == config.compilationLevelFullProfile) {
 405             curLevel = CompilationLevel.FullProfile;
 406         } else if (level == config.compilationLevelFullOptimization) {
 407             curLevel = CompilationLevel.FullOptimization;
 408         } else {
 409             throw JVMCIError.shouldNotReachHere();
 410         }
 411 
 412         switch (hsCompilerFactory.adjustCompilationLevel(declaringClass, name, signature, isOsr, curLevel)) {
 413             case None:
 414                 return config.compilationLevelNone;
 415             case Simple:
 416                 return config.compilationLevelSimple;
 417             case LimitedProfile:
 418                 return config.compilationLevelLimitedProfile;
 419             case FullProfile:
 420                 return config.compilationLevelFullProfile;
 421             case FullOptimization:
 422                 return config.compilationLevelFullOptimization;
 423             default:
 424                 return level;
 425         }
 426     }
 427 
 428     /**
 429      * Called from the VM.
 430      */
 431     @SuppressWarnings({"unused"})
 432     private HotSpotCompilationRequestResult compileMethod(HotSpotResolvedJavaMethod method, int entryBCI, long jvmciEnv, int id) {
 433         CompilationRequestResult result = getCompiler().compileMethod(new HotSpotCompilationRequest(method, entryBCI, jvmciEnv, id));
 434         assert result != null : "compileMethod must always return something";
 435         HotSpotCompilationRequestResult hsResult;
 436         if (result instanceof HotSpotCompilationRequestResult) {
 437             hsResult = (HotSpotCompilationRequestResult) result;
 438         } else {
 439             Object failure = result.getFailure();
 440             if (failure != null) {
 441                 boolean retry = false; // Be conservative with unknown compiler
 442                 hsResult = HotSpotCompilationRequestResult.failure(failure.toString(), retry);
 443             } else {
 444                 int inlinedBytecodes = -1;
 445                 hsResult = HotSpotCompilationRequestResult.success(inlinedBytecodes);
 446             }
 447         }
 448 
 449         return hsResult;
 450     }
 451 
 452     /**
 453      * Shuts down the runtime.
 454      *
 455      * Called from the VM.
 456      */
 457     @SuppressWarnings({"unused"})
 458     private void shutdown() throws Exception {
 459         for (HotSpotVMEventListener vmEventListener : getVmEventListeners()) {
 460             vmEventListener.notifyShutdown();
 461         }
 462     }
 463 
 464     /**
 465      * Notify on completion of a bootstrap.
 466      *
 467      * Called from the VM.
 468      */
 469     @SuppressWarnings({"unused"})
 470     private void bootstrapFinished() throws Exception {
 471         for (HotSpotVMEventListener vmEventListener : getVmEventListeners()) {
 472             vmEventListener.notifyBootstrapFinished();
 473         }
 474     }
 475 
 476     /**
 477      * Notify on successful install into the CodeCache.
 478      *
 479      * @param hotSpotCodeCacheProvider
 480      * @param installedCode
 481      * @param compiledCode
 482      */
 483     void notifyInstall(HotSpotCodeCacheProvider hotSpotCodeCacheProvider, InstalledCode installedCode, CompiledCode compiledCode) {
 484         for (HotSpotVMEventListener vmEventListener : getVmEventListeners()) {
 485             vmEventListener.notifyInstall(hotSpotCodeCacheProvider, installedCode, compiledCode);
 486         }
 487     }
 488 
 489     @SuppressFBWarnings(value = "DM_DEFAULT_ENCODING", justification = "no localization here please!")
 490     private static void printConfigLine(CompilerToVM vm, String format, Object... args) {
 491         String line = String.format(format, args);
 492         byte[] lineBytes = line.getBytes();
 493         vm.writeDebugOutput(lineBytes, 0, lineBytes.length);
 494         vm.flushDebugOutput();
 495     }
 496 
 497     private static void printConfig(HotSpotVMConfigStore store, CompilerToVM vm) {
 498         TreeMap<String, VMField> fields = new TreeMap<>(store.getFields());
 499         for (VMField field : fields.values()) {
 500             if (!field.isStatic()) {
 501                 printConfigLine(vm, "[vmconfig:instance field] %s %s {offset=%d[0x%x]}%n", field.type, field.name, field.offset, field.offset);
 502             } else {
 503                 String value = field.value == null ? "null" : field.value instanceof Boolean ? field.value.toString() : String.format("%d[0x%x]", field.value, field.value);
 504                 printConfigLine(vm, "[vmconfig:static field] %s %s = %s {address=0x%x}%n", field.type, field.name, value, field.address);
 505             }
 506         }
 507         TreeMap<String, VMFlag> flags = new TreeMap<>(store.getFlags());
 508         for (VMFlag flag : flags.values()) {
 509             printConfigLine(vm, "[vmconfig:flag] %s %s = %s%n", flag.type, flag.name, flag.value);
 510         }
 511         TreeMap<String, Long> addresses = new TreeMap<>(store.getAddresses());
 512         for (Map.Entry<String, Long> e : addresses.entrySet()) {
 513             printConfigLine(vm, "[vmconfig:address] %s = %d[0x%x]%n", e.getKey(), e.getValue(), e.getValue());
 514         }
 515         TreeMap<String, Long> constants = new TreeMap<>(store.getConstants());
 516         for (Map.Entry<String, Long> e : constants.entrySet()) {
 517             printConfigLine(vm, "[vmconfig:constant] %s = %d[0x%x]%n", e.getKey(), e.getValue(), e.getValue());
 518         }
 519         for (VMIntrinsicMethod e : store.getIntrinsics()) {
 520             printConfigLine(vm, "[vmconfig:intrinsic] %d = %s.%s %s%n", e.id, e.declaringClass, e.name, e.descriptor);
 521         }
 522     }
 523 
 524     public OutputStream getLogStream() {
 525         return new OutputStream() {
 526 
 527             @Override
 528             public void write(byte[] b, int off, int len) throws IOException {
 529                 if (b == null) {
 530                     throw new NullPointerException();
 531                 } else if (off < 0 || off > b.length || len < 0 || (off + len) > b.length || (off + len) < 0) {
 532                     throw new IndexOutOfBoundsException();
 533                 } else if (len == 0) {
 534                     return;
 535                 }
 536                 compilerToVm.writeDebugOutput(b, off, len);
 537             }
 538 
 539             @Override
 540             public void write(int b) throws IOException {
 541                 write(new byte[]{(byte) b}, 0, 1);
 542             }
 543 
 544             @Override
 545             public void flush() throws IOException {
 546                 compilerToVm.flushDebugOutput();
 547             }
 548         };
 549     }
 550 
 551     /**
 552      * Collects the current values of all JVMCI benchmark counters, summed up over all threads.
 553      */
 554     public long[] collectCounters() {
 555         return compilerToVm.collectCounters();
 556     }
 557 }