From 68d515eb3c9eeff1e36b87135f8b6d82528227f3 Mon Sep 17 00:00:00 2001 From: cpovirk Date: Mon, 13 Jan 2025 06:22:41 -0800 Subject: [PATCH] Make `common.hash` use `VarHandle` [instead of `Unsafe`](https://github.com/google/guava/issues/6806) when possible. RELNOTES=Changed various classes to stop using `sun.misc.Unsafe` under Java 9+. PiperOrigin-RevId: 714943678 --- .../common/hash/LittleEndianByteArray.java | 51 +++++--- .../common/hash/LittleEndianByteArray.java | 113 +++++++++++++++--- 2 files changed, 132 insertions(+), 32 deletions(-) diff --git a/android/guava/src/com/google/common/hash/LittleEndianByteArray.java b/android/guava/src/com/google/common/hash/LittleEndianByteArray.java index d42d5bd48b7f..cba12de50bbe 100644 --- a/android/guava/src/com/google/common/hash/LittleEndianByteArray.java +++ b/android/guava/src/com/google/common/hash/LittleEndianByteArray.java @@ -16,6 +16,7 @@ import static java.lang.Math.min; +import com.google.common.annotations.VisibleForTesting; import com.google.common.primitives.Longs; import java.lang.reflect.Field; import java.nio.ByteOrder; @@ -32,8 +33,11 @@ */ final class LittleEndianByteArray { - /** The instance that actually does the work; delegates to Unsafe or a pure-Java fallback. */ - private static final LittleEndianBytes byteArray; + /** + * The instance that actually does the work; delegates to VarHandle, Unsafe, or a Java-8 + * compatible pure-Java fallback. + */ + private static final LittleEndianBytes byteArray = makeGetter(); /** * Load 8 bytes into long in a little endian manner, from the substring between position and @@ -104,12 +108,12 @@ static int load32(byte[] source, int offset) { } /** - * Indicates that the loading of Unsafe was successful and the load and store operations will be - * very efficient. May be useful for calling code to fall back on an alternative implementation - * that is slower than Unsafe.get/store but faster than the pure-Java mask-and-shift. + * Indicates that the load and store operations will be very efficient because of use of VarHandle + * or Unsafe. May be useful for calling code to fall back on an alternative implementation that is + * slower than those implementations but faster than the pure-Java mask-and-shift. */ - static boolean usingUnsafe() { - return (byteArray instanceof UnsafeByteArray); + static boolean usingFastPath() { + return byteArray.usesFastPath(); } /** @@ -122,6 +126,8 @@ private interface LittleEndianBytes { long getLongLittleEndian(byte[] array, int offset); void putLongLittleEndian(byte[] array, int offset, long value); + + boolean usesFastPath(); } /** @@ -130,7 +136,8 @@ private interface LittleEndianBytes { * class's static initializer can fall back on a non-Unsafe version. */ @SuppressWarnings({"SunApi", "removal"}) // b/345822163 - private enum UnsafeByteArray implements LittleEndianBytes { + @VisibleForTesting + enum UnsafeByteArray implements LittleEndianBytes { // Do *not* change the order of these constants! UNSAFE_LITTLE_ENDIAN { @Override @@ -159,6 +166,11 @@ public void putLongLittleEndian(byte[] array, int offset, long value) { } }; + @Override + public boolean usesFastPath() { + return true; + } + // Provides load and store operations that use native instructions to get better performance. private static final Unsafe theUnsafe; @@ -207,7 +219,10 @@ private static Unsafe getUnsafe() { } } - /** Fallback implementation for when Unsafe is not available in our current environment. */ + /** + * Fallback implementation for when VarHandle and Unsafe are not available in our current + * environment. + */ private enum JavaLittleEndianBytes implements LittleEndianBytes { INSTANCE { @Override @@ -230,11 +245,15 @@ public void putLongLittleEndian(byte[] sink, int offset, long value) { sink[offset + i] = (byte) ((value & mask) >> (i * 8)); } } + + @Override + public boolean usesFastPath() { + return false; + } } } - static { - LittleEndianBytes theGetter = JavaLittleEndianBytes.INSTANCE; + static LittleEndianBytes makeGetter() { try { /* * UnsafeByteArray uses Unsafe.getLong() in an unsupported way, which is known to cause @@ -249,15 +268,15 @@ public void putLongLittleEndian(byte[] sink, int offset, long value) { */ String arch = System.getProperty("os.arch"); if ("amd64".equals(arch)) { - theGetter = - ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN) - ? UnsafeByteArray.UNSAFE_LITTLE_ENDIAN - : UnsafeByteArray.UNSAFE_BIG_ENDIAN; + return ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN) + ? UnsafeByteArray.UNSAFE_LITTLE_ENDIAN + : UnsafeByteArray.UNSAFE_BIG_ENDIAN; } } catch (Throwable t) { // ensure we really catch *everything* } - byteArray = theGetter; + + return JavaLittleEndianBytes.INSTANCE; } /** Deter instantiation of this class. */ diff --git a/guava/src/com/google/common/hash/LittleEndianByteArray.java b/guava/src/com/google/common/hash/LittleEndianByteArray.java index 2d197df46a9b..86ef66f65104 100644 --- a/guava/src/com/google/common/hash/LittleEndianByteArray.java +++ b/guava/src/com/google/common/hash/LittleEndianByteArray.java @@ -15,13 +15,19 @@ package com.google.common.hash; import static java.lang.Math.min; +import static java.lang.invoke.MethodHandles.byteArrayViewVarHandle; +import static java.nio.ByteOrder.LITTLE_ENDIAN; +import com.google.common.annotations.VisibleForTesting; import com.google.common.primitives.Longs; +import com.google.j2objc.annotations.J2ObjCIncompatible; +import java.lang.invoke.VarHandle; import java.lang.reflect.Field; import java.nio.ByteOrder; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +import org.jspecify.annotations.Nullable; import sun.misc.Unsafe; /** @@ -32,8 +38,11 @@ */ final class LittleEndianByteArray { - /** The instance that actually does the work; delegates to Unsafe or a pure-Java fallback. */ - private static final LittleEndianBytes byteArray; + /** + * The instance that actually does the work; delegates to VarHandle, Unsafe, or a Java-8 + * compatible pure-Java fallback. + */ + private static final LittleEndianBytes byteArray = makeGetter(); /** * Load 8 bytes into long in a little endian manner, from the substring between position and @@ -104,12 +113,12 @@ static int load32(byte[] source, int offset) { } /** - * Indicates that the loading of Unsafe was successful and the load and store operations will be - * very efficient. May be useful for calling code to fall back on an alternative implementation - * that is slower than Unsafe.get/store but faster than the pure-Java mask-and-shift. + * Indicates that the load and store operations will be very efficient because of use of VarHandle + * or Unsafe. May be useful for calling code to fall back on an alternative implementation that is + * slower than those implementations but faster than the pure-Java mask-and-shift. */ - static boolean usingUnsafe() { - return (byteArray instanceof UnsafeByteArray); + static boolean usingFastPath() { + return byteArray.usesFastPath(); } /** @@ -122,6 +131,34 @@ private interface LittleEndianBytes { long getLongLittleEndian(byte[] array, int offset); void putLongLittleEndian(byte[] array, int offset, long value); + + boolean usesFastPath(); + } + + /** VarHandle-based implementation. */ + @J2ObjCIncompatible + // We use this class only after confirming that VarHandle is available at runtime. + @SuppressWarnings("Java8ApiChecker") + @IgnoreJRERequirement + private enum VarHandleLittleEndianBytes implements LittleEndianBytes { + INSTANCE { + @Override + public long getLongLittleEndian(byte[] array, int offset) { + return (long) handle.get(array, offset); + } + + @Override + public void putLongLittleEndian(byte[] array, int offset, long value) { + handle.set(array, offset, value); + } + }; + + @Override + public boolean usesFastPath() { + return true; + } + + private static final VarHandle handle = byteArrayViewVarHandle(long[].class, LITTLE_ENDIAN); } /** @@ -130,7 +167,8 @@ private interface LittleEndianBytes { * class's static initializer can fall back on a non-Unsafe version. */ @SuppressWarnings({"SunApi", "removal"}) // b/345822163 - private enum UnsafeByteArray implements LittleEndianBytes { + @VisibleForTesting + enum UnsafeByteArray implements LittleEndianBytes { // Do *not* change the order of these constants! UNSAFE_LITTLE_ENDIAN { @Override @@ -159,6 +197,11 @@ public void putLongLittleEndian(byte[] array, int offset, long value) { } }; + @Override + public boolean usesFastPath() { + return true; + } + // Provides load and store operations that use native instructions to get better performance. private static final Unsafe theUnsafe; @@ -207,7 +250,10 @@ private static Unsafe getUnsafe() { } } - /** Fallback implementation for when Unsafe is not available in our current environment. */ + /** + * Fallback implementation for when VarHandle and Unsafe are not available in our current + * environment. + */ private enum JavaLittleEndianBytes implements LittleEndianBytes { INSTANCE { @Override @@ -230,11 +276,21 @@ public void putLongLittleEndian(byte[] sink, int offset, long value) { sink[offset + i] = (byte) ((value & mask) >> (i * 8)); } } + + @Override + public boolean usesFastPath() { + return false; + } } } - static { - LittleEndianBytes theGetter = JavaLittleEndianBytes.INSTANCE; + static LittleEndianBytes makeGetter() { + LittleEndianBytes usingVarHandle = + VarHandleLittleEndianBytesMaker.INSTANCE.tryMakeVarHandleLittleEndianBytes(); + if (usingVarHandle != null) { + return usingVarHandle; + } + try { /* * UnsafeByteArray uses Unsafe.getLong() in an unsupported way, which is known to cause @@ -249,15 +305,40 @@ public void putLongLittleEndian(byte[] sink, int offset, long value) { */ String arch = System.getProperty("os.arch"); if ("amd64".equals(arch) || "aarch64".equals(arch)) { - theGetter = - ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN) - ? UnsafeByteArray.UNSAFE_LITTLE_ENDIAN - : UnsafeByteArray.UNSAFE_BIG_ENDIAN; + return ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN) + ? UnsafeByteArray.UNSAFE_LITTLE_ENDIAN + : UnsafeByteArray.UNSAFE_BIG_ENDIAN; } } catch (Throwable t) { // ensure we really catch *everything* } - byteArray = theGetter; + + return JavaLittleEndianBytes.INSTANCE; + } + + // Compare AbstractFuture.VarHandleAtomicHelperMaker. + private enum VarHandleLittleEndianBytesMaker { + INSTANCE { + /** + * Implementation used by non-J2ObjC environments (aside, of course, from those that have + * supersource for the entirety of {@link AbstractFuture}). + */ + @Override + @J2ObjCIncompatible + @Nullable LittleEndianBytes tryMakeVarHandleLittleEndianBytes() { + try { + Class.forName("java.lang.invoke.VarHandle"); + } catch (ClassNotFoundException beforeJava9) { + return null; + } + return VarHandleLittleEndianBytes.INSTANCE; + } + }; + + /** Implementation used by J2ObjC environments, overridden for other environments. */ + @Nullable LittleEndianBytes tryMakeVarHandleLittleEndianBytes() { + return null; + } } /** Deter instantiation of this class. */