Skip to content

Commit 87593c1

Browse files
committed
[GR-54925] Add intrinsic for TStringOps#runByteSwap.
PullRequest: graal/21688
2 parents 7c186bb + 26b16fa commit 87593c1

File tree

18 files changed

+807
-107
lines changed

18 files changed

+807
-107
lines changed

compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/ArrayUtilsTest.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2025, 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
@@ -27,8 +27,17 @@
2727
import java.util.ArrayList;
2828

2929
import jdk.graal.compiler.core.test.GraalCompilerTest;
30+
import jdk.graal.compiler.graph.Node;
31+
import jdk.graal.compiler.nodes.StructuredGraph;
32+
import jdk.graal.compiler.nodes.ValueNode;
33+
import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext;
34+
import jdk.graal.compiler.nodes.graphbuilderconf.InlineInvokePlugin;
3035
import jdk.graal.compiler.nodes.graphbuilderconf.InvocationPlugins;
36+
import jdk.graal.compiler.replacements.nodes.ArrayIndexOfNode;
3137
import jdk.graal.compiler.truffle.substitutions.TruffleInvocationPlugins;
38+
import jdk.graal.compiler.truffle.test.strings.TStringTest;
39+
import jdk.vm.ci.meta.ResolvedJavaMethod;
40+
import org.junit.Assert;
3241
import org.junit.Test;
3342
import org.junit.runner.RunWith;
3443
import org.junit.runners.Parameterized;
@@ -134,6 +143,26 @@ public static int indexOfByteArray(byte[] haystack, int fromIndex, int maxIndex,
134143
return ArrayUtils.indexOf(haystack, fromIndex, maxIndex, needle);
135144
}
136145

