mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-26 14:24:46 +02:00
8231756: [JVMCI] need support for deoptimizing virtual byte arrays encoding non-byte primitives
Reviewed-by: kvn
This commit is contained in:
parent
430f8020fd
commit
12f8b52fb8
13 changed files with 332 additions and 72 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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) \
|
||||||
|
|
|
@ -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++) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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"));
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,4 +69,9 @@ public class VirtualObjectLayoutTest extends VirtualObjectTestBase {
|
||||||
public void testFormat() {
|
public void testFormat() {
|
||||||
testBase();
|
testBase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testByteArrayFormat() {
|
||||||
|
testByteArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue