Skip to content

Commit

Permalink
8287061: Support for rematerializing scalar replaced objects particip…
Browse files Browse the repository at this point in the history
…ating in allocation merges

Reviewed-by: kvn, vlivanov
  • Loading branch information
Cesar Soares Lucas authored and Vladimir Kozlov committed Jul 17, 2023
1 parent 3236ba0 commit a53345a
Show file tree
Hide file tree
Showing 26 changed files with 2,625 additions and 248 deletions.
120 changes: 112 additions & 8 deletions src/hotspot/share/code/debugInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "gc/shared/collectedHeap.hpp"
#include "memory/universe.hpp"
#include "oops/oop.inline.hpp"
#include "runtime/stackValue.hpp"
#include "runtime/handles.inline.hpp"
#include "runtime/interfaceSupport.inline.hpp"
#include "runtime/javaThread.hpp"
Expand Down Expand Up @@ -80,6 +81,20 @@ ScopeValue* DebugInfoReadStream::read_object_value(bool is_auto_box) {
return result;
}

ScopeValue* DebugInfoReadStream::read_object_merge_value() {
int id = read_int();
#ifdef ASSERT
assert(_obj_pool != nullptr, "object pool does not exist");
for (int i = _obj_pool->length() - 1; i >= 0; i--) {
assert(_obj_pool->at(i)->as_ObjectValue()->id() != id, "should not be read twice");
}
#endif
ObjectMergeValue* result = new ObjectMergeValue(id);
_obj_pool->push(result);
result->read_object(this);
return result;
}

