Skip to content

Commit 4662363

Browse files
Theo Weidmannshipilev
Theo Weidmann
authored andcommittedJan 29, 2025
8348687: [BACKOUT] C2: Non-fluid StringBuilder pattern bails out in OptoStringConcat
Reviewed-by: chagedorn, shade
1 parent d266ca9 commit 4662363

File tree

4 files changed

+67
-315
lines changed

4 files changed

+67
-315
lines changed
 

‎src/hotspot/share/opto/stringopts.cpp

+63-98
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,6 @@ class StringConcat : public ResourceObj {
173173
assert(!_control.contains(ctrl), "only push once");
174174
_control.push(ctrl);
175175
}
176-
bool has_control(Node* ctrl) {
177-
return _control.contains(ctrl);
178-
}
179176
void add_constructor(Node* init) {
180177
assert(!_constructors.contains(init), "only push once");
181178
_constructors.push(init);
@@ -410,66 +407,7 @@ Node_List PhaseStringOpts::collect_toString_calls() {
410407
return string_calls;
411408
}
412409

413-
PhaseStringOpts::ProcessAppendResult PhaseStringOpts::process_append_candidate(CallStaticJavaNode* cnode,
414-
StringConcat* sc,
415-
ciMethod* m,
416-
ciSymbol* string_sig,
417-
ciSymbol* int_sig,
418-
ciSymbol* char_sig) {
419-
if (cnode->method() != nullptr && !cnode->method()->is_static() &&
420-
cnode->method()->holder() == m->holder() &&
421-
cnode->method()->name() == ciSymbols::append_name() &&
422-
(cnode->method()->signature()->as_symbol() == string_sig ||
423-
cnode->method()->signature()->as_symbol() == char_sig ||
424-
cnode->method()->signature()->as_symbol() == int_sig)) {
425-
if (sc->has_control(cnode)) {
426-
return ProcessAppendResult::AppendWasAdded;
427-
}
428-
sc->add_control(cnode);
429-
Node* arg = cnode->in(TypeFunc::Parms + 1);
430-
if (arg == nullptr || arg->is_top()) {
431-
#ifndef PRODUCT
432-
if (PrintOptimizeStringConcat) {
433-
tty->print("giving up because the call is effectively dead");
434-
cnode->jvms()->dump_spec(tty);
435-
tty->cr();
436-
}
437-
#endif
438-
return ProcessAppendResult::AbortOptimization;
439-
}
440-
441-
if (cnode->method()->signature()->as_symbol() == int_sig) {
442-
sc->push_int(arg);
443-
} else if (cnode->method()->signature()->as_symbol() == char_sig) {
444-
sc->push_char(arg);
445-
} else if (arg->is_Proj() && arg->in(0)->is_CallStaticJava()) {
446-
CallStaticJavaNode* csj = arg->in(0)->as_CallStaticJava();
447-
if (csj->method() != nullptr &&
448-
csj->method()->intrinsic_id() == vmIntrinsics::_Integer_toString &&
449-
arg->outcnt() == 1) {
450-
// _control is the list of StringBuilder calls nodes which
451-
// will be replaced by new String code after this optimization.
452-
// Integer::toString() call is not part of StringBuilder calls
453-
// chain. It could be eliminated only if its result is used
454-
// only by this SB calls chain.
455-
// Another limitation: it should be used only once because
456-
// it is unknown that it is used only by this SB calls chain
457-
// until all related SB calls nodes are collected.
458-
assert(arg->unique_out() == cnode, "sanity");
459-
sc->add_control(csj);
460-
sc->push_int(csj->in(TypeFunc::Parms));
461-
} else {
462-
sc->push_string(arg);
463-
}
464-
} else {
465-
sc->push_string(arg);
466-
}
467-
return ProcessAppendResult::AppendWasAdded;
468-
}
469-
return ProcessAppendResult::CandidateIsNotAppend;
470-
}
471-
472-
// Recognize fluent-chain and non-fluent uses of StringBuilder/Buffer. They are either explicit usages
410+
// Recognize a fluent-chain of StringBuilder/Buffer. They are either explicit usages
473411
// of them or the legacy bytecodes of string concatenation prior to JEP-280. eg.
474412
//
475413
// String result = new StringBuilder()
@@ -478,17 +416,18 @@ PhaseStringOpts::ProcessAppendResult PhaseStringOpts::process_append_candidate(C
478416
// .append(123)
479417
// .toString(); // "foobar123"
480418
//
481-
// Fluent-chains are recognized by walking upwards along the receivers, starting from toString().
482-
// Once the allocation of the StringBuilder has been reached, DU pairs are examined to find the
483-
// constructor and non-fluent uses of the StringBuilder such as in this example:
419+
// PS: Only a certain subset of constructor and append methods are acceptable.
420+
// The criterion is that the length of argument is easy to work out in this phrase.
421+
// It will drop complex cases such as Object.
484422
//
423+
// Since it walks along the receivers of fluent-chain, it will give up if the codeshape is
424+
// not "fluent" enough. eg.
485425
// StringBuilder sb = new StringBuilder();
486426
// sb.append("foo");
487427
// sb.toString();
488428
//
489-
// PS: Only a certain subset of constructor and append methods are acceptable.
490-
// The criterion is that the length of argument is easy to work out in this phrase.
491-
// It will drop complex cases such as Object.
429+
// The receiver of toString method is the result of Allocation Node(CheckCastPP).
430+
// The append method is overlooked. It will fail at validate_control_flow() test.
492431
//
493432
StringConcat* PhaseStringOpts::build_candidate(CallStaticJavaNode* call) {
494433
ciMethod* m = call->method();
@@ -527,7 +466,7 @@ StringConcat* PhaseStringOpts::build_candidate(CallStaticJavaNode* call) {
527466
if (cnode == nullptr) {
528467
alloc = recv->isa_Allocate();
529468
if (alloc == nullptr) {
530-
return nullptr;
469+
break;
531470
}
532471
// Find the constructor call
533472
Node* result = alloc->result_cast();
@@ -539,7 +478,7 @@ StringConcat* PhaseStringOpts::build_candidate(CallStaticJavaNode* call) {
539478
alloc->jvms()->dump_spec(tty); tty->cr();
540479
}
541480
#endif
542-
return nullptr;
481+
break;
543482
}
544483
Node* constructor = nullptr;
545484
for (SimpleDUIterator i(result); i.has_next(); i.next()) {
@@ -550,10 +489,6 @@ StringConcat* PhaseStringOpts::build_candidate(CallStaticJavaNode* call) {
550489
use->method()->name() == ciSymbols::object_initializer_name() &&
551490
use->method()->holder() == m->holder()) {
552491
// Matched the constructor.
553-
if (constructor != nullptr) {
554-
// The constructor again. We must only process it once.
555-
continue;
556-
}
557492
ciSymbol* sig = use->method()->signature()->as_symbol();
558493
if (sig == ciSymbols::void_method_signature() ||
559494
sig == ciSymbols::int_void_signature() ||
@@ -607,16 +542,7 @@ StringConcat* PhaseStringOpts::build_candidate(CallStaticJavaNode* call) {
607542
}
608543
#endif
609544
}
610-
} else if (use != nullptr) {
611-
if (process_append_candidate(use, sc, m, string_sig, int_sig, char_sig) == ProcessAppendResult::AbortOptimization) {
612-
// We must abort if process_append_candidate tells us to...
613-
return nullptr;
614-
}
615-
// ...but we do not care if we really found an append or not:
616-
// - If we found an append, that's perfect. Nothing further to do.
617-
// - If this is a call to an unrelated method, validate_mem_flow() (and validate_control_flow())
618-
// will later check if this call prevents the optimization. So nothing to do here.
619-
// We will continue to look for the constructor (if not found already) and appends.
545+
break;
620546
}
621547
}
622548
if (constructor == nullptr) {
@@ -627,7 +553,7 @@ StringConcat* PhaseStringOpts::build_candidate(CallStaticJavaNode* call) {
627553
alloc->jvms()->dump_spec(tty); tty->cr();
628554
}
629555
#endif
630-
return nullptr;
556+
break;
631557
}
632558

633559
// Walked all the way back and found the constructor call so see
@@ -642,23 +568,62 @@ StringConcat* PhaseStringOpts::build_candidate(CallStaticJavaNode* call) {
642568
} else {
643569
return nullptr;
644570
}
645-
} else {
646-
ProcessAppendResult result = process_append_candidate(cnode, sc, m, string_sig, int_sig, char_sig);
647-
648-
if (result == ProcessAppendResult::AbortOptimization) {
649-
return nullptr;
650-
} else if (result == ProcessAppendResult::CandidateIsNotAppend) {
651-
// some unhandled signature
571+
} else if (cnode->method() == nullptr) {
572+
break;
573+
} else if (!cnode->method()->is_static() &&
574+
cnode->method()->holder() == m->holder() &&
575+
cnode->method()->name() == ciSymbols::append_name() &&
576+
(cnode->method()->signature()->as_symbol() == string_sig ||
577+
cnode->method()->signature()->as_symbol() == char_sig ||
578+
cnode->method()->signature()->as_symbol() == int_sig)) {
579+
sc->add_control(cnode);
580+
Node* arg = cnode->in(TypeFunc::Parms + 1);
581+
if (arg == nullptr || arg->is_top()) {
652582
#ifndef PRODUCT
653583
if (PrintOptimizeStringConcat) {
654-
tty->print("giving up because encountered unexpected signature ");
655-
cnode->tf()->dump();
656-
tty->cr();
657-
cnode->in(TypeFunc::Parms + 1)->dump();
584+
tty->print("giving up because the call is effectively dead");
585+
cnode->jvms()->dump_spec(tty); tty->cr();
658586
}
659587
#endif
660-
return nullptr;
588+
break;
661589
}
590+
if (cnode->method()->signature()->as_symbol() == int_sig) {
591+
sc->push_int(arg);
592+
} else if (cnode->method()->signature()->as_symbol() == char_sig) {
593+
sc->push_char(arg);
594+
} else {
595+
if (arg->is_Proj() && arg->in(0)->is_CallStaticJava()) {
596+
CallStaticJavaNode* csj = arg->in(0)->as_CallStaticJava();
597+
if (csj->method() != nullptr &&
598+
csj->method()->intrinsic_id() == vmIntrinsics::_Integer_toString &&
599+
arg->outcnt() == 1) {
600+
// _control is the list of StringBuilder calls nodes which
601+
// will be replaced by new String code after this optimization.
602+
// Integer::toString() call is not part of StringBuilder calls
603+
// chain. It could be eliminated only if its result is used
604+
// only by this SB calls chain.
605+
// Another limitation: it should be used only once because
606+
// it is unknown that it is used only by this SB calls chain
607+
// until all related SB calls nodes are collected.
608+
assert(arg->unique_out() == cnode, "sanity");
609+
sc->add_control(csj);
610+
sc->push_int(csj->in(TypeFunc::Parms));
611+
continue;
612+
}
613+
}
614+
sc->push_string(arg);
615+
}
616+
continue;
617+
} else {
618+
// some unhandled signature
619+
#ifndef PRODUCT
620+
if (PrintOptimizeStringConcat) {
621+
tty->print("giving up because encountered unexpected signature ");
622+
cnode->tf()->dump(); tty->cr();
623+
cnode->in(TypeFunc::Parms + 1)->dump();
624+
}
625+
#endif
626+
break;
662627
}
663628
}
664629
return nullptr;

‎src/hotspot/share/opto/stringopts.hpp

+4-22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2009, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -34,7 +34,7 @@ class IdealVariable;
3434
class PhaseStringOpts : public Phase {
3535
friend class StringConcat;
3636

37-
private:
37+
private:
3838
PhaseGVN* _gvn;
3939

4040
// List of dead nodes to clean up aggressively at the end
@@ -53,23 +53,6 @@ class PhaseStringOpts : public Phase {
5353
// a single string construction.
5454
StringConcat* build_candidate(CallStaticJavaNode* call);
5555

56-
enum class ProcessAppendResult {
57-
// Indicates that the candidate was indeed an append and process_append_candidate processed it
58-
// accordingly (added it to the StringConcat etc.)
59-
AppendWasAdded,
60-
// The candidate turned out not to be an append call. process_append_candidate did not do anything.
61-
CandidateIsNotAppend,
62-
// The candidate is an append call, but circumstances completely preventing string concat
63-
// optimization were detected and the optimization must abort.
64-
AbortOptimization
65-
};
66-
67-
// Called from build_candidate. Looks at an "append candidate", a call that might be a call
68-
// to StringBuilder::append. If so, adds it to the StringConcat.
69-
ProcessAppendResult process_append_candidate(CallStaticJavaNode* cnode, StringConcat* sc,
70-
ciMethod* m, ciSymbol* string_sig, ciSymbol* int_sig,
71-
ciSymbol* char_sig);
72-
7356
// Replace all the SB calls in concat with an optimization String allocation
7457
void replace_string_concat(StringConcat* concat);
7558

@@ -122,13 +105,12 @@ class PhaseStringOpts : public Phase {
122105
unroll_string_copy_length = 6
123106
};
124107

125-
public:
108+
public:
126109
PhaseStringOpts(PhaseGVN* gvn);
127110

128111
#ifndef PRODUCT
129112
static void print_statistics();
130-
131-
private:
113+
private:
132114
static uint _stropts_replaced;
133115
static uint _stropts_merged;
134116
static uint _stropts_total;

‎test/hotspot/jtreg/compiler/stringopts/TestFluidAndNonFluid.java

-134
This file was deleted.

‎test/micro/org/openjdk/bench/vm/compiler/FluidSBBench.java

-61
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.