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