Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2026, 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
Expand Down Expand Up @@ -31,6 +31,7 @@
import jdk.graal.compiler.core.amd64.AMD64AddressNode;
import jdk.graal.compiler.core.test.GraalCompilerTest;
import jdk.graal.compiler.nodes.ConstantNode;
import jdk.graal.compiler.nodes.ReturnNode;
import jdk.graal.compiler.nodes.StructuredGraph;
import jdk.graal.compiler.nodes.ValueNode;
import jdk.graal.compiler.nodes.calc.AddNode;
Expand Down Expand Up @@ -78,6 +79,40 @@ public void convertBaseAndShiftedIndexToDisplacement() {
assertAddress(result, null, null, Stride.S2, 1020);
}

/// Checks address scaling before the shift distance has been canonicalized.
@Test
public void convertRawShiftedIndexToDisplacement() {
for (int amount : new int[]{-64, -63, -62, -61, 64, 65, 66, 67}) {
ValueNode base = graph.addOrUniqueWithInputs(const64(1000));
ValueNode index = graph.addOrUniqueWithInputs(new LeftShiftNode(const64(10), const32(amount)));
AddressNode result = lowering.lower(base, index);
assertAddress(result, null, null, Stride.fromLog2(amount & 63), 1000 + (int) (10L << amount));
}
}

/// Checks that a negative inner distance cannot cancel the scale of an outer shift.
@Test
public void preserveNestedShiftOverflow() {
ValueNode base = graph.addOrUniqueWithInputs(const64(0));
ValueNode inner = graph.addOrUniqueWithInputs(new LeftShiftNode(const64(1), const32(-1)));
ValueNode outer = graph.addOrUniqueWithInputs(new LeftShiftNode(inner, const32(2)));
AMD64AddressNode address = (AMD64AddressNode) lowering.lower(base, outer);

// Evaluates the selected addressing mode after lowering has inspected the raw distances.
ValueNode value = graph.addOrUniqueWithInputs(const64(address.getDisplacement()));
if (address.getBase() != null) {
value = graph.addOrUniqueWithInputs(new AddNode(value, address.getBase()));
}
if (address.getIndex() != null) {
ValueNode scaledIndex = new LeftShiftNode(address.getIndex(), const32(address.getScale().log2));
value = graph.addOrUniqueWithInputs(new AddNode(value, scaledIndex));
}
ReturnNode returnNode = graph.add(new ReturnNode(value));
graph.start().setNext(returnNode);
createCanonicalizerPhase().apply(graph, getDefaultHighTierContext());
Assert.assertEquals((1L << -1) << 2, returnNode.result().asJavaConstant().asLong());
}

