8231756: [JVMCI] need support for deoptimizing virtual byte arrays encoding non-byte primitives

Reviewed-by: kvn
This commit is contained in:
Tom Rodriguez 2020-04-17 09:31:37 -07:00
parent 430f8020fd
commit 12f8b52fb8
13 changed files with 332 additions and 72 deletions

View file

@ -97,7 +97,7 @@ ScopeValue* DebugInfoReadStream::get_cached_object() {
enum { LOCATION_CODE = 0, CONSTANT_INT_CODE = 1, CONSTANT_OOP_CODE = 2, enum { LOCATION_CODE = 0, CONSTANT_INT_CODE = 1, CONSTANT_OOP_CODE = 2,
CONSTANT_LONG_CODE = 3, CONSTANT_DOUBLE_CODE = 4, CONSTANT_LONG_CODE = 3, CONSTANT_DOUBLE_CODE = 4,
OBJECT_CODE = 5, OBJECT_ID_CODE = 6, OBJECT_CODE = 5, OBJECT_ID_CODE = 6,
AUTO_BOX_OBJECT_CODE = 7 }; AUTO_BOX_OBJECT_CODE = 7, MARKER_CODE = 8 };
ScopeValue* ScopeValue::read_from(DebugInfoReadStream* stream) { ScopeValue* ScopeValue::read_from(DebugInfoReadStream* stream) {
ScopeValue* result = NULL; ScopeValue* result = NULL;
@ -110,6 +110,7 @@ ScopeValue* ScopeValue::read_from(DebugInfoReadStream* stream) {
case OBJECT_CODE: result = stream->read_object_value(false /*is_auto_box*/); break; case OBJECT_CODE: result = stream->read_object_value(false /*is_auto_box*/); break;
case AUTO_BOX_OBJECT_CODE: result = stream->read_object_value(true /*is_auto_box*/); break; case AUTO_BOX_OBJECT_CODE: result = stream->read_object_value(true /*is_auto_box*/); break;
case OBJECT_ID_CODE: result = stream->get_cached_object(); break; case OBJECT_ID_CODE: result = stream->get_cached_object(); break;
case MARKER_CODE: result = new MarkerValue(); break;
default: ShouldNotReachHere(); default: ShouldNotReachHere();
} }
return result; return result;
@ -130,6 +131,16 @@ void LocationValue::print_on(outputStream* st) const {
location().print_on(st); location().print_on(st);
} }
// MarkerValue
void MarkerValue::write_on(DebugInfoWriteStream* stream) {
stream->write_int(MARKER_CODE);
}
void MarkerValue::print_on(outputStream* st) const {
st->print("marker");
}
// ObjectValue // ObjectValue
void ObjectValue::set_value(oop value) { void ObjectValue::set_value(oop value) {

View file

@ -50,6 +50,7 @@ class ScopeValue: public ResourceObj {
virtual bool is_location() const { return false; } virtual bool is_location() const { return false; }
virtual bool is_object() const { return false; } virtual bool is_object() const { return false; }
virtual bool is_auto_box() const { return false; } virtual bool is_auto_box() const { return false; }
virtual bool is_marker() const { return false; }
virtual bool is_constant_int() const { return false; } virtual bool is_constant_int() const { return false; }
virtual bool is_constant_double() const { return false; } virtual bool is_constant_double() const { return false; }
virtual bool is_constant_long() const { return false; } virtual bool is_constant_long() const { return false; }
@ -91,6 +92,19 @@ class LocationValue: public ScopeValue {
void print_on(outputStream* st) const; void print_on(outputStream* st) const;
}; };
// A placeholder value that has no concrete meaning other than helping constructing
// other values.
class MarkerValue: public ScopeValue {
public:
bool is_marker() const { return true; }
// Serialization of debugging information
void write_on(DebugInfoWriteStream* stream);
// Printing
void print_on(outputStream* st) const;
};
// An ObjectValue describes an object eliminated by escape analysis. // An ObjectValue describes an object eliminated by escape analysis.

View file

@ -44,6 +44,7 @@ ConstantIntValue* CodeInstaller::_int_0_scope_value = new (ResourceObj::C_
ConstantIntValue* CodeInstaller::_int_1_scope_value = new (ResourceObj::C_HEAP, mtJVMCI) ConstantIntValue(1); ConstantIntValue* CodeInstaller::_int_1_scope_value = new (ResourceObj::C_HEAP, mtJVMCI) ConstantIntValue(1);
ConstantIntValue* CodeInstaller::_int_2_scope_value = new (ResourceObj::C_HEAP, mtJVMCI) ConstantIntValue(2); ConstantIntValue* CodeInstaller::_int_2_scope_value = new (ResourceObj::C_HEAP, mtJVMCI) ConstantIntValue(2);
LocationValue* CodeInstaller::_illegal_value = new (ResourceObj::C_HEAP, mtJVMCI) LocationValue(Location()); LocationValue* CodeInstaller::_illegal_value = new (ResourceObj::C_HEAP, mtJVMCI) LocationValue(Location());
MarkerValue* CodeInstaller::_virtual_byte_array_marker = new (ResourceObj::C_HEAP, mtJVMCI) MarkerValue();
VMReg CodeInstaller::getVMRegFromLocation(JVMCIObject location, int total_frame_size, JVMCI_TRAPS) { VMReg CodeInstaller::getVMRegFromLocation(JVMCIObject location, int total_frame_size, JVMCI_TRAPS) {
if (location.is_null()) { if (location.is_null()) {
@ -420,6 +421,7 @@ void CodeInstaller::record_object_value(ObjectValue* sv, JVMCIObject value, Grow
int id = jvmci_env()->get_VirtualObject_id(value); int id = jvmci_env()->get_VirtualObject_id(value);
Klass* klass = JVMCIENV->asKlass(type); Klass* klass = JVMCIENV->asKlass(type);
bool isLongArray = klass == Universe::longArrayKlassObj(); bool isLongArray = klass == Universe::longArrayKlassObj();
bool isByteArray = klass == Universe::byteArrayKlassObj();
JVMCIObjectArray values = jvmci_env()->get_VirtualObject_values(value); JVMCIObjectArray values = jvmci_env()->get_VirtualObject_values(value);
JVMCIObjectArray slotKinds = jvmci_env()->get_VirtualObject_slotKinds(value); JVMCIObjectArray slotKinds = jvmci_env()->get_VirtualObject_slotKinds(value);
@ -427,7 +429,24 @@ void CodeInstaller::record_object_value(ObjectValue* sv, JVMCIObject value, Grow
ScopeValue* cur_second = NULL; ScopeValue* cur_second = NULL;
JVMCIObject object = JVMCIENV->get_object_at(values, i); JVMCIObject object = JVMCIENV->get_object_at(values, i);
BasicType type = jvmci_env()->kindToBasicType(JVMCIENV->get_object_at(slotKinds, i), JVMCI_CHECK); BasicType type = jvmci_env()->kindToBasicType(JVMCIENV->get_object_at(slotKinds, i), JVMCI_CHECK);
ScopeValue* value = get_scope_value(object, type, objects, cur_second, JVMCI_CHECK); ScopeValue* value;
if (JVMCIENV->equals(object, jvmci_env()->get_Value_ILLEGAL())) {
if (isByteArray && type == T_ILLEGAL) {
/*
* The difference between a virtualized large access and a deferred write is the kind stored in the slotKinds
* of the virtual object: in the virtualization case, the kind is illegal, in the deferred write case, the kind
* is access stack kind (an int).
*/
value = _virtual_byte_array_marker;
} else {
value = _illegal_value;
if (type == T_DOUBLE || type == T_LONG) {
cur_second = _illegal_value;
}
}
} else {
value = get_scope_value(object, type, objects, cur_second, JVMCI_CHECK);
}
if (isLongArray && cur_second == NULL) { if (isLongArray && cur_second == NULL) {
// we're trying to put ints into a long array... this isn't really valid, but it's used for some optimizations. // we're trying to put ints into a long array... this isn't really valid, but it's used for some optimizations.
@ -435,6 +454,12 @@ void CodeInstaller::record_object_value(ObjectValue* sv, JVMCIObject value, Grow
cur_second = _int_0_scope_value; cur_second = _int_0_scope_value;
} }
if (isByteArray && cur_second != NULL && (type == T_DOUBLE || type == T_LONG)) {
// we are trying to write a long in a byte Array. We will need to count the illegals to restore the type of
// the thing we put inside.
cur_second = NULL;
}
if (cur_second != NULL) { if (cur_second != NULL) {
sv->field_values()->append(cur_second); sv->field_values()->append(cur_second);
} }

View file

@ -204,6 +204,7 @@ private:
static ConstantIntValue* _int_1_scope_value; static ConstantIntValue* _int_1_scope_value;
static ConstantIntValue* _int_2_scope_value; static ConstantIntValue* _int_2_scope_value;
static LocationValue* _illegal_value; static LocationValue* _illegal_value;
static MarkerValue* _virtual_byte_array_marker;
jint pd_next_offset(NativeInstruction* inst, jint pc_offset, JVMCIObject method, JVMCI_TRAPS); jint pd_next_offset(NativeInstruction* inst, jint pc_offset, JVMCIObject method, JVMCI_TRAPS);
void pd_patch_OopConstant(int pc_offset, JVMCIObject constant, JVMCI_TRAPS); void pd_patch_OopConstant(int pc_offset, JVMCIObject constant, JVMCI_TRAPS);

View file

@ -541,6 +541,7 @@
declare_constant(Deoptimization::Reason_unresolved) \ declare_constant(Deoptimization::Reason_unresolved) \
declare_constant(Deoptimization::Reason_jsr_mismatch) \ declare_constant(Deoptimization::Reason_jsr_mismatch) \
declare_constant(Deoptimization::Reason_LIMIT) \ declare_constant(Deoptimization::Reason_LIMIT) \
declare_constant(Deoptimization::_support_large_access_byte_array_virtualization) \
\ \
declare_constant(FieldInfo::access_flags_offset) \ declare_constant(FieldInfo::access_flags_offset) \
declare_constant(FieldInfo::name_index_offset) \ declare_constant(FieldInfo::name_index_offset) \

View file

@ -1032,6 +1032,80 @@ bool Deoptimization::realloc_objects(JavaThread* thread, frame* fr, RegisterMap*
return failures; return failures;
} }
#if INCLUDE_JVMCI
/**
* For primitive types whose kind gets "erased" at runtime (shorts become stack ints),
* we need to somehow be able to recover the actual kind to be able to write the correct
* amount of bytes.
* For that purpose, this method assumes that, for an entry spanning n bytes at index i,
* the entries at index n + 1 to n + i are 'markers'.
* For example, if we were writing a short at index 4 of a byte array of size 8, the
* expected form of the array would be:
*
* {b0, b1, b2, b3, INT, marker, b6, b7}
*
* Thus, in order to get back the size of the entry, we simply need to count the number
* of marked entries
*
* @param virtualArray the virtualized byte array
* @param i index of the virtual entry we are recovering
* @return The number of bytes the entry spans
*/
static int count_number_of_bytes_for_entry(ObjectValue *virtualArray, int i) {
int index = i;
while (++index < virtualArray->field_size() &&
virtualArray->field_at(index)->is_marker()) {}
return index - i;
}
/**
* If there was a guarantee for byte array to always start aligned to a long, we could
* do a simple check on the parity of the index. Unfortunately, that is not always the
* case. Thus, we check alignment of the actual address we are writing to.
* In the unlikely case index 0 is 5-aligned for example, it would then be possible to
* write a long to index 3.
*/
static jbyte* check_alignment_get_addr(typeArrayOop obj, int index, int expected_alignment) {
jbyte* res = obj->byte_at_addr(index);
assert((((intptr_t) res) % expected_alignment) == 0, "Non-aligned write");
return res;
}
static void byte_array_put(typeArrayOop obj, intptr_t val, int index, int byte_count) {
switch (byte_count) {
case 1:
obj->byte_at_put(index, (jbyte) *((jint *) &val));
break;
case 2:
*((jshort *) check_alignment_get_addr(obj, index, 2)) = (jshort) *((jint *) &val);
break;
case 4:
*((jint *) check_alignment_get_addr(obj, index, 4)) = (jint) *((jint *) &val);
break;
case 8: {
#ifdef _LP64
jlong res = (jlong) *((jlong *) &val);
#else
#ifdef SPARC
// For SPARC we have to swap high and low words.
jlong v = (jlong) *((jlong *) &val);
jlong res = 0;
res |= ((v & (jlong) 0xffffffff) << 32);
res |= ((v >> 32) & (jlong) 0xffffffff);
#else
jlong res = (jlong) *((jlong *) &val);
#endif // SPARC
#endif
*((jlong *) check_alignment_get_addr(obj, index, 8)) = res;
break;
}
default:
ShouldNotReachHere();
}
}
#endif // INCLUDE_JVMCI
// restore elements of an eliminated type array // restore elements of an eliminated type array
void Deoptimization::reassign_type_array_elements(frame* fr, RegisterMap* reg_map, ObjectValue* sv, typeArrayOop obj, BasicType type) { void Deoptimization::reassign_type_array_elements(frame* fr, RegisterMap* reg_map, ObjectValue* sv, typeArrayOop obj, BasicType type) {
int index = 0; int index = 0;
@ -1109,17 +1183,30 @@ void Deoptimization::reassign_type_array_elements(frame* fr, RegisterMap* reg_ma
obj->char_at_put(index, (jchar)*((jint*)&val)); obj->char_at_put(index, (jchar)*((jint*)&val));
break; break;
case T_BYTE: case T_BYTE: {
assert(value->type() == T_INT, "Agreement."); assert(value->type() == T_INT, "Agreement.");
// The value we get is erased as a regular int. We will need to find its actual byte count 'by hand'.
val = value->get_int(); val = value->get_int();
#if INCLUDE_JVMCI
int byte_count = count_number_of_bytes_for_entry(sv, i);
byte_array_put(obj, val, index, byte_count);
// According to byte_count contract, the values from i + 1 to i + byte_count are illegal values. Skip.
i += byte_count - 1; // Balance the loop counter.
index += byte_count;
// index has been updated so continue at top of loop
continue;
#else
obj->byte_at_put(index, (jbyte)*((jint*)&val)); obj->byte_at_put(index, (jbyte)*((jint*)&val));
break; break;
#endif // INCLUDE_JVMCI
}
case T_BOOLEAN: case T_BOOLEAN: {
assert(value->type() == T_INT, "Agreement."); assert(value->type() == T_INT, "Agreement.");
val = value->get_int(); val = value->get_int();
obj->bool_at_put(index, (jboolean)*((jint*)&val)); obj->bool_at_put(index, (jboolean)*((jint*)&val));
break; break;
}
default: default:
ShouldNotReachHere(); ShouldNotReachHere();
@ -1128,7 +1215,6 @@ void Deoptimization::reassign_type_array_elements(frame* fr, RegisterMap* reg_ma
} }
} }
// restore fields of an eliminated object array // restore fields of an eliminated object array
void Deoptimization::reassign_object_array_elements(frame* fr, RegisterMap* reg_map, ObjectValue* sv, objArrayOop obj) { void Deoptimization::reassign_object_array_elements(frame* fr, RegisterMap* reg_map, ObjectValue* sv, objArrayOop obj) {
for (int i = 0; i < sv->field_size(); i++) { for (int i = 0; i < sv->field_size(); i++) {

View file

@ -137,6 +137,11 @@ class Deoptimization : AllStatic {
Unpack_LIMIT = 4 Unpack_LIMIT = 4
}; };
#if INCLUDE_JVMCI
// Can reconstruct virtualized unsafe large accesses to byte arrays.
static const int _support_large_access_byte_array_virtualization = 1;
#endif
// Make all nmethods that are marked_for_deoptimization not_entrant and deoptimize any live // Make all nmethods that are marked_for_deoptimization not_entrant and deoptimize any live
// activations using those nmethods. If an nmethod is passed as an argument then it is // activations using those nmethods. If an nmethod is passed as an argument then it is
// marked_for_deoptimization and made not_entrant. Otherwise a scan of the code cache is done to // marked_for_deoptimization and made not_entrant. Otherwise a scan of the code cache is done to

View file

@ -184,8 +184,10 @@ StackValue* StackValue::create_stack_value(const frame* fr, const RegisterMap* r
} else if (sv->is_object()) { // Scalar replaced object in compiled frame } else if (sv->is_object()) { // Scalar replaced object in compiled frame
Handle ov = ((ObjectValue *)sv)->value(); Handle ov = ((ObjectValue *)sv)->value();
return new StackValue(ov, (ov.is_null()) ? 1 : 0); return new StackValue(ov, (ov.is_null()) ? 1 : 0);
} else if (sv->is_marker()) {
// Should never need to directly construct a marker.
ShouldNotReachHere();
} }
// Unknown ScopeValue type // Unknown ScopeValue type
ShouldNotReachHere(); ShouldNotReachHere();
return new StackValue((intptr_t) 0); // dummy return new StackValue((intptr_t) 0); // dummy

View file

@ -190,6 +190,29 @@ public final class VirtualObject implements JavaValue {
if (fieldIndex < fields.length) { if (fieldIndex < fields.length) {
throw new JVMCIError("Not enough values provided for fields in %s", this); throw new JVMCIError("Not enough values provided for fields in %s", this);
} }
} else if (type.getComponentType().getJavaKind() == JavaKind.Byte) {
for (int i = 0; i < values.length;) {
JavaKind slotkind = slotKinds[i];
if (slotkind != JavaKind.Byte) {
if (!slotkind.isPrimitive()) {
throw new JVMCIError("Storing a non-primitive in a byte array: %s %s", slotkind, toString());
}
int byteCount = 1;
while (++i < values.length && slotKinds[i] == JavaKind.Illegal) {
byteCount++;
}
/*
* Checks: a) The byte count is a valid count (ie: power of two), b) if the kind
* was not erased to int (happens for regular byte array accesses), check that
* the count is correct, c) No writes spanning more than a long.
*/
if (!CodeUtil.isPowerOf2(byteCount) || (slotkind.getStackKind() != JavaKind.Int && byteCount != slotkind.getByteCount()) || byteCount > JavaKind.Long.getByteCount()) {
throw new JVMCIError("Invalid number of illegals to reconstruct a byte array: %s in %s", byteCount, toString());
}
continue;
}
i++;
}
} }
} }

View file

@ -24,7 +24,6 @@
package org.graalvm.compiler.core.test; package org.graalvm.compiler.core.test;
import java.lang.reflect.Field;
import java.util.Arrays; import java.util.Arrays;
import org.graalvm.compiler.api.directives.GraalDirectives; import org.graalvm.compiler.api.directives.GraalDirectives;
@ -34,6 +33,8 @@ import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
import org.graalvm.compiler.nodes.extended.RawLoadNode; import org.graalvm.compiler.nodes.extended.RawLoadNode;
import org.graalvm.compiler.nodes.extended.RawStoreNode; import org.graalvm.compiler.nodes.extended.RawStoreNode;
import org.graalvm.compiler.nodes.spi.CoreProviders; import org.graalvm.compiler.nodes.spi.CoreProviders;
import org.graalvm.compiler.nodes.virtual.VirtualArrayNode;
import org.graalvm.compiler.nodes.virtual.VirtualObjectNode;
import org.graalvm.compiler.options.OptionValues; import org.graalvm.compiler.options.OptionValues;
import org.graalvm.compiler.phases.common.CanonicalizerPhase; import org.graalvm.compiler.phases.common.CanonicalizerPhase;
import org.graalvm.compiler.virtual.phases.ea.PartialEscapePhase; import org.graalvm.compiler.virtual.phases.ea.PartialEscapePhase;
@ -48,36 +49,6 @@ public class UnsafeVirtualizationTest extends GraalCompilerTest {
private static boolean[] FT = new boolean[]{false, true}; private static boolean[] FT = new boolean[]{false, true};
public static class Base {
/*
* This padding ensure that the size of the Base class ends up as a multiple of 8, which
* makes the first field of the subclass 8-byte aligned.
*/
double padding;
}
public static class A extends Base {
int f1;
int f2;
}
private static final long AF1Offset;
private static final long AF2Offset;
static {
long o1 = -1;
long o2 = -1;
try {
Field f1 = A.class.getDeclaredField("f1");
Field f2 = A.class.getDeclaredField("f2");
o1 = UNSAFE.objectFieldOffset(f1);
o2 = UNSAFE.objectFieldOffset(f2);
} catch (NoSuchFieldException | SecurityException e) {
throw new AssertionError(e);
}
AF1Offset = o1;
AF2Offset = o2;
}
// Side effect to create a deopt point, after possible virtualization. // Side effect to create a deopt point, after possible virtualization.
static int sideEffectField; static int sideEffectField;
@ -86,68 +57,68 @@ public class UnsafeVirtualizationTest extends GraalCompilerTest {
} }
public static int unsafeSnippet1(double i1, boolean c) { public static int unsafeSnippet1(double i1, boolean c) {
A a = new A(); TestClassInt a = new TestClassInt();
UNSAFE.putDouble(a, AF1Offset, i1); UNSAFE.putDouble(a, TestClassInt.fieldOffset1, i1);
sideEffect(); sideEffect();
if (c) { if (c) {
GraalDirectives.deoptimize(); GraalDirectives.deoptimize();
} }
return UNSAFE.getInt(a, AF1Offset) + UNSAFE.getInt(a, AF2Offset); return UNSAFE.getInt(a, TestClassInt.fieldOffset1) + UNSAFE.getInt(a, TestClassInt.fieldOffset2);
} }
public static long unsafeSnippet2a(int i1, boolean c) { public static long unsafeSnippet2a(int i1, boolean c) {
A a = new A(); TestClassInt a = new TestClassInt();
UNSAFE.putDouble(a, AF1Offset, i1); UNSAFE.putDouble(a, TestClassInt.fieldOffset1, i1);
a.f1 = i1; a.setFirstField(i1);
sideEffect(); sideEffect();
if (c) { if (c) {
GraalDirectives.deoptimize(); GraalDirectives.deoptimize();
} }
return UNSAFE.getLong(a, AF1Offset); return UNSAFE.getLong(a, TestClassInt.fieldOffset1);
} }
public static long unsafeSnippet2b(int i1, boolean c) { public static long unsafeSnippet2b(int i1, boolean c) {
A a = new A(); TestClassInt a = new TestClassInt();
UNSAFE.putDouble(a, AF1Offset, i1); UNSAFE.putDouble(a, TestClassInt.fieldOffset1, i1);
a.f2 = i1; a.setSecondField(i1);
sideEffect(); sideEffect();
if (c) { if (c) {
GraalDirectives.deoptimize(); GraalDirectives.deoptimize();
} }
return UNSAFE.getLong(a, AF1Offset); return UNSAFE.getLong(a, TestClassInt.fieldOffset1);
} }
public static long unsafeSnippet3a(int i1, boolean c) { public static long unsafeSnippet3a(int i1, boolean c) {
A a = new A(); TestClassInt a = new TestClassInt();
UNSAFE.putDouble(a, AF1Offset, i1); UNSAFE.putDouble(a, TestClassInt.fieldOffset1, i1);
UNSAFE.putInt(a, AF1Offset, i1); UNSAFE.putInt(a, TestClassInt.fieldOffset1, i1);
sideEffect(); sideEffect();
if (c) { if (c) {
GraalDirectives.deoptimize(); GraalDirectives.deoptimize();
} }
return UNSAFE.getLong(a, AF1Offset); return UNSAFE.getLong(a, TestClassInt.fieldOffset1);
} }
public static long unsafeSnippet3b(int i1, boolean c) { public static long unsafeSnippet3b(int i1, boolean c) {
A a = new A(); TestClassInt a = new TestClassInt();
UNSAFE.putDouble(a, AF1Offset, i1); UNSAFE.putDouble(a, TestClassInt.fieldOffset1, i1);
UNSAFE.putInt(a, AF2Offset, i1); UNSAFE.putInt(a, TestClassInt.fieldOffset2, i1);
sideEffect(); sideEffect();
if (c) { if (c) {
GraalDirectives.deoptimize(); GraalDirectives.deoptimize();
} }
return UNSAFE.getLong(a, AF1Offset); return UNSAFE.getLong(a, TestClassInt.fieldOffset1);
} }
public static int unsafeSnippet4(double i1, boolean c) { public static int unsafeSnippet4(double i1, boolean c) {
A a = new A(); TestClassInt a = new TestClassInt();
UNSAFE.putDouble(a, AF1Offset, i1); UNSAFE.putDouble(a, TestClassInt.fieldOffset1, i1);
UNSAFE.putDouble(a, AF1Offset, i1); UNSAFE.putDouble(a, TestClassInt.fieldOffset1, i1);
sideEffect(); sideEffect();
if (c) { if (c) {
GraalDirectives.deoptimize(); GraalDirectives.deoptimize();
} }
return UNSAFE.getInt(a, AF1Offset) + UNSAFE.getInt(a, AF2Offset); return UNSAFE.getInt(a, TestClassInt.fieldOffset1) + UNSAFE.getInt(a, TestClassInt.fieldOffset2);
} }
public static int unsafeSnippet5(long i1, boolean c) { public static int unsafeSnippet5(long i1, boolean c) {
@ -514,17 +485,22 @@ public class UnsafeVirtualizationTest extends GraalCompilerTest {
canonicalizer.apply(graph, context); canonicalizer.apply(graph, context);
} }
Result r = executeExpected(method, null, args); Result r = executeExpected(method, null, args);
int readCount = 0; int readCount = graph.getNodes().filter(RawLoadNode.class).count();
int writeCount = 0; int writeCount = graph.getNodes().filter(RawStoreNode.class).count();
boolean escapeReads = shouldEscapeRead && context.getPlatformConfigurationProvider().canVirtualizeLargeByteArrayAccess();
boolean escapeWrites = shouldEscapeWrite && context.getPlatformConfigurationProvider().canVirtualizeLargeByteArrayAccess();
if (escapeReads) {
readCount = graph.getNodes().filter(RawLoadNode.class).count();
}
if (escapeWrites) {
writeCount = graph.getNodes().filter(RawStoreNode.class).count();
}
new PartialEscapePhase(true, true, canonicalizer, null, options).apply(graph, context); new PartialEscapePhase(true, true, canonicalizer, null, options).apply(graph, context);
boolean canVirtualize = true;
assert graph.getNodes().filter(VirtualObjectNode.class).count() == 1;
VirtualObjectNode virtual = graph.getNodes().filter(VirtualObjectNode.class).first();
if (virtual instanceof VirtualArrayNode) {
VirtualArrayNode array = (VirtualArrayNode) virtual;
if (array.isVirtualByteArray()) {
canVirtualize = context.getPlatformConfigurationProvider().canVirtualizeLargeByteArrayAccess();
}
}
boolean escapeReads = shouldEscapeRead && canVirtualize;
boolean escapeWrites = shouldEscapeWrite && canVirtualize;
if (escapeReads) { if (escapeReads) {
int newCount = graph.getNodes().filter(RawLoadNode.class).count(); int newCount = graph.getNodes().filter(RawLoadNode.class).count();
assertTrue(readCount > newCount, "PEA did not escape reads. before: " + readCount + ", after " + newCount); assertTrue(readCount > newCount, "PEA did not escape reads. before: " + readCount + ", after " + newCount);

View file

@ -96,7 +96,8 @@ public class EATestBase extends GraalCompilerTest {
try { try {
long localFieldOffset1 = UNSAFE.objectFieldOffset(EATestBase.TestClassInt.class.getField("x")); long localFieldOffset1 = UNSAFE.objectFieldOffset(EATestBase.TestClassInt.class.getField("x"));
// Make the fields 8 byte aligned (Required for testing setLong on Architectures // Make the fields 8 byte aligned (Required for testing setLong on Architectures
// which does not support unaligned memory access // which does not support unaligned memory access. The code has to be extra careful
// because some JDKs do a better job of packing fields.
if (localFieldOffset1 % 8 == 0) { if (localFieldOffset1 % 8 == 0) {
fieldOffset1 = localFieldOffset1; fieldOffset1 = localFieldOffset1;
fieldOffset2 = UNSAFE.objectFieldOffset(EATestBase.TestClassInt.class.getField("y")); fieldOffset2 = UNSAFE.objectFieldOffset(EATestBase.TestClassInt.class.getField("y"));

View file

@ -34,6 +34,8 @@ import jdk.vm.ci.runtime.JVMCI;
public abstract class VirtualObjectTestBase { public abstract class VirtualObjectTestBase {
private static final int DEFAULT_BYTE_ARRAY_LENGTH = 50;
public static class SimpleObject { public static class SimpleObject {
int i1; int i1;
int i2; int i2;
@ -50,7 +52,7 @@ public abstract class VirtualObjectTestBase {
dummyValue = dummyValue | dummyValue << 32; dummyValue = dummyValue | dummyValue << 32;
if (kind.isNumericInteger()) { if (kind.isNumericInteger()) {
return JavaConstant.forIntegerKind(kind, dummyValue); return JavaConstant.forIntegerKind(kind, dummyValue);
} else if (kind == JavaKind.Float) { } else if (kind == JavaKind.Double) {
return JavaConstant.forDouble(Double.longBitsToDouble(dummyValue)); return JavaConstant.forDouble(Double.longBitsToDouble(dummyValue));
} else if (kind == JavaKind.Float) { } else if (kind == JavaKind.Float) {
return JavaConstant.forFloat(Float.intBitsToFloat((int) dummyValue)); return JavaConstant.forFloat(Float.intBitsToFloat((int) dummyValue));
@ -117,4 +119,112 @@ public abstract class VirtualObjectTestBase {
kinds[kinds.length - 1] = JavaKind.Int; kinds[kinds.length - 1] = JavaKind.Int;
test(simple, getJavaValues(kinds), kinds, true); test(simple, getJavaValues(kinds), kinds, true);
} }
public void testByteArray() {
MetaAccessProvider metaAccess = JVMCI.getRuntime().getHostJVMCIBackend().getMetaAccess();
ResolvedJavaType byteArrayType = metaAccess.lookupJavaType(byte[].class);
JavaKind[] arrayKinds = new JavaKind[DEFAULT_BYTE_ARRAY_LENGTH];
for (int i = 0; i < DEFAULT_BYTE_ARRAY_LENGTH; i++) {
arrayKinds[i] = JavaKind.Byte;
}
JavaKind[] kinds;
JavaValue[] values;
// Generate a straighforward byte array.
kinds = arrayKinds.clone();
values = getJavaValues(kinds);
test(byteArrayType, values, kinds, false);
// Write all primitive kinds into a byte array. Only primitives with enough Illegals should
// succeed.
for (JavaKind kind : JavaKind.values()) {
if (kind.isPrimitive() && kind != JavaKind.Void) {
kinds = arrayKinds.clone();
kinds[0] = kind;
for (int i = 1; i < kind.getByteCount() /* && i < DEFAULT_BYTE_ARRAY_LENGTH */; i++) {
kinds[i] = JavaKind.Illegal;
}
values = getJavaValues(kinds);
test(byteArrayType, values, kinds, false);
}
}
// Write all kinds into a byte array, but forget to set Illegals.
for (JavaKind kind : JavaKind.values()) {
if (kind.isPrimitive() && kind != JavaKind.Void) {
kinds = arrayKinds.clone();
kinds[0] = kind;
values = getJavaValues(kinds);
test(byteArrayType, values, kinds, kind.getStackKind() != JavaKind.Int);
}
}
// Write all kinds into a byte array, but set a wrong number of Illegals.
for (JavaKind kind : JavaKind.values()) {
if (kind.isPrimitive() && kind != JavaKind.Void) {
kinds = arrayKinds.clone();
kinds[0] = kind;
for (int i = 1; i < 5 /* && i < DEFAULT_BYTE_ARRAY_LENGTH */; i++) {
kinds[i] = JavaKind.Illegal;
}
values = getJavaValues(kinds);
test(byteArrayType, values, kinds, true);
}
}
// Write a reference in a byte array.
kinds = arrayKinds.clone();
kinds[0] = JavaKind.Object;
values = getJavaValues(kinds);
test(byteArrayType, values, kinds, true);
// Put too many Illegals.
kinds = arrayKinds.clone();
for (int i = 1; i < DEFAULT_BYTE_ARRAY_LENGTH; i++) {
kinds[i] = JavaKind.Illegal;
}
values = getJavaValues(kinds);
test(byteArrayType, values, kinds, true);
// Fill a byte array with Illegals.
kinds = arrayKinds.clone();
for (int i = 0; i < DEFAULT_BYTE_ARRAY_LENGTH; i++) {
kinds[i] = JavaKind.Illegal;
}
values = getJavaValues(kinds);
test(byteArrayType, values, kinds, true);
// Write all kinds in a byte array successively.
kinds = arrayKinds.clone();
int i = 0;
for (JavaKind kind : JavaKind.values()) {
if (kind.isPrimitive() && kind != JavaKind.Void) {
kinds[i] = kind;
for (int j = 1; j < kind.getByteCount(); j++) {
kinds[i + j] = JavaKind.Illegal;
}
i = i + kind.getByteCount();
}
}
values = getJavaValues(kinds);
test(byteArrayType, values, kinds, false);
// Write all kinds in a byte array successively, with an interleaving byte.
kinds = arrayKinds.clone();
i = 0;
for (JavaKind kind : JavaKind.values()) {
if (kind.isPrimitive() && kind != JavaKind.Void) {
kinds[i] = kind;
for (int j = 1; j < kind.getByteCount(); j++) {
kinds[i + j] = JavaKind.Illegal;
}
i = i + kind.getByteCount() + 1;
}
}
values = getJavaValues(kinds);
test(byteArrayType, values, kinds, false);
}
} }

View file

@ -69,4 +69,9 @@ public class VirtualObjectLayoutTest extends VirtualObjectTestBase {
public void testFormat() { public void testFormat() {
testBase(); testBase();
} }
@Test
public void testByteArrayFormat() {
testByteArray();
}
} }