Skip to content
2 changes: 1 addition & 1 deletion src/hotspot/share/c1/c1_Runtime1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ JRT_ENTRY(int, Runtime1::substitutability_check(JavaThread* current, oopDesc* le
JavaValue result(T_BOOLEAN);
JavaCalls::call_static(&result,
vmClasses::ValueObjectMethods_klass(),
vmSymbols::isSubstitutable_name(),
UseAltSubstitutabilityMethod ? vmSymbols::isSubstitutableAlt_name() : vmSymbols::isSubstitutable_name(),
vmSymbols::object_object_boolean_signature(),
&args, CHECK_0);
return result.get_jboolean() ? 1 : 0;
Expand Down
53 changes: 52 additions & 1 deletion src/hotspot/share/classfile/classFileParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1383,6 +1383,7 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs,
assert(nullptr == _fields_type_annotations, "invariant");

bool is_inline_type = !class_access_flags.is_identity_class() && !class_access_flags.is_abstract();
bool is_value_class = !class_access_flags.is_identity_class() && !class_access_flags.is_interface();
cfs->guarantee_more(2, CHECK); // length
const u2 length = cfs->get_u2_fast();
*java_fields_count_ptr = length;
Expand All @@ -1394,7 +1395,7 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs,
// two more slots are required for inline classes:
// one for the static field with a reference to the pre-allocated default value
// one for the field the JVM injects when detecting an empty inline class
const int total_fields = length + num_injected + (is_inline_type ? 2 : 0);
const int total_fields = length + num_injected + (is_inline_type ? 2 : 0) + (is_value_class ? 1 : 0);

// Allocate a temporary resource array to collect field data.
// After parsing all fields, data are stored in a UNSIGNED5 compressed stream.
Expand Down Expand Up @@ -1575,6 +1576,22 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs,
_temp_field_info->adr_at(idx2)->set_index(idx2);
_static_oop_count++;
}
if (!access_flags().is_identity_class() && !access_flags().is_interface()
&& _class_name != vmSymbols::java_lang_Object()) {
// Acmp map required for abstract and concrete value classes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this comment. Explains why abstract classes are omitted from above.

FieldInfo::FieldFlags fflags2(0);
fflags2.update_injected(true);
fflags2.update_stable(true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If c2 can model the field map read as a stable array read, this allows ValueObjectMethods to generate efficient code for specialized value types in the future. 👍

AccessFlags aflags2(JVM_ACC_STATIC);
FieldInfo fi3(aflags2,
(u2)vmSymbols::as_int(VM_SYMBOL_ENUM_NAME(acmp_maps_name)),
(u2)vmSymbols::as_int(VM_SYMBOL_ENUM_NAME(int_array_signature)),
0,
fflags2);
int idx2 = _temp_field_info->append(fi3);
_temp_field_info->adr_at(idx2)->set_index(idx2);
_static_oop_count++;
}

if (_need_verify && length > 1) {
// Check duplicated fields
Expand Down Expand Up @@ -5520,6 +5537,40 @@ void ClassFileParser::fill_instance_klass(InstanceKlass* ik,
vk->initialize_calling_convention(CHECK);
}

if (EnableValhalla && !access_flags().is_identity_class() && !access_flags().is_interface()
&& _class_name != vmSymbols::java_lang_Object()) {
// Both abstract and concrete value classes need a field map for acmp
ik->set_acmp_maps_offset(_layout_info->_acmp_maps_offset);
// Current format of acmp maps:
// All maps are stored contiguously in a single int array because it might
// be to early to instantiate an Object array (to be investigated)
// Format is:
// [number_of_nonoop_entries][offset0][size[0][offset1][size1]...[oop_offset0][oop_offset1]...
// ^ ^
// | |
// --------------------- Pair of integer describing a segment of
// contiguous non-oop fields
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the ascii art - very helpful.

// First element is the number of segment of contiguous non-oop fields
// Then, each segment of contiguous non-oop fields is described by two consecutive elements:
// the offset then the size.
// After the last segment of contiguous non-oop fields, oop fields are described, one element
// per oop field, containing the offset of the field.
int nonoop_acmp_map_size = _layout_info->_nonoop_acmp_map->length() * 2;
int oop_acmp_map_size = _layout_info->_oop_acmp_map->length();
typeArrayOop map = oopFactory::new_intArray(nonoop_acmp_map_size + oop_acmp_map_size + 1, CHECK);
typeArrayHandle map_h(THREAD, map);
map_h->int_at_put(0, _layout_info->_nonoop_acmp_map->length());
for (int i = 0; i < _layout_info->_nonoop_acmp_map->length(); i++) {
map_h->int_at_put(i * 2 + 1, _layout_info->_nonoop_acmp_map->at(i).first);
map_h->int_at_put(i * 2 + 2, _layout_info->_nonoop_acmp_map->at(i).second);
}
int oop_map_start = nonoop_acmp_map_size + 1;
for (int i = 0; i < _layout_info->_oop_acmp_map->length(); i++) {
map_h->int_at_put(oop_map_start + i, _layout_info->_oop_acmp_map->at(i));
}
ik->java_mirror()->obj_field_put(ik->acmp_maps_offset(), map_h());
}

ClassLoadingService::notify_class_loaded(ik, false /* not shared class */);

if (!is_internal()) {
Expand Down
12 changes: 12 additions & 0 deletions src/hotspot/share/classfile/classFileParser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "oops/instanceKlass.hpp"
#include "oops/typeArrayOop.hpp"
#include "utilities/accessFlags.hpp"
#include "utilities/pair.hpp"

class Annotations;
template <typename T>
Expand Down Expand Up @@ -71,6 +72,8 @@ class OopMapBlocksBuilder : public ResourceObj {
class FieldLayoutInfo : public ResourceObj {
public:
OopMapBlocksBuilder* oop_map_blocks;
GrowableArray<Pair<int,int>>* _nonoop_acmp_map;
GrowableArray<int>* _oop_acmp_map;
int _instance_size;
int _nonstatic_field_size;
int _static_field_size;
Expand All @@ -83,11 +86,20 @@ class FieldLayoutInfo : public ResourceObj {
int _nullable_layout_size_in_bytes;
int _null_marker_offset;
int _null_reset_value_offset;
int _acmp_maps_offset;
bool _has_nonstatic_fields;
bool _is_naturally_atomic;
bool _must_be_atomic;
bool _has_inline_fields;
bool _is_empty_inline_klass;
FieldLayoutInfo() : oop_map_blocks(nullptr), _nonoop_acmp_map(nullptr), _oop_acmp_map(nullptr),
_instance_size(-1), _nonstatic_field_size(-1), _static_field_size(-1),
_payload_alignment(-1), _payload_offset(-1), _payload_size_in_bytes(-1),
_non_atomic_size_in_bytes(-1), _non_atomic_alignment(-1),
_atomic_layout_size_in_bytes(-1), _nullable_layout_size_in_bytes(-1),
_null_marker_offset(-1), _null_reset_value_offset(-1), _acmp_maps_offset(-1),
_has_nonstatic_fields(false), _is_naturally_atomic(false), _must_be_atomic(false),
_has_inline_fields(false), _is_empty_inline_klass(false) { }
};

// Parser for for .class files
Expand Down
168 changes: 168 additions & 0 deletions src/hotspot/share/classfile/fieldLayoutBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ FieldLayout::FieldLayout(GrowableArray<FieldInfo>* field_info, Array<InlineLayou
_super_alignment(-1),
_super_min_align_required(-1),
_null_reset_value_offset(-1),
_acmp_maps_offset(-1),
_super_has_fields(false),
_has_inherited_fields(false) {}

Expand Down Expand Up @@ -403,6 +404,9 @@ LayoutRawBlock* FieldLayout::insert_field_block(LayoutRawBlock* slot, LayoutRawB
if (_field_info->adr_at(block->field_index())->name(_cp) == vmSymbols::null_reset_value_name()) {
_null_reset_value_offset = block->offset();
}
if (_field_info->adr_at(block->field_index())->name(_cp) == vmSymbols::acmp_maps_name()) {
_acmp_maps_offset = block->offset();
}
}
if (block->block_kind() == LayoutRawBlock::FLAT && block->layout_kind() == LayoutKind::NULLABLE_ATOMIC_FLAT) {
int nm_offset = block->inline_klass()->null_marker_offset() - block->inline_klass()->payload_offset() + block->offset();
Expand Down Expand Up @@ -1249,6 +1253,7 @@ void FieldLayoutBuilder::compute_inline_class_layout() {
_static_layout->add(_static_fields->big_primitive_fields());
_static_layout->add(_static_fields->small_primitive_fields());

generate_acmp_maps();
epilogue();
}

Expand Down Expand Up @@ -1288,6 +1293,152 @@ void FieldLayoutBuilder::register_embedded_oops(OopMapBlocksBuilder* nonstatic_o
register_embedded_oops_from_list(nonstatic_oop_maps, group->small_primitive_fields());
}

static int insert_segment(GrowableArray<Pair<int,int>>* map, int offset, int size, int last_idx) {
if (map->is_empty()) {
return map->append(Pair<int,int>(offset, size));
}
last_idx = last_idx == -1 ? 0 : last_idx;
int start = map->adr_at(last_idx)->first > offset ? 0 : last_idx;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

last_idx is an optimization to try to prevent searching through the field map to find where to insert {offset,size} for the new entry. When would it not be ascending? super classes and inlined classes are inserted in order that they're found in the LayoutBlocks so wouldn't the offsets be ascending?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You answered this in slack for me. Can you put this at the top of this function:

The insert_segment creates an ordered list of maps {offset, size} from the LayoutRawBlocks.  LayoutRawBlocks are queued in ascending offset order, and are processed in this order.  This allows the last_idx optimization which would be critical for classes with a huge number of fields.
But, this property is not required, and, if you look at the entire set of fields being processed, they might not all be processed by ascending offset order.
Inherited fields are inserted first, by copying them from the super-class, and then local fields are processed. It is possible to have local fields interleaved with super-class fields. So all fields are not necessarily processed in ascending offset order. The algorithm tries to do it, and optimizes the insertion when it is done that way, but it also handles cases where this is not the case. 

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments have been added in FieldLayoutBuilder::generate_acmp_maps().
Let me know if the explanation is sufficient to clarify how the code works.

bool inserted = false;
for (int c = start; c < map->length() && !inserted ; c++) {
if (offset == (map->adr_at(c)->first + map->adr_at(c)->second)) {
//contiguous to the last field, can be coalesced
map->adr_at(c)->second = map->adr_at(c)->second + size;
inserted = true;
break; // break out of the for loop
}
if (offset < (map->adr_at(c)->first)) {
map->insert_before(c, Pair<int,int>(offset, size));
last_idx = c;
inserted = true;
break; // break out of the for loop
}
}
if (!inserted) {
last_idx = map->append(Pair<int,int>(offset, size));
}
return last_idx;
}

static int insert_map_at_offset(GrowableArray<Pair<int,int>>* nonoop_map, GrowableArray<int>* oop_map,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This copies the contents of the maps from an inline class that's flattened into this class or a super class into the map for this class.

const InstanceKlass* ik, int offset, int payload_offset, int last_idx) {
oop mirror = ik->java_mirror();
oop array = mirror->obj_field(ik->acmp_maps_offset());
assert(array != nullptr, "Sanity check");
typeArrayOop fmap = (typeArrayOop)array;
typeArrayHandle fmap_h(Thread::current(), fmap);
int nb_nonoop_field = fmap_h->int_at(0);
int field_offset = offset - payload_offset;
for (int i = 0; i < nb_nonoop_field; i++) {
last_idx = insert_segment(nonoop_map,
field_offset + fmap_h->int_at( i * 2 + 1),
fmap_h->int_at( i * 2 + 2), last_idx);
}
int len = fmap_h->length();
for (int i = nb_nonoop_field * 2 + 1; i < len; i++) {
oop_map->append(field_offset + fmap_h->int_at(i));
}
return last_idx;
}

static void split_after(GrowableArray<Pair<int,int>>* map, int idx, int head) {
int offset = map->adr_at(idx)->first;
int size = map->adr_at(idx)->second;
if (size <= head) return;
map->adr_at(idx)->first = offset + head;
map->adr_at(idx)->second = size - head;
map->insert_before(idx, Pair<int,int>(offset, head));

}

void FieldLayoutBuilder::generate_acmp_maps() {
assert(_is_inline_type || _is_abstract_value, "Must be done only for value classes (abstract or not)");

// create/initialize current class' maps
// The Pair<int,int> values in the nonoop_acmp_map represent <offset,size> segments of memory
_nonoop_acmp_map = new GrowableArray<Pair<int,int>>();
_oop_acmp_map = new GrowableArray<int>();
if (_is_empty_inline_class) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not return first before allocating these GrowableArray in resource area?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It streamlines the code in InstanceKlass::fill_instance_klass() by not having to handle the null map case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. I can't decide if you need a comment to prevent someone from changing this to have the quick exit. I guess they'll get a crash for the null case.

int last_idx = -1;
if (_super_klass != nullptr && _super_klass != vmClasses::Object_klass()) { // Assumes j.l.Object cannot have fields
last_idx = insert_map_at_offset(_nonoop_acmp_map, _oop_acmp_map, _super_klass, 0, 0, last_idx);
}

// Processing local fields
LayoutRawBlock* b = _layout->blocks();
while(b != _layout->last_block()) {
switch(b->block_kind()) {
case LayoutRawBlock::RESERVED:
case LayoutRawBlock::EMPTY:
case LayoutRawBlock::PADDING:
case LayoutRawBlock::NULL_MARKER:
case LayoutRawBlock::INHERITED: // inherited fields are handled during maps creation/initialization
// skip
break;

case LayoutRawBlock::REGULAR:
{
FieldInfo* fi = _field_info->adr_at(b->field_index());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an assumption that the block field indexes are in ascending order?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand the question.
LayoutRawBlocks represent the fields layout, they are chained in ascending offset order.
The field_index is the index in the FieldInfo array (from the class file) where this field is declared.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that was the question. I meant to ask if the offsets are in ascending order, so that makes the code to coalesce them simpler. Can you add a comment at 1367 that the field offsets are in ascending order and that makes reading this easier.

if (fi->signature(_constant_pool)->starts_with("L") || fi->signature(_constant_pool)->starts_with("[")) {
_oop_acmp_map->append(b->offset());
} else {
// Non-oop case
last_idx = insert_segment(_nonoop_acmp_map, b->offset(), b->size(), last_idx);
}
break;
}
case LayoutRawBlock::FLAT:
{
InlineKlass* vk = b->inline_klass();
last_idx = insert_map_at_offset(_nonoop_acmp_map, _oop_acmp_map, vk, b->offset(), vk->payload_offset(), last_idx);
if (b->layout_kind() == LayoutKind::NULLABLE_ATOMIC_FLAT) {
int null_marker_offset = b->offset() + vk->null_marker_offset_in_payload();
last_idx = insert_segment(_nonoop_acmp_map, null_marker_offset, 1, last_idx);
// Important note: the implementation assumes that for nullable flat fields, if the
// null marker is zero (field is null), then all the fields of the flat field are also
// zeroed. So, nullable flat field are not encoded different than null-free flat fields,
// all fields are included in the map, plus the null marker
// If it happens that the assumption above is wrong, then nullable flat fields would
// require a dedicated section in the acmp map, and be handled differently: null_marker
// comparison first, and if null markers are identical and non-zero, then conditional
// comparison of the other fields
}
}
break;

}
b = b->next_block();
}

// split segments into well-aligned blocks
int idx = 0;
while (idx < _nonoop_acmp_map->length()) {
int offset = _nonoop_acmp_map->adr_at(idx)->first;
int size = _nonoop_acmp_map->adr_at(idx)->second;
int mod = offset % 8;
switch (mod) {
case 0:
break;
case 4:
split_after(_nonoop_acmp_map, idx, 4);
break;
case 2:
case 6:
split_after(_nonoop_acmp_map, idx, 2);
break;
case 1:
case 3:
case 5:
case 7:
split_after(_nonoop_acmp_map, idx, 1);
break;
default:
ShouldNotReachHere();
}
idx++;
}
}

void FieldLayoutBuilder::epilogue() {
// Computing oopmaps
OopMapBlocksBuilder* nonstatic_oop_maps =
Expand Down Expand Up @@ -1338,6 +1489,13 @@ void FieldLayoutBuilder::epilogue() {
_info->_is_empty_inline_klass = _is_empty_inline_class;
}

// Acmp maps are needed for both concrete and abstract value classes
if (_is_inline_type || _is_abstract_value) {
_info->_acmp_maps_offset = _static_layout->acmp_maps_offset();
_info->_nonoop_acmp_map = _nonoop_acmp_map;
_info->_oop_acmp_map = _oop_acmp_map;
}

// This may be too restrictive, since if all the fields fit in 64
// bits we could make the decision to align instances of this class
// to 64-bit boundaries, and load and store them as single words.
Expand Down Expand Up @@ -1411,6 +1569,16 @@ void FieldLayoutBuilder::epilogue() {
if (_null_marker_offset != -1) {
st.print_cr("Null marker offset = %d", _null_marker_offset);
}
st.print("Non-oop acmp map: ");
for (int i = 0 ; i < _nonoop_acmp_map->length(); i++) {
st.print("<%d,%d>, ", _nonoop_acmp_map->at(i).first, _nonoop_acmp_map->at(i).second);
}
st.print_cr("");
st.print("oop acmp map: ");
for (int i = 0 ; i < _oop_acmp_map->length(); i++) {
st.print("%d, ", _oop_acmp_map->at(i));
}
st.print_cr("");
}
st.print_cr("---");
// Print output all together.
Expand Down
8 changes: 8 additions & 0 deletions src/hotspot/share/classfile/fieldLayoutBuilder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ class FieldLayout : public ResourceObj {
int _super_alignment;
int _super_min_align_required;
int _null_reset_value_offset; // offset of the reset value in class mirror, only for static layout of inline classes
int _acmp_maps_offset;
bool _super_has_fields;
bool _has_inherited_fields;

Expand Down Expand Up @@ -224,6 +225,10 @@ class FieldLayout : public ResourceObj {
assert(_null_reset_value_offset != -1, "Must have been set");
return _null_reset_value_offset;
}
int acmp_maps_offset() const {
assert(_acmp_maps_offset != -1, "Must have been set");
return _acmp_maps_offset;
}
bool super_has_fields() const { return _super_has_fields; }
bool has_inherited_fields() const { return _has_inherited_fields; }

Expand Down Expand Up @@ -281,6 +286,8 @@ class FieldLayoutBuilder : public ResourceObj {
FieldGroup* _static_fields;
FieldLayout* _layout;
FieldLayout* _static_layout;
GrowableArray<Pair<int,int>>* _nonoop_acmp_map ;
GrowableArray<int>* _oop_acmp_map;
int _nonstatic_oopmap_count;
int _payload_alignment;
int _payload_offset;
Expand Down Expand Up @@ -337,6 +344,7 @@ class FieldLayoutBuilder : public ResourceObj {
void add_flat_field_oopmap(OopMapBlocksBuilder* nonstatic_oop_map, InlineKlass* vk, int offset);
void register_embedded_oops_from_list(OopMapBlocksBuilder* nonstatic_oop_maps, GrowableArray<LayoutRawBlock*>* list);
void register_embedded_oops(OopMapBlocksBuilder* nonstatic_oop_maps, FieldGroup* group);
void generate_acmp_maps();
};

#endif // SHARE_CLASSFILE_FIELDLAYOUTBUILDER_HPP
2 changes: 2 additions & 0 deletions src/hotspot/share/classfile/vmSymbols.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,7 @@ class SerializeClosure;
template(resolved_references_name, "<resolved_references>") \
template(init_lock_name, "<init_lock>") \
template(null_reset_value_name, ".null_reset") \
template(acmp_maps_name, ".acmp_maps") \
template(empty_marker_name, ".empty") \
template(address_size_name, "ADDRESS_SIZE0") \
template(page_size_name, "PAGE_SIZE") \
Expand Down Expand Up @@ -775,6 +776,7 @@ class SerializeClosure;
\
template(java_lang_runtime_ValueObjectMethods, "java/lang/runtime/ValueObjectMethods") \
template(isSubstitutable_name, "isSubstitutable") \
template(isSubstitutableAlt_name, "isSubstitutableAlt") \
template(valueObjectHashCode_name, "valueObjectHashCode") \
template(jdk_internal_value_PrimitiveClass, "jdk/internal/value/PrimitiveClass") \
template(jdk_internal_value_ValueClass, "jdk/internal/value/ValueClass") \
Expand Down
1 change: 1 addition & 0 deletions src/hotspot/share/interpreter/bytecodeTracer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class BytecodePrinter {
// the adjustments that BytecodeStream performs applies.
void trace(const methodHandle& method, address bcp, uintptr_t tos, uintptr_t tos2, outputStream* st) {
ResourceMark rm;
ttyLocker ttyl;
bool method_changed = _current_method != method();
_current_method = method();
_is_linked = method->method_holder()->is_linked();
Expand Down
Loading