Skip to content

Commit ee63055

Browse files
cpovirkGoogle Java Core Libraries
authored and
Google Java Core Libraries
committed
Make common.hash use VarHandle [instead of Unsafe](#6806) when possible.
(One other note: I had some more fun with [Java 8 support](#6614) in `LittleEndianByteArray`.) RELNOTES=Changed various classes to stop using `sun.misc.Unsafe` under Java 9+. PiperOrigin-RevId: 715182856
1 parent 80aab00 commit ee63055

File tree

2 files changed

+136
-32
lines changed

2 files changed

+136
-32
lines changed

android/guava/src/com/google/common/hash/LittleEndianByteArray.java

+35-16
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import static java.lang.Math.min;
1818

19+
import com.google.common.annotations.VisibleForTesting;
1920
import com.google.common.primitives.Longs;
2021
import java.lang.reflect.Field;
2122
import java.nio.ByteOrder;
@@ -32,8 +33,11 @@
3233
*/
3334
final class LittleEndianByteArray {
3435

35-
/** The instance that actually does the work; delegates to Unsafe or a pure-Java fallback. */
36-
private static final LittleEndianBytes byteArray;
36+
/**
37+
* The instance that actually does the work; delegates to VarHandle, Unsafe, or a Java-8
38+
* compatible pure-Java fallback.
39+
*/
40+
private static final LittleEndianBytes byteArray = makeGetter();
3741

3842
/**
3943
* 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) {
104108
}
105109

106110
/**
107-
* Indicates that the loading of Unsafe was successful and the load and store operations will be
108-
* very efficient. May be useful for calling code to fall back on an alternative implementation
109-
* that is slower than Unsafe.get/store but faster than the pure-Java mask-and-shift.
111+
* Indicates that the load and store operations will be very efficient because of use of VarHandle
112+
* or Unsafe. May be useful for calling code to fall back on an alternative implementation that is
113+
* slower than those implementations but faster than the pure-Java mask-and-shift.
110114
*/
111-
static boolean usingUnsafe() {
112-
return (byteArray instanceof UnsafeByteArray);
115+
static boolean usingFastPath() {
116+
return byteArray.usesFastPath();
113117
}
114118

115119
/**
@@ -122,6 +126,8 @@ private interface LittleEndianBytes {
122126
long getLongLittleEndian(byte[] array, int offset);
123127

124128
void putLongLittleEndian(byte[] array, int offset, long value);
129+
130+
boolean usesFastPath();
125131
}
126132

127133
/**
@@ -130,7 +136,8 @@ private interface LittleEndianBytes {
130136
* class's static initializer can fall back on a non-Unsafe version.
131137
*/
132138
@SuppressWarnings({"SunApi", "removal"}) // b/345822163
133-
private enum UnsafeByteArray implements LittleEndianBytes {
139+
@VisibleForTesting
140+
enum UnsafeByteArray implements LittleEndianBytes {
134141
// Do *not* change the order of these constants!
135142
UNSAFE_LITTLE_ENDIAN {
136143
@Override
@@ -159,6 +166,11 @@ public void putLongLittleEndian(byte[] array, int offset, long value) {
159166
}
160167
};
161168

169+
@Override
170+
public boolean usesFastPath() {
171+
return true;
172+
}
173+
162174
// Provides load and store operations that use native instructions to get better performance.
163175
private static final Unsafe theUnsafe;
164176

@@ -207,7 +219,10 @@ private static Unsafe getUnsafe() {
207219
}
208220
}
209221

210-
/** Fallback implementation for when Unsafe is not available in our current environment. */
222+
/**
223+
* Fallback implementation for when VarHandle and Unsafe are not available in our current
224+
* environment.
225+
*/
211226
private enum JavaLittleEndianBytes implements LittleEndianBytes {
212227
INSTANCE {
213228
@Override
@@ -230,11 +245,15 @@ public void putLongLittleEndian(byte[] sink, int offset, long value) {
230245
sink[offset + i] = (byte) ((value & mask) >> (i * 8));
231246
}
232247
}
248+
249+
@Override
250+
public boolean usesFastPath() {
251+
return false;
252+
}
233253
}
234254
}
235255

236-
static {
237-
LittleEndianBytes theGetter = JavaLittleEndianBytes.INSTANCE;
256+
static LittleEndianBytes makeGetter() {
238257
try {
239258
/*
240259
* 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) {
249268
*/
250269
String arch = System.getProperty("os.arch");
251270
if ("amd64".equals(arch)) {
252-
theGetter =
253-
ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)
254-
? UnsafeByteArray.UNSAFE_LITTLE_ENDIAN
255-
: UnsafeByteArray.UNSAFE_BIG_ENDIAN;
271+
return ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)
272+
? UnsafeByteArray.UNSAFE_LITTLE_ENDIAN
273+
: UnsafeByteArray.UNSAFE_BIG_ENDIAN;
256274
}
257275
} catch (Throwable t) {
258276
// ensure we really catch *everything*
259277
}
260-
byteArray = theGetter;
278+
279+
return JavaLittleEndianBytes.INSTANCE;
261280
}
262281

263282
/** Deter instantiation of this class. */

guava/src/com/google/common/hash/LittleEndianByteArray.java

+101-16
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,19 @@
1515
package com.google.common.hash;
1616

1717
import static java.lang.Math.min;
18+
import static java.lang.invoke.MethodHandles.byteArrayViewVarHandle;
19+
import static java.nio.ByteOrder.LITTLE_ENDIAN;
1820

21+
import com.google.common.annotations.VisibleForTesting;
1922
import com.google.common.primitives.Longs;
23+
import com.google.j2objc.annotations.J2ObjCIncompatible;
24+
import java.lang.invoke.VarHandle;
2025
import java.lang.reflect.Field;
2126
import java.nio.ByteOrder;
2227
import java.security.AccessController;
2328
import java.security.PrivilegedActionException;
2429
import java.security.PrivilegedExceptionAction;
30+
import org.jspecify.annotations.Nullable;
2531
import sun.misc.Unsafe;
2632

2733
/**
@@ -32,8 +38,11 @@
3238
*/
3339
final class LittleEndianByteArray {
3440

35-
/** The instance that actually does the work; delegates to Unsafe or a pure-Java fallback. */
36-
private static final LittleEndianBytes byteArray;
41+
/**
42+
* The instance that actually does the work; delegates to VarHandle, Unsafe, or a Java-8
43+
* compatible pure-Java fallback.
44+
*/
45+
private static final LittleEndianBytes byteArray = makeGetter();
3746

3847
/**
3948
* 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) {
104113
}
105114

106115
/**
107-
* Indicates that the loading of Unsafe was successful and the load and store operations will be
108-
* very efficient. May be useful for calling code to fall back on an alternative implementation
109-
* that is slower than Unsafe.get/store but faster than the pure-Java mask-and-shift.
116+
* Indicates that the load and store operations will be very efficient because of use of VarHandle
117+
* or Unsafe. May be useful for calling code to fall back on an alternative implementation that is
118+
* slower than those implementations but faster than the pure-Java mask-and-shift.
110119
*/
111-
static boolean usingUnsafe() {
112-
return (byteArray instanceof UnsafeByteArray);
120+
static boolean usingFastPath() {
121+
return byteArray.usesFastPath();
113122
}
114123

115124
/**
@@ -122,6 +131,38 @@ private interface LittleEndianBytes {
122131
long getLongLittleEndian(byte[] array, int offset);
123132

124133
void putLongLittleEndian(byte[] array, int offset, long value);
134+
135+
boolean usesFastPath();
136+
}
137+
138+
/** VarHandle-based implementation. */
139+
@J2ObjCIncompatible
140+
// We use this class only after confirming that VarHandle is available at runtime.
141+
@SuppressWarnings("Java8ApiChecker")
142+
@IgnoreJRERequirement
143+
private enum VarHandleLittleEndianBytes implements LittleEndianBytes {
144+
INSTANCE {
145+
@Override
146+
public long getLongLittleEndian(byte[] array, int offset) {
147+
return (long) HANDLE.get(array, offset);
148+
}
149+
150+
@Override
151+
public void putLongLittleEndian(byte[] array, int offset, long value) {
152+
HANDLE.set(array, offset, value);
153+
}
154+
};
155+
156+
@Override
157+
public boolean usesFastPath() {
158+
return true;
159+
}
160+
161+
/*
162+
* non-private so that our `-source 8` build doesn't need to generate a synthetic accessor
163+
* method, whose mention of VarHandle would upset WriteReplaceOverridesTest under Java 8.
164+
*/
165+
static final VarHandle HANDLE = byteArrayViewVarHandle(long[].class, LITTLE_ENDIAN);
125166
}
126167

127168
/**
@@ -130,7 +171,8 @@ private interface LittleEndianBytes {
130171
* class's static initializer can fall back on a non-Unsafe version.
131172
*/
132173
@SuppressWarnings({"SunApi", "removal"}) // b/345822163
133-
private enum UnsafeByteArray implements LittleEndianBytes {
174+
@VisibleForTesting
175+
enum UnsafeByteArray implements LittleEndianBytes {
134176
// Do *not* change the order of these constants!
135177
UNSAFE_LITTLE_ENDIAN {
136178
@Override
@@ -159,6 +201,11 @@ public void putLongLittleEndian(byte[] array, int offset, long value) {
159201
}
160202
};
161203

204+
@Override
205+
public boolean usesFastPath() {
206+
return true;
207+
}
208+
162209
// Provides load and store operations that use native instructions to get better performance.
163210
private static final Unsafe theUnsafe;
164211

@@ -207,7 +254,10 @@ private static Unsafe getUnsafe() {
207254
}
208255
}
209256

210-
/** Fallback implementation for when Unsafe is not available in our current environment. */
257+
/**
258+
* Fallback implementation for when VarHandle and Unsafe are not available in our current
259+
* environment.
260+
*/
211261
private enum JavaLittleEndianBytes implements LittleEndianBytes {
212262
INSTANCE {
213263
@Override
@@ -230,11 +280,21 @@ public void putLongLittleEndian(byte[] sink, int offset, long value) {
230280
sink[offset + i] = (byte) ((value & mask) >> (i * 8));
231281
}
232282
}
283+
284+
@Override
285+
public boolean usesFastPath() {
286+
return false;
287+
}
233288
}
234289
}
235290

236-
static {
237-
LittleEndianBytes theGetter = JavaLittleEndianBytes.INSTANCE;
291+
static LittleEndianBytes makeGetter() {
292+
LittleEndianBytes usingVarHandle =
293+
VarHandleLittleEndianBytesMaker.INSTANCE.tryMakeVarHandleLittleEndianBytes();
294+
if (usingVarHandle != null) {
295+
return usingVarHandle;
296+
}
297+
238298
try {
239299
/*
240300
* UnsafeByteArray uses Unsafe.getLong() in an unsupported way, which is known to cause
@@ -249,15 +309,40 @@ public void putLongLittleEndian(byte[] sink, int offset, long value) {
249309
*/
250310
String arch = System.getProperty("os.arch");
251311
if ("amd64".equals(arch) || "aarch64".equals(arch)) {
252-
theGetter =
253-
ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)
254-
? UnsafeByteArray.UNSAFE_LITTLE_ENDIAN
255-
: UnsafeByteArray.UNSAFE_BIG_ENDIAN;
312+
return ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)
313+
? UnsafeByteArray.UNSAFE_LITTLE_ENDIAN
314+
: UnsafeByteArray.UNSAFE_BIG_ENDIAN;
256315
}
257316
} catch (Throwable t) {
258317
// ensure we really catch *everything*
259318
}
260-
byteArray = theGetter;
319+
320+
return JavaLittleEndianBytes.INSTANCE;
321+
}
322+
323+
// Compare AbstractFuture.VarHandleAtomicHelperMaker.
324+
private enum VarHandleLittleEndianBytesMaker {
325+
INSTANCE {
326+
/**
327+
* Implementation used by non-J2ObjC environments (aside, of course, from those that have
328+
* supersource for the entirety of {@link AbstractFuture}).
329+
*/
330+
@Override
331+
@J2ObjCIncompatible
332+
@Nullable LittleEndianBytes tryMakeVarHandleLittleEndianBytes() {
333+
try {
334+
Class.forName("java.lang.invoke.VarHandle");
335+
} catch (ClassNotFoundException beforeJava9) {
336+
return null;
337+
}
338+
return VarHandleLittleEndianBytes.INSTANCE;
339+
}
340+
};
341+
342+
/** Implementation used by J2ObjC environments, overridden for other environments. */
343+
@Nullable LittleEndianBytes tryMakeVarHandleLittleEndianBytes() {
344+
return null;
345+
}
261346
}
262347

263348
/** Deter instantiation of this class. */

0 commit comments

Comments
 (0)