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.ArrayList;
  31 import java.util.Collections;
  32 import java.util.HashMap;
  33 import java.util.List;
  34 import java.util.Map;
  35 import java.util.Objects;
  36 import java.util.ServiceLoader;
  37 import java.util.TreeMap;
  38 
  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.services.HotSpotJVMCICompilerFactory;
  47 import jdk.vm.ci.hotspot.services.HotSpotJVMCICompilerFactory.CompilationLevel;
  48 import jdk.vm.ci.hotspot.services.HotSpotVMEventListener;
  49 import jdk.vm.ci.meta.JavaKind;
  50 import jdk.vm.ci.meta.JavaType;
  51 import jdk.vm.ci.meta.ResolvedJavaType;
  52 import jdk.vm.ci.runtime.JVMCI;
  53 import jdk.vm.ci.runtime.JVMCIBackend;
  54 import jdk.vm.ci.runtime.JVMCICompiler;
  55 import jdk.vm.ci.runtime.services.JVMCICompilerFactory;
  56 import jdk.vm.ci.services.Services;
  57 
  58 /**
  59  * HotSpot implementation of a JVMCI runtime.
  60  *
  61  * The initialization of this class is very fragile since it's initialized both through
  62  * {@link JVMCI#initialize()} or through calling {@link HotSpotJVMCIRuntime#runtime()} and
  63  * {@link HotSpotJVMCIRuntime#runtime()} is also called by {@link JVMCI#initialize()}. So this class
  64  * can't have a static initializer and any required initialization must be done as part of
  65  * {@link #runtime()}. This allows the initialization to funnel back through
  66  * {@link JVMCI#initialize()} without deadlocking.
  67  */
  68 public final class HotSpotJVMCIRuntime implements HotSpotJVMCIRuntimeProvider {
  69 
  70     @SuppressWarnings("try")
  71     static class DelayedInit {
  72         private static final HotSpotJVMCIRuntime instance;
  73 
  74         static {
  75             try (InitTimer t = timer("HotSpotJVMCIRuntime.<init>")) {
  76                 instance = new HotSpotJVMCIRuntime();
  77             }
  78         }
  79     }
  80 
  81     /**
  82      * Gets the singleton {@link HotSpotJVMCIRuntime} object.
  83      */
  84     public static HotSpotJVMCIRuntime runtime() {
  85         JVMCI.initialize();
  86         return DelayedInit.instance;
  87     }
  88 
  89     /**
  90      * A list of all supported JVMCI options.
  91      */
  92     public enum Option {
  93         Compiler(String.class, null, "Selects the system compiler."),
  94         // Note: The following one is not used (see InitTimer.ENABLED). It is added here
  95         // so that -Djvmci.PrintFlags=true shows the option.
  96         InitTimer(boolean.class, false, "Specifies if initialization timing is enabled."),
  97         PrintConfig(boolean.class, false, "Prints VM configuration available via JVMCI and exits."),
  98         PrintFlags(boolean.class, false, "Prints all JVMCI flags and exits."),
  99         ShowFlags(boolean.class, false, "Prints all JVMCI flags and continues."),
 100         TraceMethodDataFilter(String.class, null, "");
 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 help;
 117 
 118         Option(Class<?> type, Object defaultValue, String help) {
 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.help = help;
 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(JVMCI_OPTION_PROPERTY_PREFIX + name());
 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          * Returns the option's value as boolean.
 151          *
 152          * @return option's value
 153          */
 154         public boolean getBoolean() {
 155             return (boolean) getValue();
 156         }
 157 
 158         /**
 159          * Returns the option's value as String.
 160          *
 161          * @return option's value
 162          */
 163         public String getString() {
 164             return (String) getValue();
 165         }
 166 
 167         /**
 168          * Prints all option flags to {@code out}.
 169          *
 170          * @param out stream to print to
 171          */
 172         public static void printFlags(PrintStream out) {
 173             out.println("[List of JVMCI options]");
 174             for (Option option : values()) {
 175                 Object value = option.getValue();
 176                 String assign = option.isDefault ? ":=" : " =";
 177                 out.printf("%9s %-40s %s %-14s %s%n", option.type.getSimpleName(), option, assign, value, option.help);
 178             }
 179         }
 180     }
 181 
 182     public static HotSpotJVMCIBackendFactory findFactory(String architecture) {
 183         for (HotSpotJVMCIBackendFactory factory : Services.load(HotSpotJVMCIBackendFactory.class)) {
 184             if (factory.getArchitecture().equalsIgnoreCase(architecture)) {
 185                 return factory;
 186             }
 187         }
 188 
 189         throw new JVMCIError("No JVMCI runtime available for the %s architecture", architecture);
 190     }
 191 
 192     /**
 193      * Gets the kind of a word value on the {@linkplain #getHostJVMCIBackend() host} backend.
 194      */
 195     public static JavaKind getHostWordKind() {
 196         return runtime().getHostJVMCIBackend().getCodeCache().getTarget().wordJavaKind;
 197     }
 198 
 199     protected final CompilerToVM compilerToVm;
 200 
 201     protected final HotSpotVMConfigStore configStore;
 202     protected final HotSpotVMConfig config;
 203     private final JVMCIBackend hostBackend;
 204 
 205     private final JVMCICompilerFactory compilerFactory;
 206     private final HotSpotJVMCICompilerFactory hsCompilerFactory;
 207     private volatile JVMCICompiler compiler;
 208     protected final HotSpotJVMCIMetaAccessContext metaAccessContext;
 209 
 210     /**
 211      * Stores the result of {@link HotSpotJVMCICompilerFactory#getCompilationLevelAdjustment} so
 212      * that it can be read from the VM.
 213      */
 214     @SuppressWarnings("unused") private final int compilationLevelAdjustment;
 215 
 216     private final Map<Class<? extends Architecture>, JVMCIBackend> backends = new HashMap<>();
 217 
 218     private volatile List<HotSpotVMEventListener> vmEventListeners;
 219 
 220     private Iterable<HotSpotVMEventListener> getVmEventListeners() {
 221         if (vmEventListeners == null) {
 222             synchronized (this) {
 223                 if (vmEventListeners == null) {
 224                     List<HotSpotVMEventListener> listeners = new ArrayList<>();
 225                     for (HotSpotVMEventListener vmEventListener : ServiceLoader.load(HotSpotVMEventListener.class)) {
 226                         listeners.add(vmEventListener);
 227                     }
 228                     vmEventListeners = listeners;
 229                 }
 230             }
 231         }
 232         return vmEventListeners;
 233     }
 234 
 235     /**
 236      * Stores the result of {@link HotSpotJVMCICompilerFactory#getTrivialPrefixes()} so that it can
 237      * be read from the VM.
 238      */
 239     @SuppressWarnings("unused") private final String[] trivialPrefixes;
 240 
 241     @SuppressWarnings("try")
 242     @SuppressFBWarnings(value = "DM_EXIT", justification = "PrintFlags is meant to exit the VM")
 243     private HotSpotJVMCIRuntime() {
 244         compilerToVm = new CompilerToVM();
 245 
 246         try (InitTimer t = timer("HotSpotVMConfig<init>")) {
 247             configStore = new HotSpotVMConfigStore(compilerToVm);
 248             config = new HotSpotVMConfig(configStore);
 249         }
 250 
 251         String hostArchitecture = config.getHostArchitectureName();
 252 
 253         HotSpotJVMCIBackendFactory factory;
 254         try (InitTimer t = timer("find factory:", hostArchitecture)) {
 255             factory = findFactory(hostArchitecture);
 256         }
 257 
 258         try (InitTimer t = timer("create JVMCI backend:", hostArchitecture)) {
 259             hostBackend = registerBackend(factory.createJVMCIBackend(this, null));
 260         }
 261 
 262         metaAccessContext = new HotSpotJVMCIMetaAccessContext();
 263 
 264         boolean printFlags = Option.PrintFlags.getBoolean();
 265         boolean showFlags = Option.ShowFlags.getBoolean();
 266         if (printFlags || showFlags) {
 267             Option.printFlags(System.out);
 268             if (printFlags) {
 269                 System.exit(0);
 270             }
 271         }
 272 
 273         if (Option.PrintConfig.getBoolean()) {
 274             printConfig(configStore, compilerToVm);
 275             System.exit(0);
 276         }
 277 
 278         compilerFactory = HotSpotJVMCICompilerConfig.getCompilerFactory();
 279         if (compilerFactory instanceof HotSpotJVMCICompilerFactory) {
 280             hsCompilerFactory = (HotSpotJVMCICompilerFactory) compilerFactory;
 281             trivialPrefixes = hsCompilerFactory.getTrivialPrefixes();
 282             switch (hsCompilerFactory.getCompilationLevelAdjustment()) {
 283                 case None:
 284                     compilationLevelAdjustment = config.compLevelAdjustmentNone;
 285                     break;
 286                 case ByHolder:
 287                     compilationLevelAdjustment = config.compLevelAdjustmentByHolder;
 288                     break;
 289                 case ByFullSignature:
 290                     compilationLevelAdjustment = config.compLevelAdjustmentByFullSignature;
 291                     break;
 292                 default:
 293                     compilationLevelAdjustment = config.compLevelAdjustmentNone;
 294                     break;
 295             }
 296         } else {
 297             hsCompilerFactory = null;
 298             trivialPrefixes = null;
 299             compilationLevelAdjustment = config.compLevelAdjustmentNone;
 300         }
 301     }
 302 
 303     private JVMCIBackend registerBackend(JVMCIBackend backend) {
 304         Class<? extends Architecture> arch = backend.getCodeCache().getTarget().arch.getClass();
 305         JVMCIBackend oldValue = backends.put(arch, backend);
 306         assert oldValue == null : "cannot overwrite existing backend for architecture " + arch.getSimpleName();
 307         return backend;
 308     }
 309 
 310     public ResolvedJavaType fromClass(Class<?> javaClass) {
 311         return metaAccessContext.fromClass(javaClass);
 312     }
 313 
 314     public HotSpotVMConfigStore getConfigStore() {
 315         return configStore;
 316     }
 317 
 318     public HotSpotVMConfig getConfig() {
 319         return config;
 320     }
 321 
 322     public CompilerToVM getCompilerToVM() {
 323         return compilerToVm;
 324     }
 325 
 326     public JVMCICompiler getCompiler() {
 327         if (compiler == null) {
 328             synchronized (this) {
 329                 if (compiler == null) {
 330                     compiler = compilerFactory.createCompiler(this);
 331                 }
 332             }
 333         }
 334         return compiler;
 335     }
 336 
 337     public JavaType lookupType(String name, HotSpotResolvedObjectType accessingType, boolean resolve) {
 338         Objects.requireNonNull(accessingType, "cannot resolve type without an accessing class");
 339         // If the name represents a primitive type we can short-circuit the lookup.
 340         if (name.length() == 1) {
 341             JavaKind kind = JavaKind.fromPrimitiveOrVoidTypeChar(name.charAt(0));
 342             return fromClass(kind.toJavaClass());
 343         }
 344 
 345         // Resolve non-primitive types in the VM.
 346         HotSpotResolvedObjectTypeImpl hsAccessingType = (HotSpotResolvedObjectTypeImpl) accessingType;
 347         final HotSpotResolvedObjectTypeImpl klass = compilerToVm.lookupType(name, hsAccessingType.mirror(), resolve);
 348 
 349         if (klass == null) {
 350             assert resolve == false;
 351             return HotSpotUnresolvedJavaType.create(this, name);
 352         }
 353         return klass;
 354     }
 355 
 356     public JVMCIBackend getHostJVMCIBackend() {
 357         return hostBackend;
 358     }
 359 
 360     public <T extends Architecture> JVMCIBackend getJVMCIBackend(Class<T> arch) {
 361         assert arch != Architecture.class;
 362         return backends.get(arch);
 363     }
 364 
 365     public Map<Class<? extends Architecture>, JVMCIBackend> getJVMCIBackends() {
 366         return Collections.unmodifiableMap(backends);
 367     }
 368 
 369     /**
 370      * Called from the VM.
 371      */
 372     @SuppressWarnings({"unused"})
 373     private int adjustCompilationLevel(Class<?> declaringClass, String name, String signature, boolean isOsr, int level) {
 374         CompilationLevel curLevel;
 375         if (level == config.compilationLevelNone) {
 376             curLevel = CompilationLevel.None;
 377         } else if (level == config.compilationLevelSimple) {
 378             curLevel = CompilationLevel.Simple;
 379         } else if (level == config.compilationLevelLimitedProfile) {
 380             curLevel = CompilationLevel.LimitedProfile;
 381         } else if (level == config.compilationLevelFullProfile) {
 382             curLevel = CompilationLevel.FullProfile;
 383         } else if (level == config.compilationLevelFullOptimization) {
 384             curLevel = CompilationLevel.FullOptimization;
 385         } else {
 386             throw JVMCIError.shouldNotReachHere();
 387         }
 388 
 389         switch (hsCompilerFactory.adjustCompilationLevel(declaringClass, name, signature, isOsr, curLevel)) {
 390             case None:
 391                 return config.compilationLevelNone;
 392             case Simple:
 393                 return config.compilationLevelSimple;
 394             case LimitedProfile:
 395                 return config.compilationLevelLimitedProfile;
 396             case FullProfile:
 397                 return config.compilationLevelFullProfile;
 398             case FullOptimization:
 399                 return config.compilationLevelFullOptimization;
 400             default:
 401                 return level;
 402         }
 403     }
 404 
 405     /**
 406      * Called from the VM.
 407      */
 408     @SuppressWarnings({"unused"})
 409     private HotSpotCompilationRequestResult compileMethod(HotSpotResolvedJavaMethod method, int entryBCI, long jvmciEnv, int id) {
 410         CompilationRequestResult result = getCompiler().compileMethod(new HotSpotCompilationRequest(method, entryBCI, jvmciEnv, id));
 411         assert result != null : "compileMethod must always return something";
 412         HotSpotCompilationRequestResult hsResult;
 413         if (result instanceof HotSpotCompilationRequestResult) {
 414             hsResult = (HotSpotCompilationRequestResult) result;
 415         } else {
 416             Object failure = result.getFailure();
 417             if (failure != null) {
 418                 boolean retry = false; // Be conservative with unknown compiler
 419                 hsResult = HotSpotCompilationRequestResult.failure(failure.toString(), retry);
 420             } else {
 421                 int inlinedBytecodes = -1;
 422                 hsResult = HotSpotCompilationRequestResult.success(inlinedBytecodes);
 423             }
 424         }
 425 
 426         return hsResult;
 427     }
 428 
 429     /**
 430      * Shuts down the runtime.
 431      *
 432      * Called from the VM.
 433      */
 434     @SuppressWarnings({"unused"})
 435     private void shutdown() throws Exception {
 436         for (HotSpotVMEventListener vmEventListener : getVmEventListeners()) {
 437             vmEventListener.notifyShutdown();
 438         }
 439     }
 440 
 441     /**
 442      * Notify on completion of a bootstrap.
 443      *
 444      * Called from the VM.
 445      */
 446     @SuppressWarnings({"unused"})
 447     private void bootstrapFinished() throws Exception {
 448         for (HotSpotVMEventListener vmEventListener : getVmEventListeners()) {
 449             vmEventListener.notifyBootstrapFinished();
 450         }
 451     }
 452 
 453     /**
 454      * Notify on successful install into the CodeCache.
 455      *
 456      * @param hotSpotCodeCacheProvider
 457      * @param installedCode
 458      * @param compiledCode
 459      */
 460     void notifyInstall(HotSpotCodeCacheProvider hotSpotCodeCacheProvider, InstalledCode installedCode, CompiledCode compiledCode) {
 461         for (HotSpotVMEventListener vmEventListener : getVmEventListeners()) {
 462             vmEventListener.notifyInstall(hotSpotCodeCacheProvider, installedCode, compiledCode);
 463         }
 464     }
 465 
 466     @SuppressFBWarnings(value = "DM_DEFAULT_ENCODING", justification = "no localization here please!")
 467     private static void printConfigLine(CompilerToVM vm, String format, Object... args) {
 468         String line = String.format(format, args);
 469         byte[] lineBytes = line.getBytes();
 470         vm.writeDebugOutput(lineBytes, 0, lineBytes.length);
 471         vm.flushDebugOutput();
 472     }
 473 
 474     private static void printConfig(HotSpotVMConfigStore store, CompilerToVM vm) {
 475         TreeMap<String, VMField> fields = new TreeMap<>(store.getFields());
 476         for (VMField field : fields.values()) {
 477             if (!field.isStatic()) {
 478                 printConfigLine(vm, "[vmconfig:instance field] %s %s {offset=%d[0x%x]}%n", field.type, field.name, field.offset, field.offset);
 479             } else {
 480                 String value = field.value == null ? "null" : String.format("%d[0x%x]", field.value, field.value);
 481                 printConfigLine(vm, "[vmconfig:static field] %s %s = %s {address=0x%x}%n", field.type, field.name, value, field.address);
 482             }
 483         }
 484         TreeMap<String, VMFlag> flags = new TreeMap<>(store.getFlags());
 485         for (VMFlag flag : flags.values()) {
 486             printConfigLine(vm, "[vmconfig:flag] %s %s = %s%n", flag.type, flag.name, flag.value);
 487         }
 488         TreeMap<String, Long> addresses = new TreeMap<>(store.getAddresses());
 489         for (Map.Entry<String, Long> e : addresses.entrySet()) {
 490             printConfigLine(vm, "[vmconfig:address] %s = %d[0x%x]%n", e.getKey(), e.getValue(), e.getValue());
 491         }
 492         TreeMap<String, Long> constants = new TreeMap<>(store.getConstants());
 493         for (Map.Entry<String, Long> e : constants.entrySet()) {
 494             printConfigLine(vm, "[vmconfig:constant] %s = %d[0x%x]%n", e.getKey(), e.getValue(), e.getValue());
 495         }
 496         TreeMap<String, Long> typeSizes = new TreeMap<>(store.getTypeSizes());
 497         for (Map.Entry<String, Long> e : typeSizes.entrySet()) {
 498             printConfigLine(vm, "[vmconfig:type size] %s = %d%n", e.getKey(), e.getValue());
 499         }
 500     }
 501 
 502     public OutputStream getLogStream() {
 503         return new OutputStream() {
 504 
 505             @Override
 506             public void write(byte[] b, int off, int len) throws IOException {
 507                 if (b == null) {
 508                     throw new NullPointerException();
 509                 } else if (off < 0 || off > b.length || len < 0 || (off + len) > b.length || (off + len) < 0) {
 510                     throw new IndexOutOfBoundsException();
 511                 } else if (len == 0) {
 512                     return;
 513                 }
 514                 compilerToVm.writeDebugOutput(b, off, len);
 515             }
 516 
 517             @Override
 518             public void write(int b) throws IOException {
 519                 write(new byte[]{(byte) b}, 0, 1);
 520             }
 521 
 522             @Override
 523             public void flush() throws IOException {
 524                 compilerToVm.flushDebugOutput();
 525             }
 526         };
 527     }
 528 
 529     /**
 530      * Collects the current values of all JVMCI benchmark counters, summed up over all threads.
 531      */
 532     public long[] collectCounters() {
 533         return compilerToVm.collectCounters();
 534     }
 535 }