ScopeValue* DebugInfoReadStream::get_cached_object() {
int id = read_int();
assert(_obj_pool != nullptr, "object pool does not exist");
Expand All @@ -98,7 +113,8 @@ ScopeValue* DebugInfoReadStream::get_cached_object() {
enum { LOCATION_CODE = 0, CONSTANT_INT_CODE = 1, CONSTANT_OOP_CODE = 2,
CONSTANT_LONG_CODE = 3, CONSTANT_DOUBLE_CODE = 4,
OBJECT_CODE = 5, OBJECT_ID_CODE = 6,
AUTO_BOX_OBJECT_CODE = 7, MARKER_CODE = 8 };
AUTO_BOX_OBJECT_CODE = 7, MARKER_CODE = 8,
OBJECT_MERGE_CODE = 9 };

ScopeValue* ScopeValue::read_from(DebugInfoReadStream* stream) {
ScopeValue* result = nullptr;
Expand All @@ -110,6 +126,7 @@ ScopeValue* ScopeValue::read_from(DebugInfoReadStream* stream) {
case CONSTANT_DOUBLE_CODE: result = new ConstantDoubleValue(stream); 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 OBJECT_MERGE_CODE: result = stream->read_object_merge_value(); break;
case OBJECT_ID_CODE: result = stream->get_cached_object(); break;
case MARKER_CODE: result = new MarkerValue(); break;
default: ShouldNotReachHere();
Expand Down Expand Up @@ -149,6 +166,7 @@ void ObjectValue::set_value(oop value) {
}

void ObjectValue::read_object(DebugInfoReadStream* stream) {
_is_root = stream->read_bool();
_klass = read_from(stream);
assert(_klass->is_constant_oop(), "should be constant java mirror oop");
int length = stream->read_int();
Expand All @@ -166,6 +184,7 @@ void ObjectValue::write_on(DebugInfoWriteStream* stream) {
set_visited(true);
stream->write_int(is_auto_box() ? AUTO_BOX_OBJECT_CODE : OBJECT_CODE);
stream->write_int(_id);
stream->write_bool(_is_root);
_klass->write_on(stream);
int length = _field_values.length();
stream->write_int(length);
Expand All @@ -176,21 +195,106 @@ void ObjectValue::write_on(DebugInfoWriteStream* stream) {
}

void ObjectValue::print_on(outputStream* st) const {
st->print("%s[%d]", is_auto_box() ? "box_obj" : "obj", _id);
st->print("%s[%d]", is_auto_box() ? "box_obj" : is_object_merge() ? "merge_obj" : "obj", _id);
}

void ObjectValue::print_fields_on(outputStream* st) const {
#ifndef PRODUCT
if (_field_values.length() > 0) {
_field_values.at(0)->print_on(st);
}
for (int i = 1; i < _field_values.length(); i++) {
st->print(", ");
_field_values.at(i)->print_on(st);
if (is_object_merge()) {
ObjectMergeValue* omv = (ObjectMergeValue*)this;
st->print("selector=\"");
omv->selector()->print_on(st);
st->print("\"");
ScopeValue* merge_pointer = omv->merge_pointer();
if (!(merge_pointer->is_object() && merge_pointer->as_ObjectValue()->value()() == nullptr) &&
!(merge_pointer->is_constant_oop() && merge_pointer->as_ConstantOopReadValue()->value()() == nullptr)) {
st->print(", merge_pointer=\"");
merge_pointer->print_on(st);
st->print("\"");
}
GrowableArray<ScopeValue*>* possible_objects = omv->possible_objects();
st->print(", candidate_objs=[%d", possible_objects->at(0)->as_ObjectValue()->id());
int ncandidates = possible_objects->length();
for (int i = 1; i < ncandidates; i++) {
st->print(", %d", possible_objects->at(i)->as_ObjectValue()->id());
}
st->print("]");
} else {
st->print("\n Fields: ");
if (_field_values.length() > 0) {
_field_values.at(0)->print_on(st);
}
for (int i = 1; i < _field_values.length(); i++) {
st->print(", ");
_field_values.at(i)->print_on(st);
}
}
#endif
}


// ObjectMergeValue

// Returns the ObjectValue that should be used for the local that this
// ObjectMergeValue represents. ObjectMergeValue represents allocation
// merges in C2. This method will select which path the allocation merge
// took during execution of the Trap that triggered the rematerialization
// of the object.
ObjectValue* ObjectMergeValue::select(frame& fr, RegisterMap& reg_map) {
StackValue* sv_selector = StackValue::create_stack_value(&fr, &reg_map, _selector);
jint selector = sv_selector->get_int();

// If the selector is '-1' it means that execution followed the path
// where no scalar replacement happened.
// Otherwise, it is the index in _possible_objects array that holds
// the description of the scalar replaced object.
if (selector == -1) {
StackValue* sv_merge_pointer = StackValue::create_stack_value(&fr, &reg_map, _merge_pointer);
_selected = new ObjectValue(id());

// Retrieve the pointer to the real object and use it as if we had
// allocated it during the deoptimization
_selected->set_value(sv_merge_pointer->get_obj()());

// No need to rematerialize
return nullptr;
} else {
assert(selector < _possible_objects.length(), "sanity");
_selected = (ObjectValue*) _possible_objects.at(selector);
return _selected;
}
}

void ObjectMergeValue::read_object(DebugInfoReadStream* stream) {
_selector = read_from(stream);
_merge_pointer = read_from(stream);
int ncandidates = stream->read_int();
for (int i = 0; i < ncandidates; i++) {
ScopeValue* result = read_from(stream);
assert(result->is_object(), "Candidate is not an object!");
ObjectValue* obj = result->as_ObjectValue();
_possible_objects.append(obj);
}
}

void ObjectMergeValue::write_on(DebugInfoWriteStream* stream) {
if (is_visited()) {
stream->write_int(OBJECT_ID_CODE);
stream->write_int(_id);
} else {
set_visited(true);
stream->write_int(OBJECT_MERGE_CODE);
stream->write_int(_id);
_selector->write_on(stream);
_merge_pointer->write_on(stream);
int ncandidates = _possible_objects.length();
stream->write_int(ncandidates);
for (int i = 0; i < ncandidates; i++) {
_possible_objects.at(i)->as_ObjectValue()->write_on(stream);
}
}
}

// ConstantIntValue

ConstantIntValue::ConstantIntValue(DebugInfoReadStream* stream) {
Expand Down
102 changes: 89 additions & 13 deletions src/hotspot/share/code/debugInfo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,14 @@ class ConstantOopReadValue;
class ConstantOopWriteValue;
class LocationValue;
class ObjectValue;
class ObjectMergeValue;

class ScopeValue: public AnyObj {
public:
// Testers
virtual bool is_location() const { return false; }
virtual bool is_object() const { return false; }
virtual bool is_object_merge() 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; }
Expand All @@ -73,6 +75,11 @@ class ScopeValue: public AnyObj {
return (ObjectValue*)this;
}

ObjectMergeValue* as_ObjectMergeValue() {
assert(is_object_merge(), "must be");
return (ObjectMergeValue*)this;
}

LocationValue* as_LocationValue() {
assert(is_location(), "must be");
return (LocationValue*)this;
Expand Down Expand Up @@ -126,13 +133,18 @@ class ObjectValue: public ScopeValue {
GrowableArray<ScopeValue*> _field_values;
Handle _value;
bool _visited;
bool _is_root; // Will be true if this object is referred to
// as a local/expression/monitor in the JVMs.
// Otherwise false, meaning it's just a candidate
// in an object allocation merge.
public:
ObjectValue(int id, ScopeValue* klass)
: _id(id)
, _klass(klass)
, _field_values()
, _value()
, _visited(false) {
, _visited(false)
, _is_root(true) {
assert(klass->is_constant_oop(), "should be constant java mirror oop");
}

Expand All @@ -141,20 +153,24 @@ class ObjectValue: public ScopeValue {
, _klass(nullptr)
, _field_values()
, _value()
, _visited(false) {}
, _visited(false)
, _is_root(true) {}

// Accessors
bool is_object() const { return true; }
int id() const { return _id; }
ScopeValue* klass() const { return _klass; }
GrowableArray<ScopeValue*>* field_values() { return &_field_values; }
ScopeValue* field_at(int i) const { return _field_values.at(i); }
int field_size() { return _field_values.length(); }
Handle value() const { return _value; }
bool is_visited() const { return _visited; }

void set_value(oop value);
void set_visited(bool visited) { _visited = visited; }
bool is_object() const { return true; }
int id() const { return _id; }
virtual ScopeValue* klass() const { return _klass; }
virtual GrowableArray<ScopeValue*>* field_values() { return &_field_values; }
virtual ScopeValue* field_at(int i) const { return _field_values.at(i); }
virtual int field_size() { return _field_values.length(); }
virtual Handle value() const { return _value; }
bool is_visited() const { return _visited; }
bool is_root() const { return _is_root; }

void set_id(int id) { _id = id; }
virtual void set_value(oop value);
void set_visited(bool visited) { _visited = visited; }
void set_root(bool root) { _is_root = root; }

// Serialization of debugging information
void read_object(DebugInfoReadStream* stream);
Expand All @@ -165,6 +181,65 @@ class ObjectValue: public ScopeValue {
void print_fields_on(outputStream* st) const;
};

// An ObjectMergeValue describes objects that were inputs to a Phi in C2 and at
// least one of them was scalar replaced.
// '_selector' is an integer value that will be '-1' if during the execution of
// the C2 compiled code the path taken was that of the Phi input that was NOT
// scalar replaced. In that case '_merge_pointer' is a pointer to an already
// allocated object. If '_selector' is not '-1' then it should be the index of
// an object in '_possible_objects'. That object is an ObjectValue describing an
// object that was scalar replaced.

class ObjectMergeValue: public ObjectValue {
protected:
ScopeValue* _selector;
ScopeValue* _merge_pointer;
GrowableArray<ScopeValue*> _possible_objects;

// This holds the ObjectValue that should be used in place of this
// ObjectMergeValue. I.e., it's the ScopeValue from _possible_objects that was
// selected by 'select()' or is a on-the-fly created ScopeValue representing
// the _merge_pointer if _selector is -1.
//
// We need to keep this reference around because there will be entries in
// ScopeDesc that reference this ObjectMergeValue directly. After
// rematerialization ObjectMergeValue will be just a wrapper for the
// Objectvalue pointed by _selected.
ObjectValue* _selected;
public:
ObjectMergeValue(int id, ScopeValue* merge_pointer, ScopeValue* selector)
: ObjectValue(id)
, _selector(selector)
, _merge_pointer(merge_pointer)
, _possible_objects()
, _selected(nullptr) {}

ObjectMergeValue(int id)
: ObjectValue(id)
, _selector(nullptr)
, _merge_pointer(nullptr)
, _possible_objects()
, _selected(nullptr) {}

bool is_object_merge() const { return true; }
ScopeValue* selector() const { return _selector; }
ScopeValue* merge_pointer() const { return _merge_pointer; }
GrowableArray<ScopeValue*>* possible_objects() { return &_possible_objects; }
ObjectValue* select(frame& fr, RegisterMap& reg_map) ;

ScopeValue* klass() const { ShouldNotReachHere(); return nullptr; }
GrowableArray<ScopeValue*>* field_values() { ShouldNotReachHere(); return nullptr; }
ScopeValue* field_at(int i) const { ShouldNotReachHere(); return nullptr; }
int field_size() { ShouldNotReachHere(); return -1; }

Handle value() const { assert(_selected != nullptr, "Should call select() first."); return _selected->value(); }
void set_value(oop value) { assert(_selected != nullptr, "Should call select() first."); _selected->set_value(value); }

// Serialization of debugging information
void read_object(DebugInfoReadStream* stream);
void write_on(DebugInfoWriteStream* stream);
};

class AutoBoxObjectValue : public ObjectValue {
bool _cached;
public:
Expand Down Expand Up @@ -316,6 +391,7 @@ class DebugInfoReadStream : public CompressedReadStream {
return o;
}
ScopeValue* read_object_value(bool is_auto_box);
ScopeValue* read_object_merge_value();
ScopeValue* get_cached_object();
// BCI encoding is mostly unsigned, but -1 is a distinguished value
int read_bci() { return read_int() + InvocationEntryBci; }
Expand Down
41 changes: 38 additions & 3 deletions src/hotspot/share/code/scopeDesc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ GrowableArray<ScopeValue*>* ScopeDesc::decode_object_values(int decode_offset) {
// object's fields could reference it (OBJECT_ID_CODE).
(void)ScopeValue::read_from(stream);
}
assert(result->length() == length, "inconsistent debug information");
return result;
}

Expand All @@ -130,6 +129,38 @@ GrowableArray<MonitorValue*>* ScopeDesc::decode_monitor_values(int decode_offset
return result;
}

GrowableArray<ScopeValue*>* ScopeDesc::objects_to_rematerialize(frame& frm, RegisterMap& map) {
if (_objects == nullptr) {
return nullptr;
}

GrowableArray<ScopeValue*>* result = new GrowableArray<ScopeValue*>();
for (int i = 0; i < _objects->length(); i++) {
assert(_objects->at(i)->is_object(), "invalid debug information");
ObjectValue* sv = _objects->at(i)->as_ObjectValue();

// If the object is not referenced in current JVM state, then it's only
// a candidate in an ObjectMergeValue, we don't need to rematerialize it
// unless when/if it's returned by 'select()' below.
if (!sv->is_root()) {
continue;
}

if (sv->is_object_merge()) {
sv = sv->as_ObjectMergeValue()->select(frm, map);
// If select() returns nullptr, then the object doesn't need to be
// rematerialized.
if (sv == nullptr) {
continue;
}
}

result->append_if_missing(sv);
}

return result;
}

DebugInfoReadStream* ScopeDesc::stream_at(int decode_offset) const {
return new DebugInfoReadStream(_code, decode_offset, _objects);
}
Expand Down Expand Up @@ -238,8 +269,12 @@ void ScopeDesc::print_on(outputStream* st, PcDesc* pd) const {
st->print_cr(" Objects");
for (int i = 0; i < _objects->length(); i++) {
ObjectValue* sv = (ObjectValue*) _objects->at(i);
st->print(" - %d: ", sv->id());
st->print("%s ", java_lang_Class::as_Klass(sv->klass()->as_ConstantOopReadValue()->value()())->external_name());
st->print(" - %d: %c ", i, sv->is_root() ? 'R' : ' ');
sv->print_on(st);
st->print(", ");
if (!sv->is_object_merge()) {
st->print("%s", java_lang_Class::as_Klass(sv->klass()->as_ConstantOopReadValue()->value()())->external_name());
}
sv->print_fields_on(st);
st->cr();
}
Expand Down
Loading

1 comment on commit a53345a

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

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

Please sign in to comment.