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