8323274: C2: array load may float above range check

Reviewed-by: epeter, thartmann
This commit is contained in:
Roland Westrelin 2024-02-22 11:07:13 +00:00
parent cc1e216eb9
commit 4406915ebc
9 changed files with 672 additions and 4 deletions

@ -152,7 +152,10 @@ int ArrayCopyNode::get_count(PhaseGVN *phase) const {
}
Node* ArrayCopyNode::load(BarrierSetC2* bs, PhaseGVN *phase, Node*& ctl, MergeMemNode* mem, Node* adr, const TypePtr* adr_type, const Type *type, BasicType bt) {
DecoratorSet decorators = C2_READ_ACCESS | C2_CONTROL_DEPENDENT_LOAD | IN_HEAP | C2_ARRAY_COPY;
// Pin the load: if this is an array load, it's going to be dependent on a condition that's not a range check for that
// access. If that condition is replaced by an identical dominating one, then an unpinned load would risk floating
// above runtime checks that guarantee it is within bounds.
DecoratorSet decorators = C2_READ_ACCESS | C2_CONTROL_DEPENDENT_LOAD | IN_HEAP | C2_ARRAY_COPY | C2_UNKNOWN_CONTROL_LOAD;
C2AccessValuePtr addr(adr, adr_type);
C2OptAccess access(*phase, ctl, mem, decorators, bt, adr->in(AddPNode::Base), addr);
Node* res = bs->load_at(access, type);

@ -1744,6 +1744,8 @@ public:
void update_addp_chain_base(Node* x, Node* old_base, Node* new_base);
bool can_move_to_inner_loop(Node* n, LoopNode* n_loop, Node* x);
void pin_array_access_nodes_dependent_on(Node* ctrl);
};