@Test
public void convertBaseAndNegatedShiftedIndexToDisplacement() {
ValueNode base = graph.addOrUniqueWithInputs(const64(1000));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ public boolean add(String s) {
verifiers.add(new VerifyPhaseNoDirectRecursion());
verifiers.add(new VerifyStringCaseUsage());
verifiers.add(new VerifyMathAbs());
verifiers.add(new VerifyShiftAmounts());
verifiers.add(new VerifyLoopInfo());
verifiers.add(new VerifyGuardsStageUsages());
verifiers.add(new VerifyAArch64RegisterUsages());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* Copyright (c) 2026, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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 jdk.graal.compiler.core.test;

import org.junit.Assert;
import org.junit.Test;

import jdk.graal.compiler.core.common.type.IntegerStamp;
import jdk.graal.compiler.core.common.type.StampPair;
import jdk.graal.compiler.nodes.ConstantNode;
import jdk.graal.compiler.nodes.LogicNode;
import jdk.graal.compiler.nodes.NodeView;
import jdk.graal.compiler.nodes.ParameterNode;
import jdk.graal.compiler.nodes.ReturnNode;
import jdk.graal.compiler.nodes.StructuredGraph;
import jdk.graal.compiler.nodes.ValueNode;
import jdk.graal.compiler.nodes.calc.ConditionalNode;
import jdk.graal.compiler.nodes.calc.IntegerBelowNode;
import jdk.graal.compiler.nodes.calc.IntegerLessThanNode;
import jdk.graal.compiler.nodes.calc.LeftShiftNode;
import jdk.graal.compiler.nodes.calc.MulNode;
import jdk.graal.compiler.nodes.calc.NegateNode;
import jdk.graal.compiler.nodes.calc.RightShiftNode;
import jdk.graal.compiler.nodes.calc.UnsignedRightShiftNode;
import jdk.graal.compiler.nodes.loop.DerivedScaledInductionVariable;
import jdk.graal.compiler.nodes.loop.InductionVariable;
import jdk.graal.compiler.nodes.loop.Loop;
import jdk.graal.compiler.nodes.loop.LoopsData;

/// Tests analyses of shifts before their constant distances have been canonicalized.
public class ShiftDistanceAnalysisTest extends GraalCompilerTest {

/// Checks that comparison cancellation preserves Java results for raw shift constants.
private void checkComparison(int bits, int amount, boolean unsigned, long xValue, long yValue) {
StructuredGraph graph = new StructuredGraph.Builder(getInitialOptions(), getDebugContext()).build();
IntegerStamp stamp = IntegerStamp.create(bits, 0, 3);
ParameterNode x = graph.addWithoutUnique(new ParameterNode(0, StampPair.createSingle(stamp)));
ParameterNode y = graph.addWithoutUnique(new ParameterNode(1, StampPair.createSingle(stamp)));
ConstantNode distance = ConstantNode.forInt(amount, graph);
// Retains the raw constant so comparison analysis runs before shift canonicalization.
LeftShiftNode shiftX = graph.unique(new LeftShiftNode(x, distance));
LeftShiftNode shiftY = graph.unique(new LeftShiftNode(y, distance));
LogicNode comparison = unsigned
? IntegerBelowNode.create(getConstantReflection(), getMetaAccess(), graph.getOptions(), null, shiftX, shiftY, NodeView.DEFAULT)
: IntegerLessThanNode.create(getConstantReflection(), getMetaAccess(), graph.getOptions(), null, shiftX, shiftY, NodeView.DEFAULT);
comparison = graph.addOrUniqueWithInputs(comparison);
ValueNode result = graph.addOrUniqueWithInputs(ConditionalNode.create(comparison, NodeView.DEFAULT));
ReturnNode returnNode = graph.add(new ReturnNode(result));
graph.start().setNext(returnNode);

x.replaceAtUsages(ConstantNode.forIntegerBits(bits, xValue, graph));
y.replaceAtUsages(ConstantNode.forIntegerBits(bits, yValue, graph));
createCanonicalizerPhase().apply(graph, getDefaultHighTierContext());

boolean expected;
if (bits == Integer.SIZE) {
int shiftedX = (int) xValue << amount;
int shiftedY = (int) yValue << amount;
expected = unsigned ? Integer.compareUnsigned(shiftedX, shiftedY) < 0 : shiftedX < shiftedY;
} else {
long shiftedX = xValue << amount;
long shiftedY = yValue << amount;
expected = unsigned ? Long.compareUnsigned(shiftedX, shiftedY) < 0 : shiftedX < shiftedY;
}
Assert.assertEquals(expected ? 1 : 0, returnNode.result().asJavaConstant().asInt());
}

/// Covers both comparison orders and distances on either side of each Java shift mask.
@Test
public void testComparisonCancellation() {
for (int bits : new int[]{Integer.SIZE, Long.SIZE}) {
for (int amount : new int[]{-1, -2, -bits, 0, 1, bits - 1, bits, bits + 1}) {
for (boolean unsigned : new boolean[]{false, true}) {
for (long x = 0; x <= 3; x++) {
for (long y = 0; y <= 3; y++) {
checkComparison(bits, amount, unsigned, x, y);
}
}
}
}
}
}

/// Supplies an int induction variable with a shift that survives parsing.
public static int intLoop(int limit, int amount) {
int result = 0;
for (int i = 0; i < limit; i++) {
result += i << amount;
}
return result;
}

/// Supplies a long induction variable with a shift that survives parsing.
public static long longLoop(long limit, int amount) {
long result = 0;
for (long i = 0; i < limit; i++) {
result += i << amount;
}
return result;
}

/// Checks the scale inferred from the original shift width rather than the host long width.
private void checkLoopScale(String snippet, int amount) {
StructuredGraph graph = parseEager(snippet, StructuredGraph.AllowAssumptions.NO);
LeftShiftNode shift = graph.getNodes().filter(LeftShiftNode.class).first();
graph.getParameter(1).replaceAtUsages(ConstantNode.forInt(amount, graph));
// Queries IV metadata before canonicalization can normalize the injected distance.
LoopsData loops = getDefaultHighTierContext().getLoopsDataProvider().getLoopsData(graph);
Assert.assertEquals(1, loops.loops().size());
Loop loop = loops.loops().get(0);
InductionVariable iv = loop.getInductionVariables().get(shift);
assertTrue(iv instanceof DerivedScaledInductionVariable);
long expected = snippet.equals("intLoop") ? 1 << amount : 1L << amount;
Assert.assertEquals(expected, ((DerivedScaledInductionVariable) iv).getScale().asJavaConstant().asLong());
}

/// Covers int and long scales for negative, masked, and out-of-range distances.
@Test
public void testLoopScale() {
for (int amount : new int[]{-65, -33, -32, -1, 0, 1, 31, 32, 33, 63, 64, 65}) {
checkLoopScale("intLoop", amount);
checkLoopScale("longLoop", amount);
}
}

/// Checks multiplication conversion using raw distances and the original operand width.
@Test
public void testEquivalentMultiplication() {
for (int bits : new int[]{Integer.SIZE, Long.SIZE}) {
ParameterNode x = new ParameterNode(0, StampPair.createSingle(IntegerStamp.create(bits)));
for (int amount : new int[]{Integer.MIN_VALUE, -65, -33, -32, -1, 0, 1, 31, 32, 33, 63, 64, 65, Integer.MAX_VALUE}) {
LeftShiftNode shift = new LeftShiftNode(x, ConstantNode.forInt(amount));
MulNode mul = shift.getEquivalentMulNode();
Assert.assertNotNull(mul);
long expected = bits == Integer.SIZE ? 1 << amount : 1L << amount;
Assert.assertEquals(expected, mul.getY().asJavaConstant().asLong());
}
}
}

/// Checks that negation recognizes a sign shift before its distance is canonicalized.
@Test
public void testSignShiftNegation() {
for (int bits : new int[]{Integer.SIZE, Long.SIZE}) {
ParameterNode x = new ParameterNode(0, StampPair.createSingle(IntegerStamp.create(bits)));
for (int amount : new int[]{-1, bits - 1, 2 * bits - 1, Integer.MAX_VALUE}) {
RightShiftNode shift = new RightShiftNode(x, ConstantNode.forInt(amount));
ValueNode negated = NegateNode.create(shift, NodeView.DEFAULT);
assertTrue(negated instanceof UnsignedRightShiftNode);
Assert.assertSame(x, ((UnsignedRightShiftNode) negated).getX());
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright (c) 2026, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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 jdk.graal.compiler.core.test;

import jdk.graal.compiler.nodes.Invoke;
import jdk.graal.compiler.nodes.StructuredGraph;
import jdk.graal.compiler.nodes.ValueNode;
import jdk.graal.compiler.nodes.calc.BinaryNode;
import jdk.graal.compiler.nodes.calc.ShiftNode;
import jdk.graal.compiler.nodes.java.LoadFieldNode;
import jdk.graal.compiler.nodes.java.MethodCallTargetNode;
import jdk.graal.compiler.nodes.spi.CoreProviders;
import jdk.graal.compiler.nodes.type.StampTool;
import jdk.graal.compiler.nodes.util.GraphUtil;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;

/// Checks that constant distances of known shifts are read through `ShiftNode.getConstantShiftAmount()`.
///
/// This check follows `getY()` results through local aliases and casts in the parsed graph. It
/// cannot identify shifts hidden behind a generic `BinaryNode` receiver or a separate method.
public class VerifyShiftAmounts extends VerifyPhase<CoreProviders> {

@Override
public boolean checkContract() {
return false;
}

@Override
protected void verify(StructuredGraph graph, CoreProviders context) {
ResolvedJavaType shiftType = context.getMetaAccess().lookupJavaType(ShiftNode.class);
ResolvedJavaType binaryType = context.getMetaAccess().lookupJavaType(BinaryNode.class);
ResolvedJavaType valueType = context.getMetaAccess().lookupJavaType(ValueNode.class);
ResolvedJavaMethod caller = graph.method();
if (caller.getDeclaringClass().equals(shiftType) && caller.getName().equals("getConstantShiftAmount")) {
// The helper is the single place that reads and masks the raw constant distance.
return;
}
for (MethodCallTargetNode call : graph.getNodes(MethodCallTargetNode.TYPE)) {
ResolvedJavaMethod callee = call.targetMethod();
if (callee.getDeclaringClass().equals(valueType) && (callee.getName().equals("asJavaConstant") || callee.getName().equals("asConstant"))) {
ValueNode receiver = GraphUtil.unproxify(call.arguments().get(0));
ValueNode owner = operandOwner(receiver, binaryType);
if (owner != null) {
ResolvedJavaType receiverType = StampTool.typeOrNull(owner);
if (receiverType != null && shiftType.isAssignableFrom(receiverType)) {
throw new VerificationError(call, "Use ShiftNode.getConstantShiftAmount() instead of extracting a raw constant from ShiftNode.getY(); " +
"the distance may not yet have been masked by canonicalization.");
}
}
}
}
}

/// Finds the binary node supplying its second operand, including an inlined getter.
private static ValueNode operandOwner(ValueNode distance, ResolvedJavaType binaryType) {
if (distance instanceof Invoke invoke && invoke.callTarget() instanceof MethodCallTargetNode getY) {
ResolvedJavaMethod accessor = getY.targetMethod();
if (accessor.getDeclaringClass().equals(binaryType) && accessor.getName().equals("getY")) {
return getY.arguments().get(0);
}
} else if (distance instanceof LoadFieldNode load && load.field().getDeclaringClass().equals(binaryType) && load.field().getName().equals("y")) {
return load.object();
}
return null;
}
}
Loading