Skip to content

Commit 02b8c1e

Browse files
Quan Anh MaiTobiHartmann
authored andcommitted
8354068: [lworld] Fold strict final fields more aggressively
Reviewed-by: thartmann
1 parent 4825d5f commit 02b8c1e

File tree

2 files changed

+242
-19
lines changed

2 files changed

+242
-19
lines changed

src/hotspot/share/opto/memnode.cpp

Lines changed: 183 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -143,31 +143,184 @@ extern void print_alias_types();
143143

144144
#endif
145145

146-
Node *MemNode::optimize_simple_memory_chain(Node *mchain, const TypeOopPtr *t_oop, Node *load, PhaseGVN *phase) {
147-
assert((t_oop != nullptr), "sanity");
146+
// If call is a constructor call on receiver, returns the class which declares the target method,
147+
// else returns nullptr. This information can then be used to deduce if call modifies a field of
148+
// receiver. Specifically, if the field is declared in a class that is a subclass of the one
149+
// declaring the constructor, then the field is set inside the constructor, else the field must be
150+
// set before the constructor invocation. E.g. A field Super.x will be set during the execution of
151+
// Sub::<init>, while a field Sub.y must be set before Super::<init> is invoked.
152+
static ciInstanceKlass* find_constructor_call_method_holder(Node* call, Node* receiver) {
153+
if (!call->is_CallJava()) {
154+
return nullptr;
155+
}
156+
157+
ciMethod* target = call->as_CallJava()->method();
158+
if (target == nullptr || !target->is_object_constructor()) {
159+
return nullptr;
160+
}
161+
162+
assert(call->req() > TypeFunc::Parms, "constructor must have at least 1 argument");
163+
Node* parm = call->in(TypeFunc::Parms)->uncast();
164+
receiver = receiver->uncast();
165+
if (parm == receiver || (parm->is_InlineType() && parm->as_InlineType()->get_oop()->uncast() == receiver)) {
166+
return target->holder();
167+
}
168+
169+
return nullptr;
170+
}
171+
172+
// Find the memory output corresponding to the fall-through path of a call
173+
static Node* find_call_fallthrough_mem_output(CallNode* call) {
174+
ResourceMark rm;
175+
CallProjections* projs = call->extract_projections(false, false);
176+
Node* res = projs->fallthrough_memproj;
177+
assert(res != nullptr, "must have a fallthrough mem output");
178+
return res;
179+
}
180+
181+
// Try to find a better memory input for a load from a strict final field of an object that is
182+
// allocated in the current compilation unit, or is the first parameter when we are in a
183+
// constructor
184+
static Node* optimize_strict_final_load_memory_from_local_object(ciField* field, ProjNode* base_uncasted) {
185+
if (!EnableValhalla) {
186+
// In this method we depends on the fact that strict fields are set before the invocation of
187+
// super(), I'm not sure if this is true without Valhalla
188+
return nullptr;
189+
}
190+
191+
// The node that can be passed into a constructor
192+
Node* base = base_uncasted;
193+
if (!base_uncasted->is_Parm()) {
194+
assert(base_uncasted->_con == AllocateNode::RawAddress && base_uncasted->in(0)->is_Allocate(), "must be the RawAddress of an AllocateNode");
195+
base = base_uncasted->in(0)->as_Allocate()->result_cast();
196+
assert(base != nullptr && base->in(1) == base_uncasted, "must find a valid base");
197+
}
198+
199+
// Try to see if there is a constructor call on the base
200+
for (DUIterator_Fast imax, i = base->fast_outs(imax); i < imax; i++) {
201+
Node* out = base->fast_out(i);
202+
ciInstanceKlass* target_holder = find_constructor_call_method_holder(out, base);
203+
if (target_holder == nullptr) {
204+
continue;
205+
} else if (target_holder->is_subclass_of(field->holder())) {
206+
return find_call_fallthrough_mem_output(out->as_CallJava());
207+
} else {
208+
Node* res = out->in(TypeFunc::Memory);
209+
assert(res != nullptr, "should have a memory input");
210+
return res;
211+
}
212+
}
213+
214+
return nullptr;
215+
}
216+
217+
// Try to find a better memory input for a load from a strict final field
218+
static Node* try_optimize_strict_final_load_memory(PhaseGVN* phase, ciField* field, Node* adr, ProjNode*& base_local) {
219+
intptr_t offset = 0;
220+
Node* base = AddPNode::Ideal_base_and_offset(adr, phase, offset);
221+
if (base == nullptr) {
222+
return nullptr;
223+
}
224+
225+
Node* base_uncasted = base->uncast();
226+
if (base_uncasted->is_Proj()) {
227+
MultiNode* multi = base_uncasted->in(0)->as_Multi();
228+
if (multi->is_Allocate()) {
229+
// The result of an AllocateNode, try to find the constructor call
230+
base_local = base_uncasted->as_Proj();
231+
return optimize_strict_final_load_memory_from_local_object(field, base_uncasted->as_Proj());
232+
} else if (multi->is_Call()) {
233+
// The oop is returned from a call, the memory can be the fallthrough output of the call
234+
return find_call_fallthrough_mem_output(multi->as_Call());
235+
} else if (multi->is_Start()) {
236+
// The oop is a parameter
237+
if (phase->C->method()->is_object_constructor() && base_uncasted->as_Proj()->_con == TypeFunc::Parms) {
238+
// The receiver of a constructor is similar to the result of an AllocateNode
239+
base_local = base_uncasted->as_Proj();
240+
return optimize_strict_final_load_memory_from_local_object(field, base_uncasted->as_Proj());
241+
} else {
242+
// Use the start memory otherwise
243+
return multi->proj_out(TypeFunc::Memory);
244+
}
245+
}
246+
}
247+
248+
return nullptr;
249+
}
250+
251+
// Whether a call can modify a strict final field of base_local, given that base_local is allocated
252+
// inside the current compilation unit, or is the first parameter when the compilation root is a
253+
// constructor. This is equivalent to asking whether base_local is the receiver of the constructor
254+
// invocation call and the class declaring the target method is a subclass of the class declaring
255+
// field.
256+
static bool call_can_modify_local_object(ciField* field, CallNode* call, Node* base_local) {
257+
ciInstanceKlass* target_holder = find_constructor_call_method_holder(call, base_local);
258+
return target_holder != nullptr && target_holder->is_subclass_of(field->holder());
259+
}
260+
261+
Node* MemNode::optimize_simple_memory_chain(Node* mchain, const TypeOopPtr* t_oop, Node* load, PhaseGVN* phase) {
262+
assert(t_oop != nullptr, "sanity");
148263
bool is_instance = t_oop->is_known_instance_field();
149-
bool is_boxed_value_load = t_oop->is_ptr_to_boxed_value() &&
150-
(load != nullptr) && load->is_Load() &&
151-
(phase->is_IterGVN() != nullptr);
152-
if (!(is_instance || is_boxed_value_load))
153-
return mchain; // don't try to optimize non-instance types
264+
265+
ciField* field = phase->C->alias_type(t_oop)->field();
266+
bool is_strict_final_load = false;
267+
268+
// After macro expansion, an allocation may become a call, changing the memory input to the
269+
// memory output of that call would be illegal. As a result, disallow this transformation after
270+
// macro expansion.
271+
if (phase->is_IterGVN() && phase->C->allow_macro_nodes() && load != nullptr && load->is_Load() && !load->as_Load()->is_mismatched_access()) {
272+
if (EnableValhalla) {
273+
if (field != nullptr && (field->holder()->is_inlinetype() || field->holder()->is_abstract_value_klass())) {
274+
is_strict_final_load = true;
275+
}
276+
#ifdef ASSERT
277+
if (t_oop->is_inlinetypeptr() && t_oop->inline_klass()->contains_field_offset(t_oop->offset())) {
278+
assert(is_strict_final_load, "sanity check for basic cases");
279+
}
280+
#endif
281+
} else {
282+
is_strict_final_load = field != nullptr && t_oop->is_ptr_to_boxed_value();
283+
}
284+
}
285+
286+
if (!is_instance && !is_strict_final_load) {
287+
return mchain;
288+
}
289+
290+
Node* result = mchain;
291+
ProjNode* base_local = nullptr;
292+
293+
if (is_strict_final_load) {
294+
Node* adr = load->in(MemNode::Address);
295+
assert(phase->type(adr) == t_oop, "inconsistent type");
296+
Node* tmp = try_optimize_strict_final_load_memory(phase, field, adr, base_local);
297+
if (tmp != nullptr) {
298+
result = tmp;
299+
}
300+
}
301+
154302
uint instance_id = t_oop->instance_id();
155-
Node *start_mem = phase->C->start()->proj_out_or_null(TypeFunc::Memory);
156-
Node *prev = nullptr;
157-
Node *result = mchain;
303+
Node* start_mem = phase->C->start()->proj_out_or_null(TypeFunc::Memory);
304+
Node* prev = nullptr;
158305
while (prev != result) {
159306
prev = result;
160-
if (result == start_mem)
161-
break; // hit one of our sentinels
307+
if (result == start_mem) {
308+
// start_mem is the earliest memory possible
309+
break;
310+
}
311+
162312
// skip over a call which does not affect this memory slice
163313
if (result->is_Proj() && result->as_Proj()->_con == TypeFunc::Memory) {
164-
Node *proj_in = result->in(0);
314+
Node* proj_in = result->in(0);
165315
if (proj_in->is_Allocate() && proj_in->_idx == instance_id) {
166-
break; // hit one of our sentinels
316+
// This is the allocation that creates the object from which we are loading from
317+
break;
167318
} else if (proj_in->is_Call()) {
168319
// ArrayCopyNodes processed here as well
169-
CallNode *call = proj_in->as_Call();
170-
if (!call->may_modify(t_oop, phase)) { // returns false for instances
320+
CallNode* call = proj_in->as_Call();
321+
if (!call->may_modify(t_oop, phase)) {
322+
result = call->in(TypeFunc::Memory);
323+
} else if (is_strict_final_load && base_local != nullptr && !call_can_modify_local_object(field, call, base_local)) {
171324
result = call->in(TypeFunc::Memory);
172325
}
173326
} else if (proj_in->is_Initialize()) {
@@ -179,11 +332,15 @@ Node *MemNode::optimize_simple_memory_chain(Node *mchain, const TypeOopPtr *t_oo
179332
}
180333
if (is_instance) {
181334
result = proj_in->in(TypeFunc::Memory);
182-
} else if (is_boxed_value_load) {
335+
} else if (is_strict_final_load) {
183336
Node* klass = alloc->in(AllocateNode::KlassNode);
184337
const TypeKlassPtr* tklass = phase->type(klass)->is_klassptr();
185338
if (tklass->klass_is_exact() && !tklass->exact_klass()->equals(t_oop->is_instptr()->exact_klass())) {
186-
result = proj_in->in(TypeFunc::Memory); // not related allocation
339+
// Allocation of another type, must be another object
340+
result = proj_in->in(TypeFunc::Memory);
341+
} else if (base_local != nullptr && (base_local->is_Parm() || base_local->in(0) != alloc)) {
342+
// Allocation of another object
343+
result = proj_in->in(TypeFunc::Memory);
187344
}
188345
}
189346
} else if (proj_in->is_MemBar()) {
@@ -1994,7 +2151,14 @@ Node *LoadNode::Ideal(PhaseGVN *phase, bool can_reshape) {
19942151
}
19952152
}
19962153

1997-
return progress ? this : nullptr;
2154+
if (progress) {
2155+
return this;
2156+
}
2157+
2158+
if (!can_reshape) {
2159+
phase->record_for_igvn(this);
2160+
}
2161+
return nullptr;
19982162
}
19992163

20002164
// Helper to recognize certain Klass fields which are invariant across

test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestBasicFunctionality.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ protected long hash(int x, long y) {
7171
return MyValue1.createWithFieldsInline(x, y).hash();
7272
}
7373

74+
@DontInline
75+
static void call() {}
76+
7477
// Receive value class through call to interpreter
7578
@Test
7679
@IR(failOn = {ALLOC, STORE, TRAP})
@@ -1065,4 +1068,60 @@ public void test42_verifier() {
10651068
MyValue41 val = new MyValue41(rI);
10661069
test42(val);
10671070
}
1071+
1072+
static value class MyValue42 {
1073+
int x;
1074+
1075+
@ForceInline
1076+
MyValue42(int x) {
1077+
this.x = x;
1078+
call();
1079+
super();
1080+
}
1081+
1082+
@ForceInline
1083+
static Object make(int x) {
1084+
return new MyValue42(x);
1085+
}
1086+
}
1087+
1088+
@Test
1089+
@IR(failOn = {LOAD})
1090+
public MyValue42 test43(int x) {
1091+
return (MyValue42) MyValue42.make(x);
1092+
}
1093+
1094+
@Run(test = "test43")
1095+
public void test43_verifier() {
1096+
MyValue42 v = test43(rI);
1097+
Asserts.assertEQ(rI, v.x);
1098+
}
1099+
1100+
static value class MyValue43 {
1101+
int x;
1102+
1103+
@ForceInline
1104+
MyValue43(int x) {
1105+
this.x = x;
1106+
super();
1107+
call();
1108+
}
1109+
1110+
@ForceInline
1111+
static Object make(int x) {
1112+
return new MyValue43(x);
1113+
}
1114+
}
1115+
1116+
@Test
1117+
@IR(failOn = {LOAD})
1118+
public MyValue43 test44(int x) {
1119+
return (MyValue43) MyValue43.make(x);
1120+
}
1121+
1122+
@Run(test = "test44")
1123+
public void test44_verifier() {
1124+
MyValue43 v = test44(rI);
1125+
Asserts.assertEQ(rI, v.x);
1126+
}
10681127
}

0 commit comments

Comments
 (0)