@@ -143,31 +143,184 @@ extern void print_alias_types();
143
143
144
144
#endif
145
145
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" );
148
263
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
+
154
302
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 ;
158
305
while (prev != result) {
159
306
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
+
162
312
// skip over a call which does not affect this memory slice
163
313
if (result->is_Proj () && result->as_Proj ()->_con == TypeFunc::Memory) {
164
- Node * proj_in = result->in (0 );
314
+ Node* proj_in = result->in (0 );
165
315
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 ;
167
318
} else if (proj_in->is_Call ()) {
168
319
// 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)) {
171
324
result = call->in (TypeFunc::Memory);
172
325
}
173
326
} else if (proj_in->is_Initialize ()) {
@@ -179,11 +332,15 @@ Node *MemNode::optimize_simple_memory_chain(Node *mchain, const TypeOopPtr *t_oo
179
332
}
180
333
if (is_instance) {
181
334
result = proj_in->in (TypeFunc::Memory);
182
- } else if (is_boxed_value_load ) {
335
+ } else if (is_strict_final_load ) {
183
336
Node* klass = alloc->in (AllocateNode::KlassNode);
184
337
const TypeKlassPtr* tklass = phase->type (klass)->is_klassptr ();
185
338
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);
187
344
}
188
345
}
189
346
} else if (proj_in->is_MemBar ()) {
@@ -1994,7 +2151,14 @@ Node *LoadNode::Ideal(PhaseGVN *phase, bool can_reshape) {
1994
2151
}
1995
2152
}
1996
2153
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 ;
1998
2162
}
1999
2163
2000
2164
// Helper to recognize certain Klass fields which are invariant across
0 commit comments