1 /*
2  * Copyright (c) 2012, 2020, 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 
24 
25 package org.graalvm.compiler.hotspot;
26 
27 import static org.graalvm.compiler.core.CompilationWrapper.ExceptionAction.Diagnose;
28 import static org.graalvm.compiler.core.CompilationWrapper.ExceptionAction.ExitVM;
29 import static org.graalvm.compiler.core.GraalCompilerOptions.CompilationBailoutAsFailure;
30 import static org.graalvm.compiler.core.GraalCompilerOptions.CompilationFailureAction;
31 import static org.graalvm.compiler.core.phases.HighTier.Options.Inline;
32 import static org.graalvm.compiler.java.BytecodeParserOptions.InlineDuringParsing;
33 
34 import java.io.PrintStream;
35 
36 import jdk.internal.vm.compiler.collections.EconomicMap;
37 import org.graalvm.compiler.api.replacements.SnippetReflectionProvider;
38 import org.graalvm.compiler.code.CompilationResult;
39 import org.graalvm.compiler.core.CompilationPrinter;
40 import org.graalvm.compiler.core.CompilationWrapper;
41 import org.graalvm.compiler.core.common.CompilationIdentifier;
42 import org.graalvm.compiler.debug.CounterKey;
43 import org.graalvm.compiler.debug.DebugCloseable;
44 import org.graalvm.compiler.debug.DebugContext;
45 import org.graalvm.compiler.debug.DebugContext.Builder;
46 import org.graalvm.compiler.debug.DebugContext.Description;
47 import org.graalvm.compiler.debug.DebugDumpScope;
48 import org.graalvm.compiler.debug.DebugHandlersFactory;
49 import org.graalvm.compiler.debug.TimerKey;
50 import org.graalvm.compiler.nodes.StructuredGraph;
51 import org.graalvm.compiler.options.OptionKey;
52 import org.graalvm.compiler.options.OptionValues;
53 import org.graalvm.compiler.printer.GraalDebugHandlersFactory;
54 
55 import jdk.vm.ci.code.BailoutException;
56 import jdk.vm.ci.code.CodeCacheProvider;
57 import jdk.vm.ci.hotspot.HotSpotCompilationRequest;
58 import jdk.vm.ci.hotspot.HotSpotCompilationRequestResult;
59 import jdk.vm.ci.hotspot.HotSpotInstalledCode;
60 import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime;
61 import jdk.vm.ci.hotspot.HotSpotNmethod;
62 import jdk.vm.ci.hotspot.HotSpotResolvedJavaMethod;
63 import jdk.vm.ci.meta.ResolvedJavaMethod;
64 import jdk.vm.ci.runtime.JVMCICompiler;
65 
66 public class CompilationTask {
67 
68     private final HotSpotJVMCIRuntime jvmciRuntime;
69 
70     private final HotSpotGraalCompiler compiler;
71     private final HotSpotCompilationIdentifier compilationId;
72 
73     private HotSpotInstalledCode installedCode;
74 
75     /**
76      * Specifies whether the compilation result is installed as the
77      * {@linkplain HotSpotNmethod#isDefault() default} nmethod for the compiled method.
78      */
79     private final boolean installAsDefault;
80 
81     private final boolean useProfilingInfo;
82     private final boolean shouldRetainLocalVariables;
83 
84     final class HotSpotCompilationWrapper extends CompilationWrapper<HotSpotCompilationRequestResult> {
85         CompilationResult result;
86 
HotSpotCompilationWrapper()87         HotSpotCompilationWrapper() {
88             super(compiler.getGraalRuntime().getOutputDirectory(), compiler.getGraalRuntime().getCompilationProblemsPerAction());
89         }
90 
91         @Override
createRetryDebugContext(DebugContext initialDebug, OptionValues retryOptions, PrintStream logStream)92         protected DebugContext createRetryDebugContext(DebugContext initialDebug, OptionValues retryOptions, PrintStream logStream) {
93             SnippetReflectionProvider snippetReflection = compiler.getGraalRuntime().getHostProviders().getSnippetReflection();
94             Description description = initialDebug.getDescription();
95             DebugHandlersFactory factory = new GraalDebugHandlersFactory(snippetReflection);
96             return new Builder(retryOptions, factory).globalMetrics(initialDebug.getGlobalMetrics()).description(description).logStream(logStream).build();
97         }
98 
99         @Override
exitHostVM(int status)100         protected void exitHostVM(int status) {
101             HotSpotGraalServices.exit(status);
102         }
103 
104         @Override
toString()105         public String toString() {
106             return getMethod().format("%H.%n(%p) @ " + getEntryBCI());
107         }
108 
109         @Override
handleException(Throwable t)110         protected HotSpotCompilationRequestResult handleException(Throwable t) {
111             if (t instanceof BailoutException) {
112                 BailoutException bailout = (BailoutException) t;
113                 /*
114                  * Handling of permanent bailouts: Permanent bailouts that can happen for example
115                  * due to unsupported unstructured control flow in the bytecodes of a method must
116                  * not be retried. Hotspot compile broker will ensure that no recompilation at the
117                  * given tier will happen if retry is false.
118                  */
119                 return HotSpotCompilationRequestResult.failure(bailout.getMessage(), !bailout.isPermanent());
120             }
121 
122             /*
123              * Treat random exceptions from the compiler as indicating a problem compiling this
124              * method. Report the result of toString instead of getMessage to ensure that the
125              * exception type is included in the output in case there's no detail mesage.
126              */
127             return HotSpotCompilationRequestResult.failure(t.toString(), false);
128         }
129 
130         @Override
lookupAction(OptionValues values, Throwable cause)131         protected ExceptionAction lookupAction(OptionValues values, Throwable cause) {
132             if (cause instanceof BailoutException) {
133                 BailoutException bailout = (BailoutException) cause;
134                 if (bailout.isPermanent()) {
135                     // Respect current action if it has been explicitly set.
136                     if (!CompilationBailoutAsFailure.hasBeenSet(values)) {
137                         // Get more info for permanent bailouts during bootstrap.
138                         if (compiler.getGraalRuntime().isBootstrapping()) {
139                             return Diagnose;
140                         }
141 
142                     }
143                 }
144                 if (!CompilationBailoutAsFailure.getValue(values)) {
145                     return super.lookupAction(values, cause);
146                 }
147             }
148 
149             // Respect current action if it has been explicitly set.
150             if (!CompilationFailureAction.hasBeenSet(values)) {
151                 // Automatically exit on failure during bootstrap.
152                 if (compiler.getGraalRuntime().isBootstrapping()) {
153                     return ExitVM;
154                 }
155             }
156             return super.lookupAction(values, cause);
157         }
158 
159         @SuppressWarnings("try")
160         @Override
performCompilation(DebugContext debug)161         protected HotSpotCompilationRequestResult performCompilation(DebugContext debug) {
162             HotSpotResolvedJavaMethod method = getMethod();
163             int entryBCI = getEntryBCI();
164             final boolean isOSR = entryBCI != JVMCICompiler.INVOCATION_ENTRY_BCI;
165             CompilationStatistics stats = CompilationStatistics.create(debug.getOptions(), method, isOSR);
166 
167             final CompilationPrinter printer = CompilationPrinter.begin(debug.getOptions(), compilationId, method, entryBCI);
168 
169             StructuredGraph graph;
170             try (DebugContext.Scope s = debug.scope("Compiling", new DebugDumpScope(getIdString(), true))) {
171                 graph = compiler.createGraph(method, entryBCI, useProfilingInfo, compilationId, debug.getOptions(), debug);
172                 result = compiler.compile(graph, method, entryBCI, useProfilingInfo, shouldRetainLocalVariables, compilationId, debug);
173             } catch (Throwable e) {
174                 throw debug.handle(e);
175             }
176 
177             if (result != null) {
178                 try (DebugCloseable b = CodeInstallationTime.start(debug)) {
179                     installMethod(debug, graph, result);
180                 }
181                 // Installation is included in compilation time and memory usage reported by printer
182                 printer.finish(result);
183             }
184             stats.finish(method, installedCode);
185             if (result != null) {
186                 // For compilation of substitutions the method in the compilation request might be
187                 // different than the actual method parsed. The root of the compilation will always
188                 // be the first method in the methods list, so use that instead.
189                 ResolvedJavaMethod rootMethod = result.getMethods()[0];
190                 int inlinedBytecodes = result.getBytecodeSize() - rootMethod.getCodeSize();
191                 assert inlinedBytecodes >= 0 : rootMethod + " " + method;
192                 return HotSpotCompilationRequestResult.success(inlinedBytecodes);
193             }
194             return null;
195         }
196 
197     }
198 
CompilationTask(HotSpotJVMCIRuntime jvmciRuntime, HotSpotGraalCompiler compiler, HotSpotCompilationRequest request, boolean useProfilingInfo, boolean installAsDefault)199     public CompilationTask(HotSpotJVMCIRuntime jvmciRuntime,
200                     HotSpotGraalCompiler compiler,
201                     HotSpotCompilationRequest request,
202                     boolean useProfilingInfo,
203                     boolean installAsDefault) {
204         this(jvmciRuntime, compiler, request, useProfilingInfo, false, installAsDefault);
205     }
206 
CompilationTask(HotSpotJVMCIRuntime jvmciRuntime, HotSpotGraalCompiler compiler, HotSpotCompilationRequest request, boolean useProfilingInfo, boolean shouldRetainLocalVariables, boolean installAsDefault)207     public CompilationTask(HotSpotJVMCIRuntime jvmciRuntime,
208                     HotSpotGraalCompiler compiler,
209                     HotSpotCompilationRequest request,
210                     boolean useProfilingInfo,
211                     boolean shouldRetainLocalVariables,
212                     boolean installAsDefault) {
213         this.jvmciRuntime = jvmciRuntime;
214         this.compiler = compiler;
215         this.compilationId = new HotSpotCompilationIdentifier(request);
216         this.useProfilingInfo = useProfilingInfo;
217         this.shouldRetainLocalVariables = shouldRetainLocalVariables;
218         this.installAsDefault = installAsDefault;
219     }
220 
filterOptions(OptionValues options)221     public OptionValues filterOptions(OptionValues options) {
222         /*
223          * Disable inlining if HotSpot has it disabled unless it's been explicitly set in Graal.
224          */
225         HotSpotGraalRuntimeProvider graalRuntime = compiler.getGraalRuntime();
226         GraalHotSpotVMConfig config = graalRuntime.getVMConfig();
227         OptionValues newOptions = options;
228         if (!config.inline) {
229             EconomicMap<OptionKey<?>, Object> m = OptionValues.newOptionMap();
230             if (Inline.getValue(options) && !Inline.hasBeenSet(options)) {
231                 m.put(Inline, false);
232             }
233             if (InlineDuringParsing.getValue(options) && !InlineDuringParsing.hasBeenSet(options)) {
234                 m.put(InlineDuringParsing, false);
235             }
236             if (!m.isEmpty()) {
237                 newOptions = new OptionValues(options, m);
238             }
239         }
240         return newOptions;
241     }
242 
getMethod()243     public HotSpotResolvedJavaMethod getMethod() {
244         return getRequest().getMethod();
245     }
246 
getCompilationIdentifier()247     CompilationIdentifier getCompilationIdentifier() {
248         return compilationId;
249     }
250 
251     /**
252      * Returns the HotSpot id of this compilation.
253      *
254      * @return HotSpot compile id
255      */
getId()256     public int getId() {
257         return getRequest().getId();
258     }
259 
getEntryBCI()260     public int getEntryBCI() {
261         return getRequest().getEntryBCI();
262     }
263 
264     /**
265      * @return the compilation id plus a trailing '%' if the compilation is an OSR to match
266      *         PrintCompilation style output
267      */
getIdString()268     public String getIdString() {
269         if (getEntryBCI() != JVMCICompiler.INVOCATION_ENTRY_BCI) {
270             return getId() + "%";
271         } else {
272             return Integer.toString(getId());
273         }
274     }
275 
getInstalledCode()276     public HotSpotInstalledCode getInstalledCode() {
277         return installedCode;
278     }
279 
280     /**
281      * Time spent in compilation.
282      */
283     public static final TimerKey CompilationTime = DebugContext.timer("CompilationTime").doc("Time spent in compilation and code installation.");
284 
285     /**
286      * Counts the number of compiled {@linkplain CompilationResult#getBytecodeSize() bytecodes}.
287      */
288     private static final CounterKey CompiledBytecodes = DebugContext.counter("CompiledBytecodes");
289 
290     /**
291      * Counts the number of compiled {@linkplain CompilationResult#getBytecodeSize() bytecodes} for
292      * which {@linkplain CompilationResult#getTargetCode()} code was installed.
293      */
294     public static final CounterKey CompiledAndInstalledBytecodes = DebugContext.counter("CompiledAndInstalledBytecodes");
295 
296     /**
297      * Counts the number of installed {@linkplain CompilationResult#getTargetCodeSize()} bytes.
298      */
299     private static final CounterKey InstalledCodeSize = DebugContext.counter("InstalledCodeSize");
300 
301     /**
302      * Time spent in code installation.
303      */
304     public static final TimerKey CodeInstallationTime = DebugContext.timer("CodeInstallation");
305 
runCompilation(OptionValues initialOptions)306     public HotSpotCompilationRequestResult runCompilation(OptionValues initialOptions) {
307         OptionValues options = filterOptions(initialOptions);
308         HotSpotGraalRuntimeProvider graalRuntime = compiler.getGraalRuntime();
309         try (DebugContext debug = graalRuntime.openDebugContext(options, compilationId, getMethod(), compiler.getDebugHandlersFactories(), DebugContext.getDefaultLogStream())) {
310             return runCompilation(debug);
311         }
312     }
313 
314     @SuppressWarnings("try")
runCompilation(DebugContext debug)315     public HotSpotCompilationRequestResult runCompilation(DebugContext debug) {
316         HotSpotGraalRuntimeProvider graalRuntime = compiler.getGraalRuntime();
317         GraalHotSpotVMConfig config = graalRuntime.getVMConfig();
318         int entryBCI = getEntryBCI();
319         boolean isOSR = entryBCI != JVMCICompiler.INVOCATION_ENTRY_BCI;
320         HotSpotResolvedJavaMethod method = getMethod();
321 
322         if (installAsDefault || isOSR) {
323             // If there is already compiled code for this method on our level we simply return.
324             // JVMCI compiles are always at the highest compile level, even in non-tiered mode so we
325             // only need to check for that value.
326             if (method.hasCodeAtLevel(entryBCI, config.compilationLevelFullOptimization)) {
327                 return HotSpotCompilationRequestResult.failure("Already compiled", false);
328             }
329             if (HotSpotGraalCompilerFactory.shouldExclude(method)) {
330                 return HotSpotCompilationRequestResult.failure("GraalCompileOnly excluded", false);
331             }
332         }
333 
334         HotSpotCompilationWrapper compilation = new HotSpotCompilationWrapper();
335         try (DebugCloseable a = CompilationTime.start(debug)) {
336             return compilation.run(debug);
337         } finally {
338             try {
339                 int compiledBytecodes = 0;
340                 int codeSize = 0;
341 
342                 if (compilation.result != null) {
343                     compiledBytecodes = compilation.result.getBytecodeSize();
344                     CompiledBytecodes.add(debug, compiledBytecodes);
345                     if (installedCode != null) {
346                         codeSize = installedCode.getSize();
347                         CompiledAndInstalledBytecodes.add(debug, compiledBytecodes);
348                         InstalledCodeSize.add(debug, codeSize);
349                     }
350                 }
351             } catch (Throwable t) {
352                 return compilation.handleException(t);
353             }
354         }
355     }
356 
357     @SuppressWarnings("try")
installMethod(DebugContext debug, StructuredGraph graph, final CompilationResult compResult)358     private void installMethod(DebugContext debug, StructuredGraph graph, final CompilationResult compResult) {
359         final CodeCacheProvider codeCache = jvmciRuntime.getHostJVMCIBackend().getCodeCache();
360         HotSpotBackend backend = compiler.getGraalRuntime().getHostBackend();
361         installedCode = null;
362         Object[] context = {new DebugDumpScope(getIdString(), true), codeCache, getMethod(), compResult};
363         try (DebugContext.Scope s = debug.scope("CodeInstall", context, graph)) {
364             HotSpotCompilationRequest request = getRequest();
365             installedCode = (HotSpotInstalledCode) backend.createInstalledCode(debug,
366                             request.getMethod(),
367                             request,
368                             compResult,
369                             null,
370                             installAsDefault,
371                             context);
372         } catch (Throwable e) {
373             throw debug.handle(e);
374         }
375     }
376 
377     @Override
toString()378     public String toString() {
379         return "Compilation[id=" + getId() + ", " + getMethod().format("%H.%n(%p)") + (getEntryBCI() == JVMCICompiler.INVOCATION_ENTRY_BCI ? "" : "@" + getEntryBCI()) + "]";
380     }
381 
getRequest()382     private HotSpotCompilationRequest getRequest() {
383         return compilationId.getRequest();
384     }
385 }
386