146+
@Override
147+
protected void checkLowTierGraph(StructuredGraph graph) {
148+
if (TStringTest.isSupportedArchitecture(getArchitecture())) {
149+
for (Node node : graph.getNodes()) {
150+
if (node instanceof ArrayIndexOfNode) {
151+
return;
152+
}
153+
}
154+
Assert.fail("intrinsic not found in graph!");
155+
}
156+
}
157+
158+
@Override
159+
protected InlineInvokePlugin.InlineInfo bytecodeParserShouldInlineInvoke(GraphBuilderContext b, ResolvedJavaMethod method, ValueNode[] args) {
160+
if (method.getDeclaringClass().getUnqualifiedName().equals("ArrayUtils") && !method.getName().startsWith("stub")) {
161+
return InlineInvokePlugin.InlineInfo.createStandardInlineInfo(method);
162+
}
163+
return super.bytecodeParserShouldInlineInvoke(b, method, args);
164+
}
165+
137166
private static byte[] toByteArray(String s) {
138167
byte[] ret = new byte[s.length()];
139168
for (int i = 0; i < s.length(); i++) {
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package jdk.graal.compiler.truffle.test.strings;
26+
27+
import java.util.ArrayList;
28+
import java.util.List;
29+
30+
import org.junit.Assert;
31+
import org.junit.Test;
32+
import org.junit.runner.RunWith;
33+
import org.junit.runners.Parameterized;
34+
import org.junit.runners.Parameterized.Parameters;
35+
36+
import jdk.graal.compiler.replacements.nodes.ArrayCopyWithConversionsNode;
37+
38+
@RunWith(Parameterized.class)
39+
public class TStringOpsByteSwapTest extends TStringOpsTest<ArrayCopyWithConversionsNode> {
40+
41+
@Parameters(name = "{index}: args: {1}, {2}, {3}")
42+
public static List<Object[]> data() {
43+
ArrayList<Object[]> ret = new ArrayList<>();
44+
int offsetBytes = 20;
45+
int contentLength = 129;
46+
byte[] src = new byte[1024];
47+
for (int i = 0; i < src.length; i++) {
48+
src[i] = (byte) i;
49+
}
50+
for (int stride = 1; stride <= 2; stride++) {
51+
for (int fromIndexA : new int[]{0, 1, 7, 15}) {
52+
for (int fromIndexB : new int[]{0, 1, 7, 15}) {
53+
for (int length : new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 15, 16, 17, 31, 32, 33, 47, 48, 49, 63, 64, 65, 127, 128, 129}) {
54+
if (fromIndexA + length < contentLength && fromIndexB + length < contentLength) {
55+
int offsetA = offsetBytes + (fromIndexA << stride);
56+
int offsetB = offsetBytes + (fromIndexB << stride);
57+
ret.add(new Object[]{src, offsetA, offsetB, length});
58+
}
59+
}
60+
}
61+
}
62+
}
63+
return ret;
64+
}
65+
66+
final byte[] arrayA;
67+
final long offsetA;
68+
final long offsetB;
69+
final int lengthCPY;
70+
71+
public TStringOpsByteSwapTest(byte[] arrayA, int offsetA, int offsetB, int lengthCPY) {
72+
super(ArrayCopyWithConversionsNode.class);
73+
this.arrayA = arrayA;
74+
this.offsetA = offsetA + byteArrayBaseOffset();
75+
this.offsetB = offsetB + byteArrayBaseOffset();
76+
this.lengthCPY = lengthCPY;
77+
}
78+
79+
@Test
80+
public void testByteSwapS1() {
81+
ArgSupplier arrayB = () -> new byte[(int) (128 + offsetB + (lengthCPY << 1) + 128)];
82+
testWithNativeExcept(getByteSwapS1(), null, 1 << 3, DUMMY_LOCATION, arrayA, offsetA, arrayB, offsetB + 128, lengthCPY);
83+
}
84+
85+
@Test
86+
public void testByteSwapS2() {
87+
ArgSupplier arrayB = () -> new byte[(int) (128 + offsetB + (lengthCPY << 2) + 128)];
88+
testWithNativeExcept(getByteSwapS2(), null, 1 << 3, DUMMY_LOCATION, arrayA, offsetA, arrayB, offsetB + 128, lengthCPY);
89+
}
90+
91+
@Override
92+
protected void checkIntrinsicNode(ArrayCopyWithConversionsNode node) {
93+
Assert.assertTrue(node.isReverseBytes());
94+
}
95+
}

compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/strings/TStringOpsTest.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2025, 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
@@ -98,6 +98,20 @@ protected void testWithNative(ResolvedJavaMethod method, Object receiver, Object
9898
testWithNativeExcept(method, receiver, 0, args);
9999
}
100100

101+
/**
102+
* Runs the given test once as-is and once replacing all argument pairs of
103+
* {@code (byte[], long)} with {@code (null, native-pointer)}.
104+
* <p>
105+
* In most TruffleString operations, {@code (byte[] array, long offset)} stands for a string's
106+
* managed or native contents, where in the managed case, the byte array is a regular Java
107+
* array, and the offset is a byte offset into the byte array. In the native case, the byte
108+
* array is {@code null}, and the offset a pointer into native (off-heap) memory.
109+
* <p>
110+
* This test helper covers the native case for all {@code (byte[], long)} pairs, except for
111+
* parameters that have been marked as excluded by setting their corresponding bit in the
112+
* {@code ignore}-parameter: If e.g. {@code ignore == (1 << 3)}, the fourth parameter will not
113+
* be replaced by a native pointer.
114+
*/
101115
protected void testWithNativeExcept(ResolvedJavaMethod method, Object receiver, long ignore, Object... args) {
102116
test(method, receiver, args);
103117
Object[] argsWithNative = Arrays.copyOf(args, args.length);
@@ -107,9 +121,8 @@ protected void testWithNativeExcept(ResolvedJavaMethod method, Object receiver,
107121
ResolvedJavaMethod.Parameter[] parameters = method.getParameters();
108122
Assert.assertTrue(parameters.length <= 64);
109123
for (int i = 0; i < parameters.length; i++) {
110-
if (parameters[i].getType().getName().equals("[B") && (ignore & (1L << i)) == 0) {
124+
if (parameters[i].getType().getName().equals("[B") && (ignore & (1L << i)) == 0 && i + 1 < parameters.length && parameters[i + 1].getType().getName().equals("J")) {
111125
Assert.assertTrue(argsWithNative[i].toString(), argsWithNative[i] instanceof byte[]);
112-
Assert.assertEquals("J", parameters[i + 1].getType().getName());
113126
byte[] array = (byte[]) argsWithNative[i];
114127
byteBuffers[nByteBuffers] = ByteBuffer.allocateDirect(array.length);
115128
long bufferAddress = getBufferAddress(byteBuffers[nByteBuffers++]);
@@ -144,6 +157,18 @@ protected ResolvedJavaMethod getArrayCopyWithStrideIB() {
144157
byte[].class, long.class, int.class, int.class);
145158
}
146159

160+
protected ResolvedJavaMethod getByteSwapS1() {
161+
return getTStringOpsMethod("byteSwapS1",
162+
byte[].class, long.class,
163+
byte[].class, long.class, int.class);
164+
}
165+
166+
protected ResolvedJavaMethod getByteSwapS2() {
167+
return getTStringOpsMethod("byteSwapS2",
168+
byte[].class, long.class,
169+
byte[].class, long.class, int.class);
170+
}
171+
147172
protected ResolvedJavaMethod getMemcmpWithStrideIntl() {
148173
return getTStringOpsMethod("memcmpWithStrideIntl",
149174
byte[].class, long.class, int.class,

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/asm/aarch64/AArch64Assembler.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@
134134
import static jdk.graal.compiler.asm.aarch64.AArch64Assembler.Instruction.ORR;
135135
import static jdk.graal.compiler.asm.aarch64.AArch64Assembler.Instruction.RBIT;
136136
import static jdk.graal.compiler.asm.aarch64.AArch64Assembler.Instruction.RET;
137+
import static jdk.graal.compiler.asm.aarch64.AArch64Assembler.Instruction.REV16;
137138
import static jdk.graal.compiler.asm.aarch64.AArch64Assembler.Instruction.REVW;
138139
import static jdk.graal.compiler.asm.aarch64.AArch64Assembler.Instruction.REVX;
139140
import static jdk.graal.compiler.asm.aarch64.AArch64Assembler.Instruction.RORV;
@@ -982,6 +983,7 @@ public enum Instruction {
982983
CLS(0x00001400),
983984
CLZ(0x00001000),
984985
RBIT(0x00000000),
986+
REV16(0x00000400),
985987
REVX(0x00000C00),
986988
REVW(0x00000800),
987989

@@ -3039,6 +3041,18 @@ public void rev(int size, Register dst, Register src) {
30393041
}
30403042
}
30413043

3044+
/**
3045+
* C6.2.223 Reverse bytes in 16-bit halfwords.
3046+
*
3047+
* @param size register size. Has to be 32 or 64.
3048+
* @param dst general purpose register. May not be null or the stackpointer.
3049+
* @param src source register. May not be null or the stackpointer.
3050+
*/
3051+
public void rev16(int size, Register dst, Register src) {
3052+
assert verifySizeAndRegistersRR(size, dst, src);
3053+
dataProcessing1SourceOp(REV16, dst, src, generalFromSize(size));
3054+
}
3055+
30423056
/* Conditional Data Processing (5.5.6) */
30433057

30443058
/**

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/asm/amd64/AMD64Assembler.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5720,6 +5720,11 @@ public final void roll(Register dst, int imm8) {
57205720
AMD64Shift.ROL.miOp.emit(this, OperandSize.DWORD, dst, (byte) imm8);
57215721
}
57225722

5723+
public final void rorl(Register dst, int imm8) {
5724+
GraalError.guarantee(isByte(imm8), "only byte immediate is supported");
5725+
AMD64Shift.ROR.miOp.emit(this, OperandSize.DWORD, dst, (byte) imm8);
5726+
}
5727+
57235728
public final void rorq(Register dst, int imm8) {
57245729
GraalError.guarantee(isByte(imm8), "only byte immediate is supported");
57255730
AMD64Shift.ROR.miOp.emit(this, OperandSize.QWORD, dst, (byte) imm8);

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/asm/amd64/AMD64MacroAssembler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,10 @@ public final int testlAndJcc(Register src, int imm32, ConditionFlag cc, Label br
627627
return applyMIOpAndJcc(AMD64MIOp.TEST, OperandSize.DWORD, src, imm32, cc, branchTarget, isShortJmp, false, null);
628628
}
629629

630+
public final int testqAndJcc(Register src, int imm32, ConditionFlag cc, Label branchTarget, boolean isShortJmp) {
631+
return applyMIOpAndJcc(AMD64MIOp.TEST, OperandSize.QWORD, src, imm32, cc, branchTarget, isShortJmp, false, null);
632+
}
633+
630634
public final int testAndJcc(OperandSize size, Register src1, Register src2, ConditionFlag cc, Label branchTarget, boolean isShortJmp) {
631635
AMD64RMOp op = size == OperandSize.BYTE ? AMD64RMOp.TESTB : AMD64RMOp.TEST;
632636
return applyRMOpAndJcc(op, size, src1, src2, cc, branchTarget, isShortJmp);

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/aarch64/AArch64LIRGenerator.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@
5555
import jdk.graal.compiler.lir.aarch64.AArch64ArithmeticOp;
5656
import jdk.graal.compiler.lir.aarch64.AArch64ArrayCompareToOp;
5757
import jdk.graal.compiler.lir.aarch64.AArch64ArrayCopyWithConversionsOp;
58-
import jdk.graal.compiler.lir.aarch64.AArch64ArrayFillOp;
5958
import jdk.graal.compiler.lir.aarch64.AArch64ArrayEqualsOp;
59+
import jdk.graal.compiler.lir.aarch64.AArch64ArrayFillOp;
6060
import jdk.graal.compiler.lir.aarch64.AArch64ArrayIndexOfOp;
6161
import jdk.graal.compiler.lir.aarch64.AArch64ArrayRegionCompareToOp;
6262
import jdk.graal.compiler.lir.aarch64.AArch64AtomicMove;
@@ -622,16 +622,22 @@ public Variable emitArrayRegionCompareTo(EnumSet<?> runtimeCheckedCPUFeatures, V
622622

623623
@Override
624624
public void emitArrayCopyWithConversion(Stride strideSrc, Stride strideDst, EnumSet<?> runtimeCheckedCPUFeatures, Value arraySrc, Value offsetSrc, Value arrayDst, Value offsetDst, Value length) {
625-
append(new AArch64ArrayCopyWithConversionsOp(this, strideSrc, strideDst,
625+
append(new AArch64ArrayCopyWithConversionsOp(this, strideSrc, strideDst, false,
626626
emitConvertNullToZero(arrayDst), asAllocatable(offsetDst), emitConvertNullToZero(arraySrc), asAllocatable(offsetSrc), asAllocatable(length), null));
627627
}
628628

629629
@Override
630630
public void emitArrayCopyWithConversion(EnumSet<?> runtimeCheckedCPUFeatures, Value arraySrc, Value offsetSrc, Value arrayDst, Value offsetDst, Value length, Value dynamicStrides) {
631-
append(new AArch64ArrayCopyWithConversionsOp(this, null, null,
631+
append(new AArch64ArrayCopyWithConversionsOp(this, null, null, false,
632632
emitConvertNullToZero(arrayDst), asAllocatable(offsetDst), emitConvertNullToZero(arraySrc), asAllocatable(offsetSrc), asAllocatable(length), asAllocatable(dynamicStrides)));
633633
}
634634

635+
@Override
636+
public void emitArrayCopyWithReverseBytes(Stride stride, EnumSet<?> runtimeCheckedCPUFeatures, Value arraySrc, Value offsetSrc, Value arrayDst, Value offsetDst, Value length) {
637+
append(new AArch64ArrayCopyWithConversionsOp(this, stride, stride, true,
638+
emitConvertNullToZero(arrayDst), asAllocatable(offsetDst), emitConvertNullToZero(arraySrc), asAllocatable(offsetSrc), asAllocatable(length), null));
639+
}
640+
635641
@Override
636642
public void emitArrayFill(JavaKind kind, Value array, Value arrayBaseOffset, Value length, Value value) {
637643
append(new AArch64ArrayFillOp(kind, emitConvertNullToZero(array), asAllocatable(arrayBaseOffset), asAllocatable(length), asAllocatable(value)));

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/amd64/AMD64LIRGenerator.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,12 @@ public void emitArrayCopyWithConversion(EnumSet<?> runtimeCheckedCPUFeatures,
881881
ZERO_EXTEND));
882882
}
883883

884+
@SuppressWarnings("unchecked")
885+
@Override
886+
public void emitArrayCopyWithReverseBytes(Stride stride, EnumSet<?> runtimeCheckedCPUFeatures, Value arraySrc, Value offsetSrc, Value arrayDst, Value offsetDst, Value length) {
887+
append(AMD64ArrayCopyWithConversionsOp.movParamsAndCreateReverseBytes(this, stride, (EnumSet<CPUFeature>) runtimeCheckedCPUFeatures, arraySrc, offsetSrc, arrayDst, offsetDst, length));
888+
}
889+
884890
@SuppressWarnings("unchecked")
885891
@Override
886892
public Variable emitCalcStringAttributes(CalcStringAttributesEncoding encoding, EnumSet<?> runtimeCheckedCPUFeatures,

0 commit comments

Comments
 (0)