8278518: String(byte[], int, int, Charset) constructor and String.translateEscapes() miss bounds check elimination

Co-authored-by: Sergey Tsypanov <stsypanov@openjdk.org>
Reviewed-by: kvn, jrose
This commit is contained in:
Roland Westrelin 2022-01-27 08:44:58 +00:00
parent 2ea0edf2c4
commit 0dba170791
4 changed files with 232 additions and 9 deletions
src/hotspot/share/ci
test/micro/org/openjdk/bench/vm/compiler

@ -2202,10 +2202,10 @@ bool ciTypeFlow::can_trap(ciBytecodeStream& str) {
// ciTypeFlow::clone_loop_heads
//
// Clone the loop heads
bool ciTypeFlow::clone_loop_heads(Loop* lp, StateVector* temp_vector, JsrSet* temp_set) {
bool ciTypeFlow::clone_loop_heads(StateVector* temp_vector, JsrSet* temp_set) {
bool rslt = false;
for (PreorderLoops iter(loop_tree_root()); !iter.done(); iter.next()) {
lp = iter.current();
Loop* lp = iter.current();
Block* head = lp->head();
if (lp == loop_tree_root() ||
lp->is_irreducible() ||
@ -2450,6 +2450,103 @@ void ciTypeFlow::PreorderLoops::next() {
}
}
// If the tail is a branch to the head, retrieve how many times that path was taken from profiling
int ciTypeFlow::profiled_count(ciTypeFlow::Loop* loop) {
ciMethodData* methodData = method()->method_data();
if (!methodData->is_mature()) {
return 0;
}
ciTypeFlow::Block* tail = loop->tail();
if (tail->control() == -1) {
return 0;
}
ciProfileData* data = methodData->bci_to_data(tail->control());
assert(data != NULL, "some profile data expected at branch");
if (!data->is_JumpData()) {
return 0;
}
ciBytecodeStream iter(method());
iter.reset_to_bci(tail->control());
bool is_an_if = false;
bool wide = false;
Bytecodes::Code bc = iter.next();
switch (bc) {
case Bytecodes::_ifeq:
case Bytecodes::_ifne:
case Bytecodes::_iflt:
case Bytecodes::_ifge:
case Bytecodes::_ifgt:
case Bytecodes::_ifle:
case Bytecodes::_if_icmpeq:
case Bytecodes::_if_icmpne:
case Bytecodes::_if_icmplt:
case Bytecodes::_if_icmpge:
case Bytecodes::_if_icmpgt:
case Bytecodes::_if_icmple:
case Bytecodes::_if_acmpeq:
case Bytecodes::_if_acmpne:
case Bytecodes::_ifnull:
case Bytecodes::_ifnonnull:
is_an_if = true;
break;
case Bytecodes::_goto_w:
case Bytecodes::_jsr_w:
wide = true;
break;
case Bytecodes::_goto:
case Bytecodes::_jsr:
break;
default:
fatal(" invalid bytecode: %s", Bytecodes::name(iter.cur_bc()));
}
GrowableArray<ciTypeFlow::Block*>* succs = tail->successors();
if (!is_an_if) {
assert(((wide ? iter.get_far_dest() : iter.get_dest()) == loop->head()->start()) == (succs->at(ciTypeFlow::GOTO_TARGET) == loop->head()), "branch should lead to loop head");
if (succs->at(ciTypeFlow::GOTO_TARGET) == loop->head()) {
return method()->scale_count(data->as_JumpData()->taken());
}
} else {
assert((iter.get_dest() == loop->head()->start()) == (succs->at(ciTypeFlow::IF_TAKEN) == loop->head()), "bytecode and CFG not consistent");
assert((tail->limit() == loop->head()->start()) == (succs->at(ciTypeFlow::IF_NOT_TAKEN) == loop->head()), "bytecode and CFG not consistent");
if (succs->at(ciTypeFlow::IF_TAKEN) == loop->head()) {
return method()->scale_count(data->as_JumpData()->taken());
} else if (succs->at(ciTypeFlow::IF_NOT_TAKEN) == loop->head()) {
return method()->scale_count(data->as_BranchData()->not_taken());
}
}
return 0;
}
bool ciTypeFlow::Loop::at_insertion_point(Loop* lp, Loop* current) {
int lp_pre_order = lp->head()->pre_order();
if (current->head()->pre_order() < lp_pre_order) {
return true;
} else if (current->head()->pre_order() > lp_pre_order) {
return false;
}
// In the case of a shared head, make the most frequent head/tail (as reported by profiling) the inner loop
if (current->head() == lp->head()) {
int lp_count = outer()->profiled_count(lp);
int current_count = outer()->profiled_count(current);
if (current_count < lp_count) {
return true;
} else if (current_count > lp_count) {
return false;
}
}
if (current->tail()->pre_order() > lp->tail()->pre_order()) {
return true;
}
return false;
}
// ------------------------------------------------------------------
// ciTypeFlow::Loop::sorted_merge
//
@ -2467,12 +2564,10 @@ ciTypeFlow::Loop* ciTypeFlow::Loop::sorted_merge(Loop* lp) {
int lp_pre_order = lp->head()->pre_order();
// Find insertion point for "lp"
while (current != NULL) {
if (current == lp)
if (current == lp) {
return leaf; // Already in list
if (current->head()->pre_order() < lp_pre_order)
break;
if (current->head()->pre_order() == lp_pre_order &&
current->tail()->pre_order() > lp->tail()->pre_order()) {
}
if (at_insertion_point(lp, current)) {
break;
}
prev = current;
@ -2732,7 +2827,7 @@ void ciTypeFlow::flow_types() {
env()->comp_level() >= CompLevel_full_optimization) {
// Loop optimizations are not performed on Tier1 compiles.
bool changed = clone_loop_heads(loop_tree_root(), temp_vector, temp_set);
bool changed = clone_loop_heads(temp_vector, temp_set);
// If some loop heads were cloned, recompute postorder and loop tree
if (changed) {

@ -717,6 +717,9 @@ public:
bool _irreducible;
LocalSet _def_locals;
ciTypeFlow* outer() const { return head()->outer(); }
bool at_insertion_point(Loop* lp, Loop* current);
public:
Loop(Block* head, Block* tail) :
_parent(NULL), _sibling(NULL), _child(NULL),
@ -795,7 +798,7 @@ private:
bool can_trap(ciBytecodeStream& str);
// Clone the loop heads. Returns true if any cloning occurred.
bool clone_loop_heads(Loop* lp, StateVector* temp_vector, JsrSet* temp_set);
bool clone_loop_heads(StateVector* temp_vector, JsrSet* temp_set);
// Clone lp's head and replace tail's successors with clone.
Block* clone_loop_head(Loop* lp, StateVector* temp_vector, JsrSet* temp_set);
@ -913,6 +916,8 @@ private:
// Create the block map, which indexes blocks in pre_order.
void map_blocks();
int profiled_count(ciTypeFlow::Loop* loop);
public:
// Perform type inference flow analysis.
void do_flow();

@ -0,0 +1,65 @@
/*
* Copyright (c) 2022, Red Hat, Inc. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.vm.compiler;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Mode;
import java.util.concurrent.TimeUnit;
@State(Scope.Thread)
public class SharedLoopHeader {
private static final int size = 1000;
private static final boolean branch[] = new boolean[size];
private static final int count[] = new int[size];
@Setup
public void setup() {
for (int i = 0; i < branch.length; i++) {
branch[i] = ((i % 10) != 0);
}
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void sharedHeader() {
int i = 0;
while (i < branch.length) {
if (branch[i]) {
// common branch
count[i]++;
i++;
continue;
}
i += 2;
}
}
}

@ -0,0 +1,58 @@
/*
* Copyright (c) 2022, Red Hat, Inc. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.vm.compiler;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.Setup;
import java.util.concurrent.TimeUnit;
import java.nio.charset.StandardCharsets;
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class StringConstructorBenchmark {
private byte[] array;
private String str;
@Setup
public void setup() {
str = "Quizdeltagerne spiste jordb\u00e6r med fl\u00f8de, mens cirkusklovnen. \u042f";//Latin1 ending with Russian
array = str.getBytes(StandardCharsets.UTF_8);
}
@Benchmark
public String newString() {
return new String(array, 0, array.length, StandardCharsets.UTF_8);
}
@Benchmark
public String translateEscapes() {
return str.translateEscapes();
}
}