• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 
15 package com.google.common.hash;
16 
17 import com.google.common.primitives.Longs;
18 import java.lang.reflect.Field;
19 import java.nio.ByteOrder;
20 import java.security.AccessController;
21 import java.security.PrivilegedActionException;
22 import java.security.PrivilegedExceptionAction;
23 import sun.misc.Unsafe;
24 
25 /**
26  * Utility functions for loading and storing values from a byte array.
27  *
28  * @author Kevin Damm
29  * @author Kyle Maddison
30  */
31 @ElementTypesAreNonnullByDefault
32 final class LittleEndianByteArray {
33 
34   /** The instance that actually does the work; delegates to Unsafe or a pure-Java fallback. */
35   private static final LittleEndianBytes byteArray;
36 
37   /**
38    * Load 8 bytes into long in a little endian manner, from the substring between position and
39    * position + 8. The array must have at least 8 bytes from offset (inclusive).
40    *
41    * @param input the input bytes
42    * @param offset the offset into the array at which to start
43    * @return a long of a concatenated 8 bytes
44    */
load64(byte[] input, int offset)45   static long load64(byte[] input, int offset) {
46     // We don't want this in production code as this is the most critical part of the loop.
47     assert input.length >= offset + 8;
48     // Delegates to the fast (unsafe) version or the fallback.
49     return byteArray.getLongLittleEndian(input, offset);
50   }
51 
52   /**
53    * Similar to load64, but allows offset + 8 > input.length, padding the result with zeroes. This
54    * has to explicitly reverse the order of the bytes as it packs them into the result which makes
55    * it slower than the native version.
56    *
57    * @param input the input bytes
58    * @param offset the offset into the array at which to start reading
59    * @param length the number of bytes from the input to read
60    * @return a long of a concatenated 8 bytes
61    */
load64Safely(byte[] input, int offset, int length)62   static long load64Safely(byte[] input, int offset, int length) {
63     long result = 0;
64     // Due to the way we shift, we can stop iterating once we've run out of data, the rest
65     // of the result already being filled with zeros.
66 
67     // This loop is critical to performance, so please check HashBenchmark if altering it.
68     int limit = Math.min(length, 8);
69     for (int i = 0; i < limit; i++) {
70       // Shift value left while iterating logically through the array.
71       result |= (input[offset + i] & 0xFFL) << (i * 8);
72     }
73     return result;
74   }
75 
76   /**
77    * Store 8 bytes into the provided array at the indicated offset, using the value provided.
78    *
79    * @param sink the output byte array
80    * @param offset the offset into the array at which to start writing
81    * @param value the value to write
82    */
store64(byte[] sink, int offset, long value)83   static void store64(byte[] sink, int offset, long value) {
84     // We don't want to assert in production code.
85     assert offset >= 0 && offset + 8 <= sink.length;
86     // Delegates to the fast (unsafe)version or the fallback.
87     byteArray.putLongLittleEndian(sink, offset, value);
88   }
89 
90   /**
91    * Load 4 bytes from the provided array at the indicated offset.
92    *
93    * @param source the input bytes
94    * @param offset the offset into the array at which to start
95    * @return the value found in the array in the form of a long
96    */
load32(byte[] source, int offset)97   static int load32(byte[] source, int offset) {
98     // TODO(user): Measure the benefit of delegating this to LittleEndianBytes also.
99     return (source[offset] & 0xFF)
100         | ((source[offset + 1] & 0xFF) << 8)
101         | ((source[offset + 2] & 0xFF) << 16)
102         | ((source[offset + 3] & 0xFF) << 24);
103   }
104 
105   /**
106    * Indicates that the loading of Unsafe was successful and the load and store operations will be
107    * very efficient. May be useful for calling code to fall back on an alternative implementation
108    * that is slower than Unsafe.get/store but faster than the pure-Java mask-and-shift.
109    */
usingUnsafe()110   static boolean usingUnsafe() {
111     return (byteArray instanceof UnsafeByteArray);
112   }
113 
114   /**
115    * Common interface for retrieving a 64-bit long from a little-endian byte array.
116    *
117    * <p>This abstraction allows us to use single-instruction load and put when available, or fall
118    * back on the slower approach of using Longs.fromBytes(byte...).
119    */
120   private interface LittleEndianBytes {
getLongLittleEndian(byte[] array, int offset)121     long getLongLittleEndian(byte[] array, int offset);
122 
putLongLittleEndian(byte[] array, int offset, long value)123     void putLongLittleEndian(byte[] array, int offset, long value);
124   }
125 
126   /**
127    * The only reference to Unsafe is in this nested class. We set things up so that if
128    * Unsafe.theUnsafe is inaccessible, the attempt to load the nested class fails, and the outer
129    * class's static initializer can fall back on a non-Unsafe version.
130    */
131   private enum UnsafeByteArray implements LittleEndianBytes {
132     // Do *not* change the order of these constants!
133     UNSAFE_LITTLE_ENDIAN {
134       @Override
getLongLittleEndian(byte[] array, int offset)135       public long getLongLittleEndian(byte[] array, int offset) {
136         return theUnsafe.getLong(array, (long) offset + BYTE_ARRAY_BASE_OFFSET);
137       }
138 
139       @Override
putLongLittleEndian(byte[] array, int offset, long value)140       public void putLongLittleEndian(byte[] array, int offset, long value) {
141         theUnsafe.putLong(array, (long) offset + BYTE_ARRAY_BASE_OFFSET, value);
142       }
143     },
144     UNSAFE_BIG_ENDIAN {
145       @Override
getLongLittleEndian(byte[] array, int offset)146       public long getLongLittleEndian(byte[] array, int offset) {
147         long bigEndian = theUnsafe.getLong(array, (long) offset + BYTE_ARRAY_BASE_OFFSET);
148         // The hardware is big-endian, so we need to reverse the order of the bytes.
149         return Long.reverseBytes(bigEndian);
150       }
151 
152       @Override
putLongLittleEndian(byte[] array, int offset, long value)153       public void putLongLittleEndian(byte[] array, int offset, long value) {
154         // Reverse the order of the bytes before storing, since we're on big-endian hardware.
155         long littleEndianValue = Long.reverseBytes(value);
156         theUnsafe.putLong(array, (long) offset + BYTE_ARRAY_BASE_OFFSET, littleEndianValue);
157       }
158     };
159 
160     // Provides load and store operations that use native instructions to get better performance.
161     private static final Unsafe theUnsafe;
162 
163     // The offset to the first element in a byte array.
164     private static final int BYTE_ARRAY_BASE_OFFSET;
165 
166     /**
167      * Returns an Unsafe. Suitable for use in a 3rd party package. Replace with a simple call to
168      * Unsafe.getUnsafe when integrating into a JDK.
169      *
170      * @return an Unsafe instance if successful
171      */
getUnsafe()172     private static Unsafe getUnsafe() {
173       try {
174         return Unsafe.getUnsafe();
175       } catch (SecurityException tryReflectionInstead) {
176         // We'll try reflection instead.
177       }
178       try {
179         return AccessController.doPrivileged(
180             (PrivilegedExceptionAction<Unsafe>)
181                 () -> {
182                   Class<Unsafe> k = Unsafe.class;
183                   for (Field f : k.getDeclaredFields()) {
184                     f.setAccessible(true);
185                     Object x = f.get(null);
186                     if (k.isInstance(x)) {
187                       return k.cast(x);
188                     }
189                   }
190                   throw new NoSuchFieldError("the Unsafe");
191                 });
192       } catch (PrivilegedActionException e) {
193         throw new RuntimeException("Could not initialize intrinsics", e.getCause());
194       }
195     }
196 
197     static {
198       theUnsafe = getUnsafe();
199       BYTE_ARRAY_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class);
200 
201       // sanity check - this should never fail
202       if (theUnsafe.arrayIndexScale(byte[].class) != 1) {
AssertionError()203         throw new AssertionError();
204       }
205     }
206   }
207 
208   /** Fallback implementation for when Unsafe is not available in our current environment. */
209   private enum JavaLittleEndianBytes implements LittleEndianBytes {
210     INSTANCE {
211       @Override
getLongLittleEndian(byte[] source, int offset)212       public long getLongLittleEndian(byte[] source, int offset) {
213         return Longs.fromBytes(
214             source[offset + 7],
215             source[offset + 6],
216             source[offset + 5],
217             source[offset + 4],
218             source[offset + 3],
219             source[offset + 2],
220             source[offset + 1],
221             source[offset]);
222       }
223 
224       @Override
putLongLittleEndian(byte[] sink, int offset, long value)225       public void putLongLittleEndian(byte[] sink, int offset, long value) {
226         long mask = 0xFFL;
227         for (int i = 0; i < 8; mask <<= 8, i++) {
228           sink[offset + i] = (byte) ((value & mask) >> (i * 8));
229         }
230       }
231     }
232   }
233 
234   static {
235     LittleEndianBytes theGetter = JavaLittleEndianBytes.INSTANCE;
236     try {
237       /*
238        * UnsafeByteArray uses Unsafe.getLong() in an unsupported way, which is known to cause
239        * crashes on Android when running in 32-bit mode. For maximum safety, we shouldn't use
240        * Unsafe.getLong() at all, but the performance benefit on x86_64 is too great to ignore, so
241        * as a compromise, we enable the optimization only on platforms that we specifically know to
242        * work.
243        *
244        * In the future, the use of Unsafe.getLong() should be replaced by ByteBuffer.getLong(),
245        * which will have an efficient native implementation in JDK 9.
246        *
247        */
248       String arch = System.getProperty("os.arch");
249       if ("amd64".equals(arch)) {
250         theGetter =
251             ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)
252                 ? UnsafeByteArray.UNSAFE_LITTLE_ENDIAN
253                 : UnsafeByteArray.UNSAFE_BIG_ENDIAN;
254       }
255     } catch (Throwable t) {
256       // ensure we really catch *everything*
257     }
258     byteArray = theGetter;
259   }
260 
261   /** Deter instantiation of this class. */
LittleEndianByteArray()262   private LittleEndianByteArray() {}
263 }
264