Merge pull request #27302 from akka/wip-27301-copyUSAsciiStrToBytes-patriknw

Harden copyUSAciiBytesToStr, so that it works on Raspberry Pi, #27301
This commit is contained in:
Patrik Nordwall 2019-07-12 16:48:24 +02:00 committed by GitHub
commit 0f01df713f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 126 additions and 13 deletions

View file

@ -12,6 +12,20 @@ class AsciiStringCopySpec extends WordSpec with Matchers {
"The copyUSAsciiStrToBytes optimization" must {
"select working algorithm" in {
if (Unsafe.isIsJavaVersion9Plus) {
Unsafe.testUSAsciiStrToBytesAlgorithm0("abc") should ===(true)
// this is known to fail with JDK 11 on ARM32 (Raspberry Pi),
// and therefore algorithm 0 is selected on that architecture
Unsafe.testUSAsciiStrToBytesAlgorithm1("abc") should ===(true)
Unsafe.testUSAsciiStrToBytesAlgorithm2("abc") should ===(false)
} else {
Unsafe.testUSAsciiStrToBytesAlgorithm0("abc") should ===(true)
Unsafe.testUSAsciiStrToBytesAlgorithm1("abc") should ===(false)
Unsafe.testUSAsciiStrToBytesAlgorithm2("abc") should ===(true)
}
}
"copy string internal representation successfully" in {
val ascii = "abcdefghijklmnopqrstuvxyz"
val byteArray = new Array[Byte](ascii.length)

View file

@ -4,17 +4,22 @@
package akka.util;
import akka.annotation.InternalApi;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/**
* INTERNAL API
*/
@InternalApi
public final class Unsafe {
public static final sun.misc.Unsafe instance;
private static final long stringValueFieldOffset;
private static final boolean isJavaVersion9Plus;
private static final int copyUSAsciiStrToBytesAlgorithm;
static {
try {
@ -30,29 +35,110 @@ public final class Unsafe {
else instance = found;
stringValueFieldOffset = instance.objectFieldOffset(String.class.getDeclaredField("value"));
// See Oracle section 1.5.3 at:
// https://docs.oracle.com/javase/8/docs/technotes/guides/versioning/spec/versioning2.html
final int[] version = Arrays.
stream(System.getProperty("java.specification.version").split("\\.")).
mapToInt(Integer::parseInt).
toArray();
final int javaVersion = version[0] == 1 ? version[1] : version[0];
isJavaVersion9Plus = javaVersion > 8;
isJavaVersion9Plus = isIsJavaVersion9Plus();
// Select optimization algorithm for `copyUSAciiBytesToStr`.
// For example algorithm 1 will fail with JDK 11 on ARM32 (Raspberry Pi),
// and therefore algorithm 0 is selected on that architecture.
String testStr = "abc";
if (isJavaVersion9Plus && testUSAsciiStrToBytesAlgorithm1(testStr))
copyUSAsciiStrToBytesAlgorithm = 1;
else if (testUSAsciiStrToBytesAlgorithm2(testStr))
copyUSAsciiStrToBytesAlgorithm = 2;
else
copyUSAsciiStrToBytesAlgorithm = 0;
} catch (Throwable t) {
throw new ExceptionInInitializerError(t);
}
}
public static void copyUSAsciiStrToBytes(String str, byte[] bytes) {
if (isJavaVersion9Plus) {
static boolean isIsJavaVersion9Plus() {
// See Oracle section 1.5.3 at:
// https://docs.oracle.com/javase/8/docs/technotes/guides/versioning/spec/versioning2.html
final int[] version = Arrays.
stream(System.getProperty("java.specification.version").split("\\.")).
mapToInt(Integer::parseInt).
toArray();
final int javaVersion = version[0] == 1 ? version[1] : version[0];
return javaVersion > 8;
}
static boolean testUSAsciiStrToBytesAlgorithm0(String str) {
try {
byte[] bytes = new byte[str.length()];
// copy of implementation in copyUSAciiBytesToStr
byte[] strBytes = str.getBytes(StandardCharsets.US_ASCII);
System.arraycopy(strBytes, 0, bytes, 0, str.length());
// end copy
String result = copyUSAciiBytesToStr(str.length(), bytes);
return str.equals(result);
} catch (Throwable all) {
return false;
}
}
static boolean testUSAsciiStrToBytesAlgorithm1(String str) {
try {
byte[] bytes = new byte[str.length()];
// copy of implementation in copyUSAciiBytesToStr
final byte[] chars = (byte[]) instance.getObject(str, stringValueFieldOffset);
System.arraycopy(chars, 0, bytes, 0, str.length());
} else {
// end copy
String result = copyUSAciiBytesToStr(str.length(), bytes);
return str.equals(result);
} catch (Throwable all) {
return false;
}
}
static boolean testUSAsciiStrToBytesAlgorithm2(String str) {
try {
byte[] bytes = new byte[str.length()];
// copy of implementation in copyUSAciiBytesToStr
final char[] chars = (char[]) instance.getObject(str, stringValueFieldOffset);
int i = 0;
while (i < str.length()) {
bytes[i] = (byte) chars[i++];
}
// end copy
String result = copyUSAciiBytesToStr(str.length(), bytes);
return str.equals(result);
} catch (Throwable all) {
return false;
}
}
private static String copyUSAciiBytesToStr(int length, byte[] bytes) {
char[] resultChars = new char[length];
int i = 0;
while (i < length) {
// UsAscii
resultChars[i] = (char) bytes[i];
i += 1;
}
return String.valueOf(resultChars, 0, length);
}
public static void copyUSAsciiStrToBytes(String str, byte[] bytes) {
if (copyUSAsciiStrToBytesAlgorithm == 1) {
final byte[] chars = (byte[]) instance.getObject(str, stringValueFieldOffset);
System.arraycopy(chars, 0, bytes, 0, str.length());
} else if (copyUSAsciiStrToBytesAlgorithm == 2) {
final char[] chars = (char[]) instance.getObject(str, stringValueFieldOffset);
int i = 0;
while (i < str.length()) {
bytes[i] = (byte) chars[i++];
}
} else {
byte[] strBytes = str.getBytes(StandardCharsets.US_ASCII);
System.arraycopy(strBytes, 0, bytes, 0, str.length());
}
}
@ -61,7 +147,7 @@ public final class Unsafe {
long s1 = 601258;
int i = 0;
if (isJavaVersion9Plus) {
if (copyUSAsciiStrToBytesAlgorithm == 1) {
final byte[] chars = (byte[]) instance.getObject(str, stringValueFieldOffset);
while (i < str.length()) {
long x = s0 ^ (long)chars[i++]; // Mix character into PRNG state
@ -74,12 +160,25 @@ public final class Unsafe {
x ^= x >>> 17;
s1 = x ^ y;
}
} else {
} else if (copyUSAsciiStrToBytesAlgorithm == 2) {
final char[] chars = (char[]) instance.getObject(str, stringValueFieldOffset);
while (i < str.length()) {
long x = s0 ^ (long)chars[i++]; // Mix character into PRNG state
long y = s1;
// Xorshift128+ round
s0 = y;
x ^= x << 23;
y ^= y >>> 26;
x ^= x >>> 17;
s1 = x ^ y;
}
} else {
byte[] chars = str.getBytes(StandardCharsets.US_ASCII);
while (i < str.length()) {
long x = s0 ^ (long)chars[i++]; // Mix character into PRNG state
long y = s1;
// Xorshift128+ round
s0 = y;
x ^= x << 23;