src/jdk.internal.vm.ci/share/classes/jdk.vm.ci.hotspot/src/jdk/vm/ci/hotspot/HotSpotSpeculationLog.java
changeset 54669 ad45b3802d4e
parent 52381 7f90bc64b0fc
--- a/src/jdk.internal.vm.ci/share/classes/jdk.vm.ci.hotspot/src/jdk/vm/ci/hotspot/HotSpotSpeculationLog.java	Wed May 01 12:41:26 2019 -0400
+++ b/src/jdk.internal.vm.ci/share/classes/jdk.vm.ci.hotspot/src/jdk/vm/ci/hotspot/HotSpotSpeculationLog.java	Wed May 01 12:31:29 2019 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -22,98 +22,340 @@
  */
 package jdk.vm.ci.hotspot;
 
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import static jdk.vm.ci.hotspot.CompilerToVM.compilerToVM;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Formatter;
+import java.util.List;
+
+import jdk.vm.ci.code.BailoutException;
 import jdk.vm.ci.meta.JavaConstant;
 import jdk.vm.ci.meta.SpeculationLog;
 
+/**
+ * Implements a {@link SpeculationLog} that can be used to:
+ * <ul>
+ * <li>Query failed speculations recorded in a native linked list of {@code FailedSpeculation}s (see
+ * methodData.hpp).</li>
+ * <li>Make speculations during compilation and record them in compiled code. This must only be done
+ * on compilation-local {@link HotSpotSpeculationLog} objects.</li>
+ * </ul>
+ *
+ * The choice of constructor determines whether the native failed speculations list is
+ * {@linkplain #managesFailedSpeculations() managed} by a {@link HotSpotSpeculationLog} object.
+ */
 public class HotSpotSpeculationLog implements SpeculationLog {
+
+    private static final byte[] NO_FLATTENED_SPECULATIONS = {};
+
+    /**
+     * Creates a speculation log that manages a failed speculation list. That is, when this object
+     * dies, the native resources of the list are freed.
+     *
+     * @see #managesFailedSpeculations()
+     * @see #getFailedSpeculationsAddress()
+     */
+    public HotSpotSpeculationLog() {
+        managesFailedSpeculations = true;
+    }
+
+    /**
+     * Creates a speculation log that reads from an externally managed failed speculation list. That
+     * is, the lifetime of the list is independent of this object.
+     *
+     * @param failedSpeculationsAddress an address in native memory at which the pointer to the
+     *            externally managed sailed speculation list resides
+     */
+    public HotSpotSpeculationLog(long failedSpeculationsAddress) {
+        if (failedSpeculationsAddress == 0) {
+            throw new IllegalArgumentException("failedSpeculationsAddress cannot be 0");
+        }
+        this.failedSpeculationsAddress = failedSpeculationsAddress;
+        managesFailedSpeculations = false;
+    }
+
+    /**
+     * Gets the address of the pointer to the native failed speculations list.
+     *
+     * @see #managesFailedSpeculations()
+     */
+    public long getFailedSpeculationsAddress() {
+        if (managesFailedSpeculations) {
+            synchronized (this) {
+                if (failedSpeculationsAddress == 0L) {
+                    failedSpeculationsAddress = UnsafeAccess.UNSAFE.allocateMemory(HotSpotJVMCIRuntime.getHostWordKind().getByteCount());
+                    UnsafeAccess.UNSAFE.putAddress(failedSpeculationsAddress, 0L);
+                    LogCleaner c = new LogCleaner(this, failedSpeculationsAddress);
+                    assert c.address == failedSpeculationsAddress;
+                }
+            }
+        }
+        return failedSpeculationsAddress;
+    }
+
+    /**
+     * Adds {@code speculation} to the native list of failed speculations. To update this object's
+     * view of the failed speculations, {@link #collectFailedSpeculations()} must be called after
+     * this method returns.
+     *
+     * This method exists primarily for testing purposes. Speculations are normally only added to
+     * the list by HotSpot during deoptimization.
+     *
+     * @return {@code false} if the speculation could not be appended to the list
+     */
+    public boolean addFailedSpeculation(Speculation speculation) {
+        return compilerToVM().addFailedSpeculation(getFailedSpeculationsAddress(), ((HotSpotSpeculation) speculation).encoding);
+    }
+
+    /**
+     * Returns {@code true} if the value returned by {@link #getFailedSpeculationsAddress()} is only
+     * valid only as long as this object is alive, {@code false} otherwise.
+     */
+    public boolean managesFailedSpeculations() {
+        return managesFailedSpeculations;
+    }
+
     public static final class HotSpotSpeculation extends Speculation {
-        private JavaConstant encoding;
 
-        HotSpotSpeculation(SpeculationReason reason, JavaConstant encoding) {
+        /**
+         * A speculation id is a long encoding an offset (high 32 bits) and a length (low 32 bts).
+         * Combined, the index and length denote where the {@linkplain #encoding encoded
+         * speculation} is in a {@linkplain HotSpotSpeculationLog#getFlattenedSpeculations
+         * flattened} speculations array.
+         */
+        private final JavaConstant id;
+
+        private final byte[] encoding;
+
+        HotSpotSpeculation(SpeculationReason reason, JavaConstant id, byte[] encoding) {
             super(reason);
+            this.id = id;
             this.encoding = encoding;
         }
 
         public JavaConstant getEncoding() {
-            return encoding;
+            return id;
+        }
+
+        @Override
+        public String toString() {
+            long indexAndLength = id.asLong();
+            int index = decodeIndex(indexAndLength);
+            int length = decodeLength(indexAndLength);
+            return String.format("{0x%016x[index: %d, len: %d, hash: 0x%x]: %s}", indexAndLength, index, length, Arrays.hashCode(encoding), getReason());
         }
     }
 
-    /** Written by the C++ code that performs deoptimization. */
-    private volatile long lastFailed;
+    /**
+     * Address of a pointer to a set of failed speculations. The address is recorded in the nmethod
+     * compiled with this speculation log such that when it fails a speculation, the speculation is
+     * added to the list.
+     */
+    private long failedSpeculationsAddress;
+
+    private final boolean managesFailedSpeculations;
 
-    /** All speculations that have caused a deoptimization. */
-    private Set<SpeculationReason> failedSpeculations;
+    /**
+     * The list of failed speculations read from native memory via
+     * {@link CompilerToVM#getFailedSpeculations}.
+     */
+    private byte[][] failedSpeculations;
 
-    /** Strong references to all reasons embedded in the current nmethod. */
-    private HashMap<SpeculationReason, JavaConstant> speculations;
-
-    private long currentSpeculationID;
+    /**
+     * Speculations made during the compilation associated with this log.
+     */
+    private List<byte[]> speculations;
+    private List<SpeculationReason> speculationReasons;
 
     @Override
-    public synchronized void collectFailedSpeculations() {
-        if (lastFailed != 0) {
-            if (failedSpeculations == null) {
-                failedSpeculations = new HashSet<>(2);
-            }
-            if (speculations != null) {
-                SpeculationReason lastFailedSpeculation = lookupSpeculation(this.lastFailed);
-                if (lastFailedSpeculation != null) {
-                    failedSpeculations.add(lastFailedSpeculation);
-                }
-                lastFailed = 0;
-                speculations = null;
-            }
+    public void collectFailedSpeculations() {
+        if (failedSpeculationsAddress != 0 && UnsafeAccess.UNSAFE.getLong(failedSpeculationsAddress) != 0) {
+            failedSpeculations = compilerToVM().getFailedSpeculations(failedSpeculationsAddress, failedSpeculations);
+            assert failedSpeculations.getClass() == byte[][].class;
         }
     }
 
-    private SpeculationReason lookupSpeculation(long value) {
-        for (Map.Entry<SpeculationReason, JavaConstant> entry : speculations.entrySet()) {
-            if (value == entry.getValue().asLong()) {
-                return entry.getKey();
+    byte[] getFlattenedSpeculations(boolean validate) {
+        if (speculations == null) {
+            return NO_FLATTENED_SPECULATIONS;
+        }
+        if (validate) {
+            int newFailuresStart = failedSpeculations == null ? 0 : failedSpeculations.length;
+            collectFailedSpeculations();
+            if (failedSpeculations != null && failedSpeculations.length != newFailuresStart) {
+                for (SpeculationReason reason : speculationReasons) {
+                    byte[] encoding = encode(reason);
+                    // Only check against new failures
+                    if (contains(failedSpeculations, newFailuresStart, encoding)) {
+                        throw new BailoutException(false, "Speculation failed: " + reason);
+                    }
+                }
             }
         }
-        return null;
+        int size = 0;
+        for (byte[] s : speculations) {
+            size += s.length;
+        }
+        byte[] result = new byte[size];
+        size = 0;
+        for (byte[] s : speculations) {
+            System.arraycopy(s, 0, result, size, s.length);
+            size += s.length;
+        }
+        return result;
     }
 
     @Override
-    public synchronized boolean maySpeculate(SpeculationReason reason) {
-        if (failedSpeculations != null && failedSpeculations.contains(reason)) {
-            return false;
+    public boolean maySpeculate(SpeculationReason reason) {
+        if (failedSpeculations == null) {
+            collectFailedSpeculations();
+        }
+        if (failedSpeculations != null && failedSpeculations.length != 0) {
+            byte[] encoding = encode(reason);
+            return !contains(failedSpeculations, 0, encoding);
         }
         return true;
     }
 
-    @Override
-    public synchronized Speculation speculate(SpeculationReason reason) {
-        if (speculations == null) {
-            speculations = new HashMap<>();
+    /**
+     * @return {@code true} if {@code needle} is in {@code haystack[fromIndex..haystack.length-1]}
+     */
+    private static boolean contains(byte[][] haystack, int fromIndex, byte[] needle) {
+        for (int i = fromIndex; i < haystack.length; i++) {
+            byte[] fs = haystack[i];
+
+            if (Arrays.equals(fs, needle)) {
+                return true;
+            }
         }
-        JavaConstant id = speculations.get(reason);
-        if (id == null) {
-            id = JavaConstant.forLong(++currentSpeculationID);
-            speculations.put(reason, id);
+        return false;
+    }
+
+    private static long encodeIndexAndLength(int index, int length) {
+        return ((long) index) << 32 | length;
+    }
+
+    private static int decodeIndex(long indexAndLength) {
+        return (int) (indexAndLength >>> 32);
+    }
+
+    private static int decodeLength(long indexAndLength) {
+        return (int) indexAndLength & 0xFFFFFFFF;
+    }
+
+    @Override
+    public Speculation speculate(SpeculationReason reason) {
+        byte[] encoding = encode(reason);
+        JavaConstant id;
+        if (speculations == null) {
+            speculations = new ArrayList<>();
+            speculationReasons = new ArrayList<>();
+            id = JavaConstant.forLong(encodeIndexAndLength(0, encoding.length));
+            speculations.add(encoding);
+            speculationReasons.add(reason);
+        } else {
+            id = null;
+            int flattenedIndex = 0;
+            for (byte[] fs : speculations) {
+                if (Arrays.equals(fs, encoding)) {
+                    id = JavaConstant.forLong(encodeIndexAndLength(flattenedIndex, fs.length));
+                    break;
+                }
+                flattenedIndex += fs.length;
+            }
+            if (id == null) {
+                id = JavaConstant.forLong(encodeIndexAndLength(flattenedIndex, encoding.length));
+                speculations.add(encoding);
+                speculationReasons.add(reason);
+            }
         }
-        return new HotSpotSpeculation(reason, id);
+
+        return new HotSpotSpeculation(reason, id, encoding);
+    }
+
+    private static byte[] encode(SpeculationReason reason) {
+        HotSpotSpeculationEncoding encoding = (HotSpotSpeculationEncoding) reason.encode(HotSpotSpeculationEncoding::new);
+        byte[] result = encoding == null ? null : encoding.getByteArray();
+        if (result == null) {
+            throw new IllegalArgumentException(HotSpotSpeculationLog.class.getName() + " expects " + reason.getClass().getName() + ".encode() to return a non-empty encoding");
+        }
+        return result;
+    }
+
+    @Override
+    public boolean hasSpeculations() {
+        return speculations != null;
     }
 
     @Override
-    public synchronized boolean hasSpeculations() {
-        return speculations != null && !speculations.isEmpty();
+    public Speculation lookupSpeculation(JavaConstant constant) {
+        if (constant.isDefaultForKind()) {
+            return NO_SPECULATION;
+        }
+        int flattenedIndex = decodeIndex(constant.asLong());
+        int index = 0;
+        for (byte[] s : speculations) {
+            if (flattenedIndex == 0) {
+                SpeculationReason reason = speculationReasons.get(index);
+                return new HotSpotSpeculation(reason, constant, s);
+            }
+            index++;
+            flattenedIndex -= s.length;
+        }
+        throw new IllegalArgumentException("Unknown encoded speculation: " + constant);
     }
 
     @Override
-    public synchronized Speculation lookupSpeculation(JavaConstant constant) {
-        if (constant.isDefaultForKind()) {
-            return NO_SPECULATION;
+    public String toString() {
+        Formatter buf = new Formatter();
+        buf.format("{managed:%s, failedSpeculationsAddress:0x%x, failedSpeculations:[", managesFailedSpeculations, failedSpeculationsAddress);
+
+        String sep = "";
+        if (failedSpeculations != null) {
+            for (int i = 0; i < failedSpeculations.length; i++) {
+                buf.format("%s{len:%d, hash:0x%x}", sep, failedSpeculations[i].length, Arrays.hashCode(failedSpeculations[i]));
+                sep = ", ";
+            }
+        }
+
+        buf.format("], speculations:[");
+
+        int size = 0;
+        if (speculations != null) {
+            sep = "";
+            for (int i = 0; i < speculations.size(); i++) {
+                byte[] s = speculations.get(i);
+                size += s.length;
+                buf.format("%s{len:%d, hash:0x%x, reason:{%s}}", sep, s.length, Arrays.hashCode(s), speculationReasons.get(i));
+                sep = ", ";
+            }
         }
-        SpeculationReason reason = lookupSpeculation(constant.asLong());
-        assert reason != null : "Speculation should have been registered";
-        return new HotSpotSpeculation(reason, constant);
+        buf.format("], len:%d, hash:0x%x}", size, Arrays.hashCode(getFlattenedSpeculations(false)));
+        return buf.toString();
+    }
+
+    /**
+     * Frees the native memory resources associated with {@link HotSpotSpeculationLog}s once they
+     * become reclaimable.
+     */
+    private static final class LogCleaner extends Cleaner {
+
+        LogCleaner(HotSpotSpeculationLog referent, long address) {
+            super(referent);
+            this.address = address;
+        }
+
+        @Override
+        void doCleanup() {
+            long pointer = UnsafeAccess.UNSAFE.getAddress(address);
+            if (pointer != 0) {
+                compilerToVM().releaseFailedSpeculations(address);
+            }
+            UnsafeAccess.UNSAFE.freeMemory(address);
+        }
+
+        final long address;
     }
 }
+