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