1 /*
   2  * Copyright (c) 2012, 2018, 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.hotspot.HotSpotCompiledCodeBuilder.Options.ShowSubstitutionSourceInfo;
  28 import static org.graalvm.util.CollectionsUtil.anyMatch;
  29 
  30 import java.nio.ByteBuffer;
  31 import java.nio.ByteOrder;
  32 import java.util.ArrayList;
  33 import java.util.Collections;
  34 import java.util.Comparator;
  35 import java.util.EnumMap;
  36 import java.util.List;
  37 import java.util.ListIterator;
  38 import java.util.Map;
  39 import java.util.stream.Stream;
  40 import java.util.stream.Stream.Builder;
  41 
  42 import org.graalvm.compiler.api.replacements.MethodSubstitution;
  43 import org.graalvm.compiler.api.replacements.Snippet;
  44 import org.graalvm.compiler.code.CompilationResult;
  45 import org.graalvm.compiler.code.CompilationResult.CodeAnnotation;
  46 import org.graalvm.compiler.code.CompilationResult.CodeComment;
  47 import org.graalvm.compiler.code.CompilationResult.JumpTable;
  48 import org.graalvm.compiler.code.DataSection;
  49 import org.graalvm.compiler.code.SourceMapping;
  50 import org.graalvm.compiler.debug.GraalError;
  51 import org.graalvm.compiler.graph.NodeSourcePosition;
  52 import org.graalvm.compiler.options.Option;
  53 import org.graalvm.compiler.options.OptionKey;
  54 import org.graalvm.compiler.options.OptionValues;
  55 
  56 import jdk.vm.ci.code.CodeCacheProvider;
  57 import jdk.vm.ci.code.DebugInfo;
  58 import jdk.vm.ci.code.StackSlot;
  59 import jdk.vm.ci.code.site.ConstantReference;
  60 import jdk.vm.ci.code.site.DataPatch;
  61 import jdk.vm.ci.code.site.Infopoint;
  62 import jdk.vm.ci.code.site.InfopointReason;
  63 import jdk.vm.ci.code.site.Mark;
  64 import jdk.vm.ci.code.site.Site;
  65 import jdk.vm.ci.hotspot.HotSpotCompilationRequest;
  66 import jdk.vm.ci.hotspot.HotSpotCompiledCode;
  67 import jdk.vm.ci.hotspot.HotSpotCompiledCode.Comment;
  68 import jdk.vm.ci.hotspot.HotSpotCompiledNmethod;
  69 import jdk.vm.ci.hotspot.HotSpotResolvedJavaMethod;
  70 import jdk.vm.ci.meta.Assumptions.Assumption;
  71 import jdk.vm.ci.meta.ResolvedJavaMethod;
  72 
  73 public class HotSpotCompiledCodeBuilder {
  74     public static class Options {
  75         // @formatter:off
  76         @Option(help = "Controls whether the source position information of snippets and method substitutions" +
  77                 " are exposed to HotSpot.  Can be useful when profiling to get more precise position information.")
  78         public static final OptionKey<Boolean> ShowSubstitutionSourceInfo = new OptionKey<>(false);
  79     }
  80 
  81     public static HotSpotCompiledCode createCompiledCode(CodeCacheProvider codeCache, ResolvedJavaMethod method, HotSpotCompilationRequest compRequest, CompilationResult compResult, OptionValues options) {
  82         String name = compResult.getName();
  83 
  84         byte[] targetCode = compResult.getTargetCode();
  85         int targetCodeSize = compResult.getTargetCodeSize();
  86 
  87         Site[] sites = getSortedSites(compResult, options, codeCache.shouldDebugNonSafepoints() && method != null);
  88 
  89         Assumption[] assumptions = compResult.getAssumptions();
  90 
  91         ResolvedJavaMethod[] methods = compResult.getMethods();
  92 
  93         List<CodeAnnotation> annotations = compResult.getAnnotations();
  94         Comment[] comments = new Comment[annotations.size()];
  95         if (!annotations.isEmpty()) {
  96             for (int i = 0; i < comments.length; i++) {
  97                 CodeAnnotation annotation = annotations.get(i);
  98                 String text;
  99                 if (annotation instanceof CodeComment) {
 100                     CodeComment codeComment = (CodeComment) annotation;
 101                     text = codeComment.value;
 102                 } else if (annotation instanceof JumpTable) {
 103                     JumpTable jumpTable = (JumpTable) annotation;
 104                     text = "JumpTable [" + jumpTable.low + " .. " + jumpTable.high + "]";
 105                 } else {
 106                     text = annotation.toString();
 107                 }
 108                 comments[i] = new Comment(annotation.position, text);
 109             }
 110         }
 111 
 112         DataSection data = compResult.getDataSection();
 113         byte[] dataSection = new byte[data.getSectionSize()];
 114 
 115         ByteBuffer buffer = ByteBuffer.wrap(dataSection).order(ByteOrder.nativeOrder());
 116         Builder<DataPatch> patchBuilder = Stream.builder();
 117         data.buildDataSection(buffer, (position, vmConstant) -> {
 118             patchBuilder.accept(new DataPatch(position, new ConstantReference(vmConstant)));
 119         });
 120 
 121         int dataSectionAlignment = data.getSectionAlignment();
 122         DataPatch[] dataSectionPatches = patchBuilder.build().toArray(len -> new DataPatch[len]);
 123 
 124         int totalFrameSize = compResult.getTotalFrameSize();
 125         StackSlot customStackArea = compResult.getCustomStackArea();
 126         boolean isImmutablePIC = compResult.isImmutablePIC();
 127 
 128         if (method instanceof HotSpotResolvedJavaMethod) {
 129             HotSpotResolvedJavaMethod hsMethod = (HotSpotResolvedJavaMethod) method;
 130             int entryBCI = compResult.getEntryBCI();
 131             boolean hasUnsafeAccess = compResult.hasUnsafeAccess();
 132 
 133             int id;
 134             long jvmciEnv;
 135             if (compRequest != null) {
 136                 id = compRequest.getId();
 137                 jvmciEnv = compRequest.getJvmciEnv();
 138             } else {
 139                 id = hsMethod.allocateCompileId(entryBCI);
 140                 jvmciEnv = 0L;
 141             }
 142             return new HotSpotCompiledNmethod(name, targetCode, targetCodeSize, sites, assumptions, methods, comments, dataSection, dataSectionAlignment, dataSectionPatches, isImmutablePIC,
 143                             totalFrameSize, customStackArea, hsMethod, entryBCI, id, jvmciEnv, hasUnsafeAccess);
 144         } else {
 145             return new HotSpotCompiledCode(name, targetCode, targetCodeSize, sites, assumptions, methods, comments, dataSection, dataSectionAlignment, dataSectionPatches, isImmutablePIC,
 146                             totalFrameSize, customStackArea);
 147         }
 148     }
 149 
 150     static class SiteComparator implements Comparator<Site> {
 151 
 152         /**
 153          * Defines an order for sorting {@link Infopoint}s based on their
 154          * {@linkplain Infopoint#reason reasons}. This is used to choose which infopoint to preserve
 155          * when multiple infopoints collide on the same PC offset. A negative order value implies a
 156          * non-optional infopoint (i.e., must be preserved).
 157          */
 158         static final Map<InfopointReason, Integer> HOTSPOT_INFOPOINT_SORT_ORDER = new EnumMap<>(InfopointReason.class);
 159 
 160         static {
 161             HOTSPOT_INFOPOINT_SORT_ORDER.put(InfopointReason.SAFEPOINT, -4);
 162             HOTSPOT_INFOPOINT_SORT_ORDER.put(InfopointReason.CALL, -3);
 163             HOTSPOT_INFOPOINT_SORT_ORDER.put(InfopointReason.IMPLICIT_EXCEPTION, -2);
 164             HOTSPOT_INFOPOINT_SORT_ORDER.put(InfopointReason.METHOD_START, 2);
 165             HOTSPOT_INFOPOINT_SORT_ORDER.put(InfopointReason.METHOD_END, 3);
 166             HOTSPOT_INFOPOINT_SORT_ORDER.put(InfopointReason.BYTECODE_POSITION, 4);
 167         }
 168 
 169         static int ord(Infopoint info) {
 170             return HOTSPOT_INFOPOINT_SORT_ORDER.get(info.reason);
 171         }
 172 
 173         static int checkCollision(Infopoint i1, Infopoint i2) {
 174             int o1 = ord(i1);
 175             int o2 = ord(i2);
 176             if (o1 < 0 && o2 < 0) {
 177                 throw new GraalError("Non optional infopoints cannot collide: %s and %s", i1, i2);
 178             }
 179             return o1 - o2;
 180         }
 181 
 182         /**
 183          * Records whether any two {@link Infopoint}s had the same {@link Infopoint#pcOffset}.
 184          */
 185         boolean sawCollidingInfopoints;
 186 
 187         @Override
 188         public int compare(Site s1, Site s2) {
 189             if (s1.pcOffset == s2.pcOffset) {
 190                 // Marks must come first since patching a call site
 191                 // may need to know the mark denoting the call type
 192                 // (see uses of CodeInstaller::_next_call_type).
 193                 boolean s1IsMark = s1 instanceof Mark;
 194                 boolean s2IsMark = s2 instanceof Mark;
 195                 if (s1IsMark != s2IsMark) {
 196                     return s1IsMark ? -1 : 1;
 197                 }
 198 
 199                 // Infopoints must group together so put them after
 200                 // other Site types.
 201                 boolean s1IsInfopoint = s1 instanceof Infopoint;
 202                 boolean s2IsInfopoint = s2 instanceof Infopoint;
 203                 if (s1IsInfopoint != s2IsInfopoint) {
 204                     return s1IsInfopoint ? 1 : -1;
 205                 }
 206 
 207                 if (s1IsInfopoint) {
 208                     sawCollidingInfopoints = true;
 209                     return checkCollision((Infopoint) s1, (Infopoint) s2);
 210                 }
 211             }
 212             return s1.pcOffset - s2.pcOffset;
 213         }
 214     }
 215 
 216     /**
 217      * HotSpot expects sites to be presented in ascending order of PC (see
 218      * {@code DebugInformationRecorder::add_new_pc_offset}). In addition, it expects
 219      * {@link Infopoint} PCs to be unique.
 220      */
 221     private static Site[] getSortedSites(CompilationResult target, OptionValues options, boolean includeSourceInfo) {
 222         List<Site> sites = new ArrayList<>(
 223                         target.getExceptionHandlers().size() + target.getInfopoints().size() + target.getDataPatches().size() + target.getMarks().size() + target.getSourceMappings().size());
 224         sites.addAll(target.getExceptionHandlers());
 225         sites.addAll(target.getInfopoints());
 226         sites.addAll(target.getDataPatches());
 227         sites.addAll(target.getMarks());
 228 
 229         if (includeSourceInfo) {
 230             /*
 231              * Translate the source mapping into appropriate info points. In HotSpot only one
 232              * position can really be represented and recording the end PC seems to give the best
 233              * results and corresponds with what C1 and C2 do. HotSpot doesn't like to see these
 234              * unless -XX:+DebugNonSafepoints is enabled, so don't emit them in that case.
 235              */
 236 
 237             List<SourceMapping> sourceMappings = new ArrayList<>();
 238             ListIterator<SourceMapping> sourceMappingListIterator = target.getSourceMappings().listIterator();
 239             if (sourceMappingListIterator.hasNext()) {
 240                 SourceMapping currentSource = sourceMappingListIterator.next();
 241                 NodeSourcePosition sourcePosition = currentSource.getSourcePosition();
 242                 if (!sourcePosition.isPlaceholder() && !sourcePosition.isSubstitution()) {
 243                     sourceMappings.add(currentSource);
 244                 }
 245                 while (sourceMappingListIterator.hasNext()) {
 246                     SourceMapping nextSource = sourceMappingListIterator.next();
 247                     assert currentSource.getStartOffset() <= nextSource.getStartOffset() : "Must be presorted";
 248                     currentSource = nextSource;
 249                     sourcePosition = currentSource.getSourcePosition();
 250                     if (!sourcePosition.isPlaceholder() && !sourcePosition.isSubstitution()) {
 251                         sourceMappings.add(currentSource);
 252                     }
 253                 }
 254             }
 255 
 256             /*
 257              * Don't add BYTECODE_POSITION info points that would potentially create conflicts.
 258              * Under certain conditions the site's pc is not the pc that gets recorded by HotSpot
 259              * (see @code {CodeInstaller::site_Call}). So, avoid adding any source positions that
 260              * can potentially map to the same pc. To do that the following code makes sure that the
 261              * source mapping doesn't contain a pc of any important Site.
 262              */
 263             sites.sort(new SiteComparator());
 264 
 265             ListIterator<Site> siteListIterator = sites.listIterator();
 266             sourceMappingListIterator = sourceMappings.listIterator();
 267 
 268             List<Site> sourcePositionSites = new ArrayList<>();
 269             Site site = null;
 270 
 271             // Iterate over sourceMappings and sites in parallel. Create source position infopoints
 272             // only for source mappings that don't have any sites inside their intervals.
 273             while (sourceMappingListIterator.hasNext()) {
 274                 SourceMapping source = sourceMappingListIterator.next();
 275 
 276                 // Skip sites before the current source mapping
 277                 if (site == null || site.pcOffset < source.getStartOffset()) {
 278                     while (siteListIterator.hasNext()) {
 279                         site = siteListIterator.next();
 280                         if (site.pcOffset >= source.getStartOffset()) {
 281                             break;
 282                         }
 283                     }
 284                 }
 285                 assert !siteListIterator.hasNext() || site.pcOffset >= source.getStartOffset();
 286                 if (site != null && source.getStartOffset() <= site.pcOffset && site.pcOffset <= source.getEndOffset()) {
 287                     // Conflicting source mapping, skip it.
 288                     continue;
 289                 } else {
 290                     // Since the sites are sorted there can not be any more sites in this interval.
 291                 }
 292                 assert !siteListIterator.hasNext() || site.pcOffset > source.getEndOffset();
 293                 // Good source mapping. Create an infopoint and add it to the list.
 294                 NodeSourcePosition sourcePosition = source.getSourcePosition();
 295                 assert sourcePosition.verify();
 296                 if (!ShowSubstitutionSourceInfo.getValue(options)) {
 297                     sourcePosition = sourcePosition.trim();
 298                     assert verifyTrim(sourcePosition);
 299                 }
 300                 if (sourcePosition != null) {
 301                     assert !anyMatch(sites, s -> source.getStartOffset() <= s.pcOffset && s.pcOffset <= source.getEndOffset());
 302                     sourcePositionSites.add(new Infopoint(source.getEndOffset(), new DebugInfo(sourcePosition), InfopointReason.BYTECODE_POSITION));
 303                 }
 304             }
 305 
 306             sites.addAll(sourcePositionSites);
 307         }
 308 
 309         SiteComparator c = new SiteComparator();
 310         Collections.sort(sites, c);
 311 
 312         if (c.sawCollidingInfopoints) {
 313             Infopoint lastInfopoint = null;
 314             List<Site> copy = new ArrayList<>(sites.size());
 315             for (Site site : sites) {
 316                 if (site instanceof Infopoint) {
 317                     Infopoint info = (Infopoint) site;
 318                     if (lastInfopoint == null || lastInfopoint.pcOffset != info.pcOffset) {
 319                         lastInfopoint = info;
 320                         copy.add(info);
 321                     } else {
 322                         // Omit this colliding infopoint
 323                         assert lastInfopoint.reason.compareTo(info.reason) <= 0;
 324                     }
 325                 } else {
 326                     copy.add(site);
 327                 }
 328             }
 329             sites = copy;
 330         }
 331 
 332         return sites.toArray(new Site[sites.size()]);
 333     }
 334 
 335     private static boolean verifyTrim(NodeSourcePosition sourcePosition) {
 336         for (NodeSourcePosition sp = sourcePosition; sp != null; sp = sp.getCaller()) {
 337             assert (sp.getMethod().getAnnotation(Snippet.class) == null && sp.getMethod().getAnnotation(MethodSubstitution.class) == null);
 338         }
 339         return true;
 340     }
 341 }