@ -1490,7 +1490,16 @@ void PhaseIdealLoop::split_if_with_blocks_post(Node *n) {
// Replace the dominated test with an obvious true or false.
// Place it on the IGVN worklist for later cleanup.
C->set_major_progress();
dominated_by(prevdom->as_IfProj(), n->as_If());
// Split if: pin array accesses that are control dependent on a range check and moved to a regular if,
// to prevent an array load from floating above its range check. There are three cases:
// 1. Move from RangeCheck "a" to RangeCheck "b": don't need to pin. If we ever remove b, then we pin
// all its array accesses at that point.
// 2. We move from RangeCheck "a" to regular if "b": need to pin. If we ever remove b, then its array
// accesses would start to float, since we don't pin at that point.
// 3. If we move from regular if: don't pin. All array accesses are already assumed to be pinned.
bool pin_array_access_nodes = n->Opcode() == Op_RangeCheck &&
prevdom->in(0)->Opcode() != Op_RangeCheck;
dominated_by(prevdom->as_IfProj(), n->as_If(), false, pin_array_access_nodes);
DEBUG_ONLY( if (VerifyLoopOptimizations) { verify(); } );
return;
}
@ -1664,7 +1673,20 @@ void PhaseIdealLoop::try_sink_out_of_loop(Node* n) {
// n has a control input inside a loop but get_ctrl() is member of an outer loop. This could happen, for example,
// for Div nodes inside a loop (control input inside loop) without a use except for an UCT (outside the loop).
// Rewire control of n to right outside of the loop, regardless if its input(s) are later sunk or not.
_igvn.replace_input_of(n, 0, place_outside_loop(n_ctrl, loop_ctrl));
Node* maybe_pinned_n = n;
Node* outside_ctrl = place_outside_loop(n_ctrl, loop_ctrl);
if (n->depends_only_on_test()) {
Node* pinned_clone = n->pin_array_access_node();
if (pinned_clone != nullptr) {
// Pin array access nodes: if this is an array load, it's going to be dependent on a condition that's not a
// range check for that access. If that condition is replaced by an identical dominating one, then an
// unpinned load would risk floating above its range check.
register_new_node(pinned_clone, n_ctrl);
maybe_pinned_n = pinned_clone;
_igvn.replace_node(n, pinned_clone);
}
}
_igvn.replace_input_of(maybe_pinned_n, 0, outside_ctrl);
}
}
if (n_loop != _ltree_root && n->outcnt() > 1) {
@ -1678,7 +1700,16 @@ void PhaseIdealLoop::try_sink_out_of_loop(Node* n) {
for (DUIterator_Last jmin, j = n->last_outs(jmin); j >= jmin;) {
Node* u = n->last_out(j); // Clone private computation per use
_igvn.rehash_node_delayed(u);
Node* x = n->clone(); // Clone computation
Node* x = nullptr;
if (n->depends_only_on_test()) {
// Pin array access nodes: if this is an array load, it's going to be dependent on a condition that's not a
// range check for that access. If that condition is replaced by an identical dominating one, then an
// unpinned load would risk floating above its range check.
x = n->pin_array_access_node();
}
if (x == nullptr) {
x = n->clone();
}
Node* x_ctrl = nullptr;
if (u->is_Phi()) {
// Replace all uses of normal nodes. Replace Phi uses
@ -2228,6 +2259,20 @@ void PhaseIdealLoop::clone_loop_handle_data_uses(Node* old, Node_List &old_new,
// We notify all uses of old, including use, and the indirect uses,
// that may now be optimized because we have replaced old with phi.
_igvn.add_users_to_worklist(old);
if (idx == 0 &&
use->depends_only_on_test()) {
Node* pinned_clone = use->pin_array_access_node();
if (pinned_clone != nullptr) {
// Pin array access nodes: control is updated here to a region. If, after some transformations, only one path
// into the region is left, an array load could become dependent on a condition that's not a range check for
// that access. If that condition is replaced by an identical dominating one, then an unpinned load would risk
// floating above its range check.
pinned_clone->set_req(0, phi);
register_new_node(pinned_clone, get_ctrl(use));
_igvn.replace_node(use, pinned_clone);
continue;
}
}
_igvn.replace_input_of(use, idx, phi);
if( use->_idx >= new_counter ) { // If updating new phis
// Not needed for correctness, but prevents a weak assert
@ -3863,6 +3908,19 @@ bool PhaseIdealLoop::partial_peel( IdealLoopTree *loop, Node_List &old_new ) {
if (!n->is_CFG() && n->in(0) != nullptr &&
not_peel.test(n->_idx) && peel.test(n->in(0)->_idx)) {
Node* n_clone = old_new[n->_idx];
if (n_clone->depends_only_on_test()) {
// Pin array access nodes: control is updated here to the loop head. If, after some transformations, the
// backedge is removed, an array load could become dependent on a condition that's not a range check for that
// access. If that condition is replaced by an identical dominating one, then an unpinned load would risk
// floating above its range check.
Node* pinned_clone = n_clone->pin_array_access_node();
if (pinned_clone != nullptr) {
register_new_node(pinned_clone, get_ctrl(n_clone));
old_new.map(n->_idx, pinned_clone);
_igvn.replace_node(n_clone, pinned_clone);
n_clone = pinned_clone;
}
}
_igvn.replace_input_of(n_clone, 0, new_head_clone);
}
}

@ -727,6 +727,14 @@ void PhaseIdealLoop::do_split_if(Node* iff, RegionNode** new_false_region, Regio
} // End of while merge point has phis
_igvn.remove_dead_node(region);
if (iff->Opcode() == Op_RangeCheck) {
// Pin array access nodes: control is updated here to a region. If, after some transformations, only one path
// into the region is left, an array load could become dependent on a condition that's not a range check for
// that access. If that condition is replaced by an identical dominating one, then an unpinned load would risk
// floating above its range check.
pin_array_access_nodes_dependent_on(new_true);
pin_array_access_nodes_dependent_on(new_false);
}
if (new_false_region != nullptr) {
*new_false_region = new_false;
@ -737,3 +745,18 @@ void PhaseIdealLoop::do_split_if(Node* iff, RegionNode** new_false_region, Regio
DEBUG_ONLY( if (VerifyLoopOptimizations) { verify(); } );
}
void PhaseIdealLoop::pin_array_access_nodes_dependent_on(Node* ctrl) {
for (DUIterator i = ctrl->outs(); ctrl->has_out(i); i++) {
Node* use = ctrl->out(i);
if (!use->depends_only_on_test()) {
continue;
}
Node* pinned_clone = use->pin_array_access_node();
if (pinned_clone != nullptr) {
register_new_node(pinned_clone, get_ctrl(use));
_igvn.replace_node(use, pinned_clone);
--i;
}
}
}

@ -0,0 +1,130 @@
/*
* Copyright (c) 2024, 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.
*/
/*
* @test
* @bug 8323274
* @summary partial peeling loop can cause an array load to become dependent on a test other than its range check
* @run main/othervm -XX:-UseOnStackReplacement -XX:-TieredCompilation -XX:-BackgroundCompilation TestArrayAccessAboveRCAfterPartialPeeling
*/
public class TestArrayAccessAboveRCAfterPartialPeeling {
private static volatile int volatileField;
public static void main(String[] args) {
int[] array = new int[100];
for (int i = 0; i < 20_000; i++) {
test(array, 2, true, 1);
test(array, 2, false, 1);
inlined(array, 2, 42, true, 42, 1, 1);
inlined(array, 2, 42, false, 42, 1, 1);
}
try {
test(array, 2, true, -1);
} catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) {
}
}
private static int test(int[] array, int k, boolean flag, int j) {
int l;
for (l = 1; l < 2; l *= 2) {
}
int m;
for (m = 0; m < 42; m += l) {
}
int n;
for (n = 0; n < 10; n += m/42) {
}
return inlined(array, k, l, flag, m, n/10, j);
}
private static int inlined(int[] array, int k, int l, boolean flag, int m, int n, int j) {
if (array == null) {
}
int[] otherArray = new int[100];
int i = 0;
int v = 0;
if (k == m) {
}
if (flag) {
v += array[j];
v += otherArray[i];
for (; ; ) {
synchronized (new Object()) {
}
if (j >= 100) {
break;
}
if (k == 42) {
}
v += array[j];
v += otherArray[i];
if (i >= n) {
otherArray[i] = v;
}
v += array[j];
if (l == 2) {
break;
}
i++;
j *= 2;
volatileField = 42;
k = 2;
l = 42;
}
} else {
v += array[j];
v += otherArray[i];
for (; ; ) {
synchronized (new Object()) {
}
if (j >= 100) {
break;
}
if (k == 42) {
}
v += array[j];
v += otherArray[i];
if (i >= n) {
otherArray[i] = v;
}
v += array[j];
if (l == 2) {
break;
}
i++;
j *= 2;
volatileField = 42;
k = 2;
l = 42;
}
}
return v;
}
}

@ -0,0 +1,132 @@
/*
* Copyright (c) 2024, 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.
*/
/*
* @test
* @bug 8323274
* @summary sinking an array load out of loop can cause it to become dependent on a test other than its range check
* @run main/othervm -XX:-UseOnStackReplacement -XX:-TieredCompilation -XX:-BackgroundCompilation TestArrayAccessAboveRCAfterSinking
*/
import java.util.Arrays;
public class TestArrayAccessAboveRCAfterSinking {
public static void main(String[] args) {
boolean[] allFalse = new boolean[100];
boolean[] allTrue = new boolean[100];
Arrays.fill(allTrue, true);
int[] array = new int[100];
for (int i = 0; i < 20_000; i++) {
test1(allTrue, array, 0, true, 0);
test1(allTrue, array, 0, false, 0);
inlined1(allFalse, array, 2, 0);
inlined1(allFalse, array, 42, 0);
inlined1(allTrue, array, 2, 0);
test2(allTrue, array, 0, true, 0);
test2(allTrue, array, 0, false, 0);
inlined2(allFalse, array, 2, 0);
inlined2(allFalse, array, 42, 0);
inlined2(allTrue, array, 2, 0);
}
try {
test1(allTrue, array, -1, true, 0);
} catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) {
}
try {
test2(allTrue, array, -1, true, 0);
} catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) {
}
}
private static int test1(boolean[] flags, int[] array, int k, boolean flag, int v) {
if (flags == null) {
}
if (array == null) {
}
int j = 1;
for (; j < 2; j *= 2) {
}
int i;
for (i = 0; i < 10; i += j) {
}
if (flags[i - 10]) {
if (flag) {
return inlined1(flags, array, j, k);
} else {
return inlined1(flags, array, j, k) + v;
}
}
return 0;
}
private static int inlined1(boolean[] flags, int[] array, int j, int k) {
for (int i = 0; i < 100; i++) {
final boolean flag = flags[i & (j - 3)];
int v = array[i + k];
if (flag) {
return v;
}
if (j + (i & (j - 2)) == 2) {
break;
}
}
return 0;
}
private static int test2(boolean[] flags, int[] array, int k, boolean flag, int v) {
if (flags == null) {
}
if (array == null) {
}
int j = 1;
for (; j < 2; j *= 2) {
}
int i;
for (i = 0; i < 10; i += j) {
}
if (flags[i - 10]) {
if (flag) {
return inlined2(flags, array, j, k);
} else {
return inlined2(flags, array, j, k) + v;
}
}
return 0;
}
private static int inlined2(boolean[] flags, int[] array, int j, int k) {
for (int i = 0; i < 100; i++) {
int v = array[i + k];
if (flags[i & (j - 3)]) {
return v;
}
if (j + (i & (j - 2)) == 2) {
break;
}
}
return 0;
}
}

@ -0,0 +1,161 @@
/*
* Copyright (c) 2024, 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.
*/
/*
* @test
* @bug 8323274
* @summary split if can cause an array load to become dependent on a test other than its range check
* @run main/othervm -XX:-TieredCompilation -XX:-BackgroundCompilation TestArrayAccessAboveRCAfterSplitIf
*/
public class TestArrayAccessAboveRCAfterSplitIf {
private static volatile int volatileField;
public static void main(String[] args) {
int[] array = new int[1000];
for (int i = 0; i < 20_000; i++) {
test1(array, array, 0, 2, true);
inlined1(42, array, array, 0, 2, 10, true);
inlined1(2, array, array, 0, 2, 10, true);
inlined1(42, array, array, 0, 2, 10, false);
inlined1(2, array, array, 0, 2, 10, false);
test2(array, array, 0, 2, true);
inlined2(42, array, array, 0, 2, 10, true);
inlined2(2, array, array, 0, 2, 10, true);
inlined2(42, array, array, 0, 2, 10, false);
inlined2(2, array, array, 0, 2, 10, false);
}
try {
test1(array, array, -1, 2, true);
} catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) {
}
try {
test2(array, array, -1, 2, true);
} catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) {
}
}
private static int test1(int[] array1, int[] array2, int i, int l, boolean flag) {
for (int j = 0; j < 10; j++) {
}
int k;
for (k = 1; k < 2; k *= 2) {
}
int m;
for (m = 0; m < 10; m+=k) {
}
return inlined1(k, array1, array2, i, l, m, flag);
}
private static int inlined1(int k, int[] array1, int[] array2, int i, int l, int m, boolean flag) {
int v;
int[] array;
if (array1 == null) {
}
if (l == 10) {
}
if (flag) {
if (k == 2) {
v = array1[i];
array = array1;
if (l == m) {
}
} else {
v = array2[i];
array = array2;
}
v += array[i];
v += array2[i];
} else {
if (k == 2) {
v = array1[i];
array = array1;
if (l == m) {
}
} else {
v = array2[i];
array = array2;
}
v += array[i];
v += array2[i];
}
return v;
}
private static int test2(int[] array1, int[] array2, int i, int l, boolean flag) {
for (int j = 0; j < 10; j++) {
}
int k;
for (k = 1; k < 2; k *= 2) {
}
int m;
for (m = 0; m < 10; m+=k) {
}
return inlined2(k, array1, array2, i, l, m, flag);
}
private static int inlined2(int k, int[] array1, int[] array2, int i, int l, int m, boolean flag) {
int v;
int[] array;
if (array1 == null) {
}
if (l == 10) {
}
if (flag) {
if (k == 2) {
v = array1[i];
array = array1;
if (l == m) {
}
} else {
v = array2[i];
array = array2;
}
if (Integer.compareUnsigned(i, array.length) >= 0) {
}
v += array[i];
v += array2[i];
} else {
if (k == 2) {
v = array1[i];
array = array1;
if (l == m) {
}
} else {
v = array2[i];
array = array2;
}
if (Integer.compareUnsigned(i, array.length) >= 0) {
}
v += array[i];
v += array2[i];
}
return v;
}
}

@ -0,0 +1,103 @@
/*
* Copyright (c) 2024, 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.
*/
/*
* @test
* @bug 8323274
* @summary loop unswitching can cause an array load to become dependent on a test other than its range check
* @run main/othervm -XX:-TieredCompilation -XX:-BackgroundCompilation -XX:CompileOnly=TestArrayAccessAboveRCAfterUnswitching::test
* -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:StressSeed=148059521 TestArrayAccessAboveRCAfterUnswitching
* @run main/othervm -XX:-TieredCompilation -XX:-BackgroundCompilation -XX:CompileOnly=TestArrayAccessAboveRCAfterUnswitching::test
* -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM TestArrayAccessAboveRCAfterUnswitching
*/
import java.util.Arrays;
public class TestArrayAccessAboveRCAfterUnswitching {
private static int field;
public static void main(String[] args) {
int[] array = new int[1000];
boolean[] allFalse = new boolean[1000];
boolean[] allTrue = new boolean[1000];
Arrays.fill(allTrue, true);
for (int i = 0; i < 20_000; i++) {
inlined(array, allFalse, 42, 2, 2, 0);
inlined(array, allFalse, 2, 42, 2, 0);
inlined(array, allFalse, 2, 2, 2, 0);
inlined(array, allFalse, 2, 2, 42, 0);
inlined(array, allTrue, 2, 2, 2, 0);
test(array, allTrue, 0);
}
try {
test(array, allTrue, -1);
} catch (ArrayIndexOutOfBoundsException aioobe) {
}
}
private static int test(int[] array, boolean[] flags, int start) {
if (flags == null) {
}
if (array == null) {
}
int j = 1;
for (; j < 2; j *= 2) {
}
int k = 1;
for (; k < 2; k *= 2) {
}
int l = 1;
for (; l < 2; l *= 2) {
}
int i;
for (i = 0; i < 10; i += l) {
}
if (flags[i - 10]) {
return inlined(array, flags, j, k, l, start);
}
return 0;
}
private static int inlined(int[] array, boolean[] flags, int j, int k, int l, int start) {
for (int i = 0; i < 100; i++) {
final boolean flag = flags[i & (j - 3)];
int v = array[(i + start) & (j - 3)];
if (flag) {
return v;
}
if (j != 2) {
field = v;
} else {
if (k != 2) {
field = 42;
} else {
if (l == 2) {
break;
}
}
}
}
return 0;
}
}

@ -0,0 +1,56 @@
/*
* Copyright (c) 2024, 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.
*/
/*
* @test
* @bug 8323274
* @summary converting an array copy to a series of loads/stores add loads that can float
* @run main/othervm -XX:-UseOnStackReplacement -XX:-TieredCompilation -XX:-BackgroundCompilation TestArrayAccessAboveRCForArrayCopyLoad
*/
public class TestArrayAccessAboveRCForArrayCopyLoad {
public static void main(String[] args) {
int[] array = new int[10];
for (int i = 0; i < 20_000; i++) {
test(array, 0, array, 1, false);
test(array, 0, array, 1, true);
}
try {
test(array, -1, array, 0, true);
} catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) {
}
}
private static void test(int[] src, int srcPos, int[] dst, int dstPos, boolean flag) {
if (src == null) {
}
if (srcPos < dstPos) {
if (flag) {
System.arraycopy(src, srcPos, dst, dstPos, 2);
} else {
System.arraycopy(src, srcPos, dst, dstPos, 2);
}
}
}
}