1 /*
   2  * Copyright (c) 2014, 2019, 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.hotspot.CompilerToVM.compilerToVM;
  26 
  27 import java.util.ArrayList;
  28 import java.util.Arrays;
  29 import java.util.Formatter;
  30 import java.util.List;
  31 
  32 import jdk.vm.ci.code.BailoutException;
  33 import jdk.vm.ci.meta.JavaConstant;
  34 import jdk.vm.ci.meta.SpeculationLog;
  35 
  36 /**
  37  * Implements a {@link SpeculationLog} that can be used to:
  38  * <ul>
  39  * <li>Query failed speculations recorded in a native linked list of {@code FailedSpeculation}s (see
  40  * methodData.hpp).</li>
  41  * <li>Make speculations during compilation and record them in compiled code. This must only be done
  42  * on compilation-local {@link HotSpotSpeculationLog} objects.</li>
  43  * </ul>
  44  *
  45  * The choice of constructor determines whether the native failed speculations list is
  46  * {@linkplain #managesFailedSpeculations() managed} by a {@link HotSpotSpeculationLog} object.
  47  */
  48 public class HotSpotSpeculationLog implements SpeculationLog {
  49 
  50     private static final byte[] NO_FLATTENED_SPECULATIONS = {};
  51 
  52     /**
  53      * Creates a speculation log that manages a failed speculation list. That is, when this object
  54      * dies, the native resources of the list are freed.
  55      *
  56      * @see #managesFailedSpeculations()
  57      * @see #getFailedSpeculationsAddress()
  58      */
  59     public HotSpotSpeculationLog() {
  60         managesFailedSpeculations = true;
  61     }
  62 
  63     /**
  64      * Creates a speculation log that reads from an externally managed failed speculation list. That
  65      * is, the lifetime of the list is independent of this object.
  66      *
  67      * @param failedSpeculationsAddress an address in native memory at which the pointer to the
  68      *            externally managed sailed speculation list resides
  69      */
  70     public HotSpotSpeculationLog(long failedSpeculationsAddress) {
  71         if (failedSpeculationsAddress == 0) {
  72             throw new IllegalArgumentException("failedSpeculationsAddress cannot be 0");
  73         }
  74         this.failedSpeculationsAddress = failedSpeculationsAddress;
  75         managesFailedSpeculations = false;
  76     }
  77 
  78     /**
  79      * Gets the address of the pointer to the native failed speculations list.
  80      *
  81      * @see #managesFailedSpeculations()
  82      */
  83     public long getFailedSpeculationsAddress() {
  84         if (managesFailedSpeculations) {
  85             synchronized (this) {
  86                 if (failedSpeculationsAddress == 0L) {
  87                     failedSpeculationsAddress = UnsafeAccess.UNSAFE.allocateMemory(HotSpotJVMCIRuntime.getHostWordKind().getByteCount());
  88                     UnsafeAccess.UNSAFE.putAddress(failedSpeculationsAddress, 0L);
  89                     LogCleaner c = new LogCleaner(this, failedSpeculationsAddress);
  90                     assert c.address == failedSpeculationsAddress;
  91                 }
  92             }
  93         }
  94         return failedSpeculationsAddress;
  95     }
  96 
  97     /**
  98      * Adds {@code speculation} to the native list of failed speculations. To update this object's
  99      * view of the failed speculations, {@link #collectFailedSpeculations()} must be called after
 100      * this method returns.
 101      *
 102      * This method exists primarily for testing purposes. Speculations are normally only added to
 103      * the list by HotSpot during deoptimization.
 104      *
 105      * @return {@code false} if the speculation could not be appended to the list
 106      */
 107     public boolean addFailedSpeculation(Speculation speculation) {
 108         return compilerToVM().addFailedSpeculation(getFailedSpeculationsAddress(), ((HotSpotSpeculation) speculation).encoding);
 109     }
 110 
 111     /**
 112      * Returns {@code true} if the value returned by {@link #getFailedSpeculationsAddress()} is only
 113      * valid only as long as this object is alive, {@code false} otherwise.
 114      */
 115     public boolean managesFailedSpeculations() {
 116         return managesFailedSpeculations;
 117     }
 118 
 119     public static final class HotSpotSpeculation extends Speculation {
 120 
 121         /**
 122          * A speculation id is a long encoding an offset (high 32 bits) and a length (low 32 bts).
 123          * Combined, the index and length denote where the {@linkplain #encoding encoded
 124          * speculation} is in a {@linkplain HotSpotSpeculationLog#getFlattenedSpeculations
 125          * flattened} speculations array.
 126          */
 127         private final JavaConstant id;
 128 
 129         private final byte[] encoding;
 130 
 131         HotSpotSpeculation(SpeculationReason reason, JavaConstant id, byte[] encoding) {
 132             super(reason);
 133             this.id = id;
 134             this.encoding = encoding;
 135         }
 136 
 137         public JavaConstant getEncoding() {
 138             return id;
 139         }
 140 
 141         @Override
 142         public String toString() {
 143             long indexAndLength = id.asLong();
 144             int index = decodeIndex(indexAndLength);
 145             int length = decodeLength(indexAndLength);
 146             return String.format("{0x%016x[index: %d, len: %d, hash: 0x%x]: %s}", indexAndLength, index, length, Arrays.hashCode(encoding), getReason());
 147         }
 148     }
 149 
 150     /**
 151      * Address of a pointer to a set of failed speculations. The address is recorded in the nmethod
 152      * compiled with this speculation log such that when it fails a speculation, the speculation is
 153      * added to the list.
 154      */
 155     private long failedSpeculationsAddress;
 156 
 157     private final boolean managesFailedSpeculations;
 158 
 159     /**
 160      * The list of failed speculations read from native memory via
 161      * {@link CompilerToVM#getFailedSpeculations}.
 162      */
 163     private byte[][] failedSpeculations;
 164 
 165     /**
 166      * Speculations made during the compilation associated with this log.
 167      */
 168     private List<byte[]> speculations;
 169     private List<SpeculationReason> speculationReasons;
 170 
 171     @Override
 172     public void collectFailedSpeculations() {
 173         if (failedSpeculationsAddress != 0 && UnsafeAccess.UNSAFE.getLong(failedSpeculationsAddress) != 0) {
 174             failedSpeculations = compilerToVM().getFailedSpeculations(failedSpeculationsAddress, failedSpeculations);
 175             assert failedSpeculations.getClass() == byte[][].class;
 176         }
 177     }
 178 
 179     byte[] getFlattenedSpeculations(boolean validate) {
 180         if (speculations == null) {
 181             return NO_FLATTENED_SPECULATIONS;
 182         }
 183         if (validate) {
 184             int newFailuresStart = failedSpeculations == null ? 0 : failedSpeculations.length;
 185             collectFailedSpeculations();
 186             if (failedSpeculations != null && failedSpeculations.length != newFailuresStart) {
 187                 for (SpeculationReason reason : speculationReasons) {
 188                     byte[] encoding = encode(reason);
 189                     // Only check against new failures
 190                     if (contains(failedSpeculations, newFailuresStart, encoding)) {
 191                         throw new BailoutException(false, "Speculation failed: " + reason);
 192                     }
 193                 }
 194             }
 195         }
 196         int size = 0;
 197         for (byte[] s : speculations) {
 198             size += s.length;
 199         }
 200         byte[] result = new byte[size];
 201         size = 0;
 202         for (byte[] s : speculations) {
 203             System.arraycopy(s, 0, result, size, s.length);
 204             size += s.length;
 205         }
 206         return result;
 207     }
 208 
 209     @Override
 210     public boolean maySpeculate(SpeculationReason reason) {
 211         if (failedSpeculations == null) {
 212             collectFailedSpeculations();
 213         }
 214         if (failedSpeculations != null && failedSpeculations.length != 0) {
 215             byte[] encoding = encode(reason);
 216             return !contains(failedSpeculations, 0, encoding);
 217         }
 218         return true;
 219     }
 220 
 221     /**
 222      * @return {@code true} if {@code needle} is in {@code haystack[fromIndex..haystack.length-1]}
 223      */
 224     private static boolean contains(byte[][] haystack, int fromIndex, byte[] needle) {
 225         for (int i = fromIndex; i < haystack.length; i++) {
 226             byte[] fs = haystack[i];
 227 
 228             if (Arrays.equals(fs, needle)) {
 229                 return true;
 230             }
 231         }
 232         return false;
 233     }
 234 
 235     private static long encodeIndexAndLength(int index, int length) {
 236         return ((long) index) << 32 | length;
 237     }
 238 
 239     private static int decodeIndex(long indexAndLength) {
 240         return (int) (indexAndLength >>> 32);
 241     }
 242 
 243     private static int decodeLength(long indexAndLength) {
 244         return (int) indexAndLength & 0xFFFFFFFF;
 245     }
 246 
 247     @Override
 248     public Speculation speculate(SpeculationReason reason) {
 249         byte[] encoding = encode(reason);
 250         JavaConstant id;
 251         if (speculations == null) {
 252             speculations = new ArrayList<>();
 253             speculationReasons = new ArrayList<>();
 254             id = JavaConstant.forLong(encodeIndexAndLength(0, encoding.length));
 255             speculations.add(encoding);
 256             speculationReasons.add(reason);
 257         } else {
 258             id = null;
 259             int flattenedIndex = 0;
 260             for (byte[] fs : speculations) {
 261                 if (Arrays.equals(fs, encoding)) {
 262                     id = JavaConstant.forLong(encodeIndexAndLength(flattenedIndex, fs.length));
 263                     break;
 264                 }
 265                 flattenedIndex += fs.length;
 266             }
 267             if (id == null) {
 268                 id = JavaConstant.forLong(encodeIndexAndLength(flattenedIndex, encoding.length));
 269                 speculations.add(encoding);
 270                 speculationReasons.add(reason);
 271             }
 272         }
 273 
 274         return new HotSpotSpeculation(reason, id, encoding);
 275     }
 276 
 277     private static byte[] encode(SpeculationReason reason) {
 278         HotSpotSpeculationEncoding encoding = (HotSpotSpeculationEncoding) reason.encode(HotSpotSpeculationEncoding::new);
 279         byte[] result = encoding == null ? null : encoding.getByteArray();
 280         if (result == null) {
 281             throw new IllegalArgumentException(HotSpotSpeculationLog.class.getName() + " expects " + reason.getClass().getName() + ".encode() to return a non-empty encoding");
 282         }
 283         return result;
 284     }
 285 
 286     @Override
 287     public boolean hasSpeculations() {
 288         return speculations != null;
 289     }
 290 
 291     @Override
 292     public Speculation lookupSpeculation(JavaConstant constant) {
 293         if (constant.isDefaultForKind()) {
 294             return NO_SPECULATION;
 295         }
 296         int flattenedIndex = decodeIndex(constant.asLong());
 297         int index = 0;
 298         for (byte[] s : speculations) {
 299             if (flattenedIndex == 0) {
 300                 SpeculationReason reason = speculationReasons.get(index);
 301                 return new HotSpotSpeculation(reason, constant, s);
 302             }
 303             index++;
 304             flattenedIndex -= s.length;
 305         }
 306         throw new IllegalArgumentException("Unknown encoded speculation: " + constant);
 307     }
 308 
 309     @Override
 310     public String toString() {
 311         Formatter buf = new Formatter();
 312         buf.format("{managed:%s, failedSpeculationsAddress:0x%x, failedSpeculations:[", managesFailedSpeculations, failedSpeculationsAddress);
 313 
 314         String sep = "";
 315         if (failedSpeculations != null) {
 316             for (int i = 0; i < failedSpeculations.length; i++) {
 317                 buf.format("%s{len:%d, hash:0x%x}", sep, failedSpeculations[i].length, Arrays.hashCode(failedSpeculations[i]));
 318                 sep = ", ";
 319             }
 320         }
 321 
 322         buf.format("], speculations:[");
 323 
 324         int size = 0;
 325         if (speculations != null) {
 326             sep = "";
 327             for (int i = 0; i < speculations.size(); i++) {
 328                 byte[] s = speculations.get(i);
 329                 size += s.length;
 330                 buf.format("%s{len:%d, hash:0x%x, reason:{%s}}", sep, s.length, Arrays.hashCode(s), speculationReasons.get(i));
 331                 sep = ", ";
 332             }
 333         }
 334         buf.format("], len:%d, hash:0x%x}", size, Arrays.hashCode(getFlattenedSpeculations(false)));
 335         return buf.toString();
 336     }
 337 
 338     /**
 339      * Frees the native memory resources associated with {@link HotSpotSpeculationLog}s once they
 340      * become reclaimable.
 341      */
 342     private static final class LogCleaner extends Cleaner {
 343 
 344         LogCleaner(HotSpotSpeculationLog referent, long address) {
 345             super(referent);
 346             this.address = address;
 347         }
 348 
 349         @Override
 350         void doCleanup() {
 351             long pointer = UnsafeAccess.UNSAFE.getAddress(address);
 352             if (pointer != 0) {
 353                 compilerToVM().releaseFailedSpeculations(address);
 354             }
 355             UnsafeAccess.UNSAFE.freeMemory(address);
 356         }
 357 
 358         final long address;
 359     }
 360 }
 361