Projects
Mega:23.09
openjdk-1.8.0
_service:tar_scm:8080289-8040213-8189067-move-t...
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:tar_scm:8080289-8040213-8189067-move-the-store-out-of-the-loop.patch of Package openjdk-1.8.0
From c2d7c271a60a6892bbbf7a2d585aa5b50c85bef1 Mon Sep 17 00:00:00 2001 Date: Sat, 23 May 2020 17:40:00 +0800 Subject: [PATCH] 8080289 8040213 8189067: move the store out of the loop Summary: <superWord> : move the store out of the loop LLT: NA Bug url: https://bugs.openjdk.java.net/browse/JDK-8080289 https://bugs.openjdk.java.net/browse/JDK-8040213 https://bugs.openjdk.java.net/browse/JDK-8189067 --- hotspot/src/share/vm/opto/loopnode.cpp | 2 +- hotspot/src/share/vm/opto/loopnode.hpp | 4 +- hotspot/src/share/vm/opto/loopopts.cpp | 191 +++++++++++ hotspot/src/share/vm/opto/memnode.cpp | 60 ++-- hotspot/src/share/vm/opto/phaseX.hpp | 9 +- .../loopopts/TestMoveStoresOutOfLoops.java | 310 ++++++++++++++++++ 6 files changed, 547 insertions(+), 29 deletions(-) create mode 100644 hotspot/test/compiler/loopopts/TestMoveStoresOutOfLoops.java diff --git a/hotspot/src/share/vm/opto/loopnode.cpp b/hotspot/src/share/vm/opto/loopnode.cpp index 0a44d7ede..7ab9bf893 100644 --- a/hotspot/src/share/vm/opto/loopnode.cpp +++ b/hotspot/src/share/vm/opto/loopnode.cpp @@ -1192,7 +1192,7 @@ void CountedLoopEndNode::dump_spec(outputStream *st) const { //============================================================================= //------------------------------is_member-------------------------------------- // Is 'l' a member of 'this'? -int IdealLoopTree::is_member( const IdealLoopTree *l ) const { +bool IdealLoopTree::is_member(const IdealLoopTree *l) const { while( l->_nest > _nest ) l = l->_parent; return l == this; } diff --git a/hotspot/src/share/vm/opto/loopnode.hpp b/hotspot/src/share/vm/opto/loopnode.hpp index 558b10504..21995dda6 100644 --- a/hotspot/src/share/vm/opto/loopnode.hpp +++ b/hotspot/src/share/vm/opto/loopnode.hpp @@ -371,7 +371,7 @@ public: { } // Is 'l' a member of 'this'? - int is_member( const IdealLoopTree *l ) const; // Test for nested membership + bool is_member(const IdealLoopTree *l) const; // Test for nested membership // Set loop nesting depth. Accumulate has_call bits. int set_nest( uint depth ); @@ -1075,6 +1075,8 @@ private: bool split_up( Node *n, Node *blk1, Node *blk2 ); void sink_use( Node *use, Node *post_loop ); Node *place_near_use( Node *useblock ) const; + Node* try_move_store_before_loop(Node* n, Node *n_ctrl); + void try_move_store_after_loop(Node* n); bool _created_loop_node; public: diff --git a/hotspot/src/share/vm/opto/loopopts.cpp b/hotspot/src/share/vm/opto/loopopts.cpp index 62273d86d..ec244e363 100644 --- a/hotspot/src/share/vm/opto/loopopts.cpp +++ b/hotspot/src/share/vm/opto/loopopts.cpp @@ -673,6 +673,190 @@ Node *PhaseIdealLoop::conditional_move( Node *region ) { return iff->in(1); } +#ifdef ASSERT +static void enqueue_cfg_uses(Node* m, Unique_Node_List& wq) { + for (DUIterator_Fast imax, i = m->fast_outs(imax); i < imax; i++) { + Node* u = m->fast_out(i); + if (u->is_CFG()) { + if (u->Opcode() == Op_NeverBranch) { + u = ((NeverBranchNode*)u)->proj_out(0); + enqueue_cfg_uses(u, wq); + } else { + wq.push(u); + } + } + } +} +#endif + +// Try moving a store out of a loop, right before the loop +Node* PhaseIdealLoop::try_move_store_before_loop(Node* n, Node *n_ctrl) { + // Store has to be first in the loop body + IdealLoopTree *n_loop = get_loop(n_ctrl); + if (n->is_Store() && n_loop != _ltree_root && n_loop->is_loop()) { + assert(n->in(0), "store should have control set"); + Node* address = n->in(MemNode::Address); + Node* value = n->in(MemNode::ValueIn); + Node* mem = n->in(MemNode::Memory); + IdealLoopTree* address_loop = get_loop(get_ctrl(address)); + IdealLoopTree* value_loop = get_loop(get_ctrl(value)); + + // - address and value must be loop invariant + // - memory must be a memory Phi for the loop + // - Store must be the only store on this memory slice in the + // loop: if there's another store following this one then value + // written at iteration i by the second store could be overwritten + // at iteration i+n by the first store: it's not safe to move the + // first store out of the loop + // - nothing must observe the Phi memory: it guarantees no read + // before the store and no early exit out of the loop + // With those conditions, we are also guaranteed the store post + // dominates the loop head. Otherwise there would be extra Phi + // involved between the loop's Phi and the store. + + if (!n_loop->is_member(address_loop) && + !n_loop->is_member(value_loop) && + mem->is_Phi() && mem->in(0) == n_loop->_head && + mem->outcnt() == 1 && + mem->in(LoopNode::LoopBackControl) == n) { + +#ifdef ASSERT + // Verify that store's control does post dominate loop entry and + // that there's no early exit of the loop before the store. + bool ctrl_ok = false; + { + // Follow control from loop head until n, we exit the loop or + // we reach the tail + ResourceMark rm; + Unique_Node_List wq; + wq.push(n_loop->_head); + assert(n_loop->_tail != NULL, "need a tail"); + for (uint next = 0; next < wq.size(); ++next) { + Node *m = wq.at(next); + if (m == n->in(0)) { + ctrl_ok = true; + continue; + } + assert(!has_ctrl(m), "should be CFG"); + if (!n_loop->is_member(get_loop(m)) || m == n_loop->_tail) { + ctrl_ok = false; + break; + } + enqueue_cfg_uses(m, wq); + } + } + assert(ctrl_ok, "bad control"); +#endif + + // move the Store + _igvn.replace_input_of(mem, LoopNode::LoopBackControl, mem); + _igvn.replace_input_of(n, 0, n_loop->_head->in(LoopNode::EntryControl)); + _igvn.replace_input_of(n, MemNode::Memory, mem->in(LoopNode::EntryControl)); + // Disconnect the phi now. An empty phi can confuse other + // optimizations in this pass of loop opts. + _igvn.replace_node(mem, mem->in(LoopNode::EntryControl)); + n_loop->_body.yank(mem); + + IdealLoopTree* new_loop = get_loop(n->in(0)); + set_ctrl_and_loop(n, n->in(0)); + + return n; + } + } + return NULL; +} + +// Try moving a store out of a loop, right after the loop +void PhaseIdealLoop::try_move_store_after_loop(Node* n) { + if (n->is_Store()) { + assert(n->in(0), "store should have control set"); + Node *n_ctrl = get_ctrl(n); + IdealLoopTree *n_loop = get_loop(n_ctrl); + // Store must be in a loop + if (n_loop != _ltree_root && !n_loop->_irreducible) { + Node* address = n->in(MemNode::Address); + Node* value = n->in(MemNode::ValueIn); + IdealLoopTree* address_loop = get_loop(get_ctrl(address)); + // address must be loop invariant + if (!n_loop->is_member(address_loop)) { + // Store must be last on this memory slice in the loop and + // nothing in the loop must observe it + Node* phi = NULL; + for (DUIterator_Fast imax, i = n->fast_outs(imax); i < imax; i++) { + Node* u = n->fast_out(i); + if (has_ctrl(u)) { // control use? + IdealLoopTree *u_loop = get_loop(get_ctrl(u)); + if (!n_loop->is_member(u_loop)) { + continue; + } + if (u->is_Phi() && u->in(0) == n_loop->_head) { + assert(_igvn.type(u) == Type::MEMORY, "bad phi"); + assert(phi == NULL, "already found"); + phi = u; + continue; + } + } + phi = NULL; + break; + } + if (phi != NULL) { + // Nothing in the loop before the store (next iteration) + // must observe the stored value + bool mem_ok = true; + { + ResourceMark rm; + Unique_Node_List wq; + wq.push(phi); + for (uint next = 0; next < wq.size() && mem_ok; ++next) { + Node *m = wq.at(next); + for (DUIterator_Fast imax, i = m->fast_outs(imax); i < imax && mem_ok; i++) { + Node* u = m->fast_out(i); + if (u->is_Store() || u->is_Phi()) { + if (u != n) { + wq.push(u); + mem_ok = (wq.size() <= 10); + } + } else { + mem_ok = false; + break; + } + } + } + } + if (mem_ok) { + // Move the store out of the loop if the LCA of all + // users (except for the phi) is outside the loop. + Node* hook = new (C) Node(1); + _igvn.rehash_node_delayed(phi); + int count = phi->replace_edge(n, hook); + assert(count > 0, "inconsistent phi"); + + // Compute latest point this store can go + Node* lca = get_late_ctrl(n, get_ctrl(n)); + if (n_loop->is_member(get_loop(lca))) { + // LCA is in the loop - bail out + _igvn.replace_node(hook, n); + return; + } + + // Move store out of the loop + _igvn.replace_node(hook, n->in(MemNode::Memory)); + _igvn.replace_input_of(n, 0, lca); + set_ctrl_and_loop(n, lca); + + // Disconnect the phi now. An empty phi can confuse other + // optimizations in this pass of loop opts.. + if (phi->in(LoopNode::LoopBackControl) == phi) { + _igvn.replace_node(phi, phi->in(LoopNode::EntryControl)); + n_loop->_body.yank(phi); + } + } + } + } + } + } +} + //------------------------------split_if_with_blocks_pre----------------------- // Do the real work in a non-recursive function. Data nodes want to be // cloned in the pre-order so they can feed each other nicely. @@ -703,6 +887,11 @@ Node *PhaseIdealLoop::split_if_with_blocks_pre( Node *n ) { Node *n_ctrl = get_ctrl(n); if( !n_ctrl ) return n; // Dead node + Node* res = try_move_store_before_loop(n, n_ctrl); + if (res != NULL) { + return n; + } + // Attempt to remix address expressions for loop invariants Node *m = remix_address_expressions( n ); if( m ) return m; @@ -1057,6 +1246,8 @@ void PhaseIdealLoop::split_if_with_blocks_post( Node *n ) { } } + try_move_store_after_loop(n); + // Check for Opaque2's who's loop has disappeared - who's input is in the // same loop nest as their output. Remove 'em, they are no longer useful. if( n_op == Op_Opaque2 && diff --git a/hotspot/src/share/vm/opto/memnode.cpp b/hotspot/src/share/vm/opto/memnode.cpp index 3ecbe1ce0..1bab75927 100644 --- a/hotspot/src/share/vm/opto/memnode.cpp +++ b/hotspot/src/share/vm/opto/memnode.cpp @@ -2313,33 +2313,41 @@ Node *StoreNode::Ideal(PhaseGVN *phase, bool can_reshape) { // unsafe if I have intervening uses... Also disallowed for StoreCM // since they must follow each StoreP operation. Redundant StoreCMs // are eliminated just before matching in final_graph_reshape. - if (mem->is_Store() && mem->in(MemNode::Address)->eqv_uncast(address) && - mem->Opcode() != Op_StoreCM) { - // Looking at a dead closed cycle of memory? - assert(mem != mem->in(MemNode::Memory), "dead loop in StoreNode::Ideal"); - - assert(Opcode() == mem->Opcode() || - phase->C->get_alias_index(adr_type()) == Compile::AliasIdxRaw || - (is_mismatched_access() || mem->as_Store()->is_mismatched_access()), - "no mismatched stores, except on raw memory"); - - if (mem->outcnt() == 1 && // check for intervening uses - mem->as_Store()->memory_size() <= this->memory_size()) { - // If anybody other than 'this' uses 'mem', we cannot fold 'mem' away. - // For example, 'mem' might be the final state at a conditional return. - // Or, 'mem' might be used by some node which is live at the same time - // 'this' is live, which might be unschedulable. So, require exactly - // ONE user, the 'this' store, until such time as we clone 'mem' for - // each of 'mem's uses (thus making the exactly-1-user-rule hold true). - if (can_reshape) { // (%%% is this an anachronism?) - set_req_X(MemNode::Memory, mem->in(MemNode::Memory), - phase->is_IterGVN()); - } else { - // It's OK to do this in the parser, since DU info is always accurate, - // and the parser always refers to nodes via SafePointNode maps. - set_req(MemNode::Memory, mem->in(MemNode::Memory)); + { + Node* st = mem; + // If Store 'st' has more than one use, we cannot fold 'st' away. + // For example, 'st' might be the final state at a conditional + // return. Or, 'st' might be used by some node which is live at + // the same time 'st' is live, which might be unschedulable. So, + // require exactly ONE user until such time as we clone 'mem' for + // each of 'mem's uses (thus making the exactly-1-user-rule hold + // true). + while (st->is_Store() && st->outcnt() == 1 && st->Opcode() != Op_StoreCM) { + // Looking at a dead closed cycle of memory? + assert(st != st->in(MemNode::Memory), "dead loop in StoreNode::Ideal"); + assert(Opcode() == st->Opcode() || + st->Opcode() == Op_StoreVector || + Opcode() == Op_StoreVector || + phase->C->get_alias_index(adr_type()) == Compile::AliasIdxRaw || + (Opcode() == Op_StoreL && st->Opcode() == Op_StoreI) || // expanded ClearArrayNode + (Opcode() == Op_StoreI && st->Opcode() == Op_StoreL) || // initialization by arraycopy + (is_mismatched_access() || mem->as_Store()->is_mismatched_access()), + err_msg_res("no mismatched stores, except on raw memory: %s %s", NodeClassNames[Opcode()], NodeClassNames[st->Opcode()])); + + if (st->in(MemNode::Address)->eqv_uncast(address) && + st->as_Store()->memory_size() <= this->memory_size()) { + Node* use = st->raw_out(0); + phase->igvn_rehash_node_delayed(use); + if (can_reshape) { + use->set_req_X(MemNode::Memory, st->in(MemNode::Memory), phase->is_IterGVN()); + } else { + // It's OK to do this in the parser, since DU info is always accurate, + // and the parser always refers to nodes via SafePointNode maps. + use->set_req(MemNode::Memory, st->in(MemNode::Memory)); + } + return this; } - return this; + st = st->in(MemNode::Memory); } } diff --git a/hotspot/src/share/vm/opto/phaseX.hpp b/hotspot/src/share/vm/opto/phaseX.hpp index 332b1175d..852a1c295 100644 --- a/hotspot/src/share/vm/opto/phaseX.hpp +++ b/hotspot/src/share/vm/opto/phaseX.hpp @@ -327,6 +327,9 @@ public: const Type* limit_type) const { ShouldNotCallThis(); return NULL; } + // Delayed node rehash if this is an IGVN phase + virtual void igvn_rehash_node_delayed(Node* n) {} + virtual PhaseIterGVN *is_IterGVN() { return 0; } #ifndef PRODUCT @@ -495,7 +498,11 @@ public: _worklist.push(n); } - // Replace ith edge of "n" with "in" + void igvn_rehash_node_delayed(Node* n) { + rehash_node_delayed(n); + } + + // Replace ith edge of "n" with "in" void replace_input_of(Node* n, int i, Node* in) { rehash_node_delayed(n); n->set_req(i, in); diff --git a/hotspot/test/compiler/loopopts/TestMoveStoresOutOfLoops.java b/hotspot/test/compiler/loopopts/TestMoveStoresOutOfLoops.java new file mode 100644 index 000000000..4eea5d5e4 --- /dev/null +++ b/hotspot/test/compiler/loopopts/TestMoveStoresOutOfLoops.java @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. 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. + * + */ + +/** + * @test + * @bug 8080289 + * @summary Sink stores out of loops if possible + * @run main/othervm -XX:-UseOnStackReplacement -XX:-BackgroundCompilation -XX:+PrintCompilation -XX:CompileCommand=dontinline,TestMoveStoresOutOfLoops::test* TestMoveStoresOutOfLoops + * + */ + +import java.lang.reflect.*; +import java.util.*; +import java.util.function.*; + +public class TestMoveStoresOutOfLoops { + + private static long[] array = new long[10]; + private static long[] array2 = new long[10]; + private static boolean[] array3 = new boolean[1000]; + private static byte[] byte_array = new byte[10]; + + // Array store should be moved out of the loop, value stored + // should be 999, the loop should be eliminated + static void test_after_1(int idx) { + for (int i = 0; i < 1000; i++) { + array[idx] = i; + } + } + + // Array store can't be moved out of loop because of following + // non loop invariant array access + static void test_after_2(int idx) { + for (int i = 0; i < 1000; i++) { + array[idx] = i; + array2[i%10] = i; + } + } + + // Array store can't be moved out of loop because of following + // use + static void test_after_3(int idx) { + for (int i = 0; i < 1000; i++) { + array[idx] = i; + if (array[0] == -1) { + break; + } + } + } + + // Array store can't be moved out of loop because of preceding + // use + static void test_after_4(int idx) { + for (int i = 0; i < 1000; i++) { + if (array[0] == -2) { + break; + } + array[idx] = i; + } + } + + // All array stores should be moved out of the loop, one after + // the other + static void test_after_5(int idx) { + for (int i = 0; i < 1000; i++) { + array[idx] = i; + array[idx+1] = i; + array[idx+2] = i; + array[idx+3] = i; + array[idx+4] = i; + array[idx+5] = i; + } + } + + // Array store can be moved after the loop but needs to be + // cloned on both exit paths + static void test_after_6(int idx) { + for (int i = 0; i < 1000; i++) { + array[idx] = i; + if (array3[i]) { + return; + } + } + } + + // Optimize out redundant stores + static void test_stores_1(int ignored) { + array[0] = 0; + array[1] = 1; + array[2] = 2; + array[0] = 0; + array[1] = 1; + array[2] = 2; + } + + static void test_stores_2(int idx) { + array[idx+0] = 0; + array[idx+1] = 1; + array[idx+2] = 2; + array[idx+0] = 0; + array[idx+1] = 1; + array[idx+2] = 2; + } + + static void test_stores_3(int idx) { + byte_array[idx+0] = 0; + byte_array[idx+1] = 1; + byte_array[idx+2] = 2; + byte_array[idx+0] = 0; + byte_array[idx+1] = 1; + byte_array[idx+2] = 2; + } + + // Array store can be moved out of the loop before the loop header + static void test_before_1(int idx) { + for (int i = 0; i < 1000; i++) { + array[idx] = 999; + } + } + + // Array store can't be moved out of the loop before the loop + // header because there's more than one store on this slice + static void test_before_2(int idx) { + for (int i = 0; i < 1000; i++) { + array[idx] = 999; + array[i%2] = 0; + } + } + + // Array store can't be moved out of the loop before the loop + // header because of use before store + static int test_before_3(int idx) { + int res = 0; + for (int i = 0; i < 1000; i++) { + res += array[i%10]; + array[idx] = 999; + } + return res; + } + + // Array store can't be moved out of the loop before the loop + // header because of possible early exit + static void test_before_4(int idx) { + for (int i = 0; i < 1000; i++) { + if (idx / (i+1) > 0) { + return; + } + array[idx] = 999; + } + } + + // Array store can't be moved out of the loop before the loop + // header because it doesn't postdominate the loop head + static void test_before_5(int idx) { + for (int i = 0; i < 1000; i++) { + if (i % 2 == 0) { + array[idx] = 999; + } + } + } + + // Array store can be moved out of the loop before the loop header + static int test_before_6(int idx) { + int res = 0; + for (int i = 0; i < 1000; i++) { + if (i%2 == 1) { + res *= 2; + } else { + res++; + } + array[idx] = 999; + } + return res; + } + + final HashMap<String,Method> tests = new HashMap<>(); + { + for (Method m : this.getClass().getDeclaredMethods()) { + if (m.getName().matches("test_(before|after|stores)_[0-9]+")) { + assert(Modifier.isStatic(m.getModifiers())) : m; + tests.put(m.getName(), m); + } + } + } + + boolean success = true; + void doTest(String name, Runnable init, Function<String, Boolean> check) throws Exception { + Method m = tests.get(name); + for (int i = 0; i < 20000; i++) { + init.run(); + m.invoke(null, 0); + success = success && check.apply(name); + if (!success) { + break; + } + } + } + + static void array_init() { + array[0] = -1; + } + + static boolean array_check(String name) { + boolean success = true; + if (array[0] != 999) { + success = false; + System.out.println(name + " failed: array[0] = " + array[0]); + } + return success; + } + + static void array_init2() { + for (int i = 0; i < 6; i++) { + array[i] = -1; + } + } + + static boolean array_check2(String name) { + boolean success = true; + for (int i = 0; i < 6; i++) { + if (array[i] != 999) { + success = false; + System.out.println(name + " failed: array[" + i + "] = " + array[i]); + } + } + return success; + } + + static void array_init3() { + for (int i = 0; i < 3; i++) { + array[i] = -1; + } + } + + static boolean array_check3(String name) { + boolean success = true; + for (int i = 0; i < 3; i++) { + if (array[i] != i) { + success = false; + System.out.println(name + " failed: array[" + i + "] = " + array[i]); + } + } + return success; + } + + static void array_init4() { + for (int i = 0; i < 3; i++) { + byte_array[i] = -1; + } + } + + static boolean array_check4(String name) { + boolean success = true; + for (int i = 0; i < 3; i++) { + if (byte_array[i] != i) { + success = false; + System.out.println(name + " failed: byte_array[" + i + "] = " + byte_array[i]); + } + } + return success; + } + + static public void main(String[] args) throws Exception { + TestMoveStoresOutOfLoops test = new TestMoveStoresOutOfLoops(); + test.doTest("test_after_1", TestMoveStoresOutOfLoops::array_init, TestMoveStoresOutOfLoops::array_check); + test.doTest("test_after_2", TestMoveStoresOutOfLoops::array_init, TestMoveStoresOutOfLoops::array_check); + test.doTest("test_after_3", TestMoveStoresOutOfLoops::array_init, TestMoveStoresOutOfLoops::array_check); + test.doTest("test_after_4", TestMoveStoresOutOfLoops::array_init, TestMoveStoresOutOfLoops::array_check); + test.doTest("test_after_5", TestMoveStoresOutOfLoops::array_init2, TestMoveStoresOutOfLoops::array_check2); + test.doTest("test_after_6", TestMoveStoresOutOfLoops::array_init, TestMoveStoresOutOfLoops::array_check); + array3[999] = true; + test.doTest("test_after_6", TestMoveStoresOutOfLoops::array_init, TestMoveStoresOutOfLoops::array_check); + + test.doTest("test_stores_1", TestMoveStoresOutOfLoops::array_init3, TestMoveStoresOutOfLoops::array_check3); + test.doTest("test_stores_2", TestMoveStoresOutOfLoops::array_init3, TestMoveStoresOutOfLoops::array_check3); + test.doTest("test_stores_3", TestMoveStoresOutOfLoops::array_init4, TestMoveStoresOutOfLoops::array_check4); + + test.doTest("test_before_1", TestMoveStoresOutOfLoops::array_init, TestMoveStoresOutOfLoops::array_check); + test.doTest("test_before_2", TestMoveStoresOutOfLoops::array_init, TestMoveStoresOutOfLoops::array_check); + test.doTest("test_before_3", TestMoveStoresOutOfLoops::array_init, TestMoveStoresOutOfLoops::array_check); + test.doTest("test_before_4", TestMoveStoresOutOfLoops::array_init, TestMoveStoresOutOfLoops::array_check); + test.doTest("test_before_5", TestMoveStoresOutOfLoops::array_init, TestMoveStoresOutOfLoops::array_check); + test.doTest("test_before_6", TestMoveStoresOutOfLoops::array_init, TestMoveStoresOutOfLoops::array_check); + + if (!test.success) { + throw new RuntimeException("Some tests failed"); + } + } +} -- 2.19.1
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.
浙ICP备2022010568号-2