diff --git a/ci/common.jsonnet b/ci/common.jsonnet
index 850994500915..b86336176cef 100644
--- a/ci/common.jsonnet
+++ b/ci/common.jsonnet
@@ -49,7 +49,7 @@ local common_json = import "../common.json";
# The devkits versions reflect those used to build the JVMCI JDKs (e.g., see devkit_platform_revisions in /make/conf/jib-profiles.js)
devkits: {
- "windows-jdk17": { packages+: { "devkit:VS2022-17.1.0+1": "==0" }},
+ "windows-jdk17": { packages+: { "devkit:VS2022-17.6.5+1": "==0" }},
"windows-jdk19": { packages+: { "devkit:VS2022-17.1.0+1": "==0" }},
"windows-jdk20": { packages+: { "devkit:VS2022-17.1.0+1": "==0" }},
"linux-jdk17": { packages+: { "devkit:gcc10.3.0-OL6.4+1": "==0" }},
diff --git a/common.json b/common.json
index 38f6d23a3c5d..1ff7f6a519a4 100644
--- a/common.json
+++ b/common.json
@@ -14,9 +14,9 @@
"labsjdk-ce-17": {"name": "labsjdk", "version": "ce-17.0.9+9-jvmci-23.0-b22", "platformspecific": true },
"labsjdk-ce-17Debug": {"name": "labsjdk", "version": "ce-17.0.9+9-jvmci-23.0-b22-debug", "platformspecific": true },
"labsjdk-ce-17-llvm": {"name": "labsjdk", "version": "ce-17.0.9+9-jvmci-23.0-b22-sulong", "platformspecific": true },
- "labsjdk-ee-17": {"name": "labsjdk", "version": "ee-17.0.10+9-jvmci-23.0-b25", "platformspecific": true },
- "labsjdk-ee-17Debug": {"name": "labsjdk", "version": "ee-17.0.10+9-jvmci-23.0-b25-debug", "platformspecific": true },
- "labsjdk-ee-17-llvm": {"name": "labsjdk", "version": "ee-17.0.10+9-jvmci-23.0-b25-sulong", "platformspecific": true },
+ "labsjdk-ee-17": {"name": "labsjdk", "version": "ee-17.0.11+6-jvmci-23.0-b33", "platformspecific": true },
+ "labsjdk-ee-17Debug": {"name": "labsjdk", "version": "ee-17.0.11+6-jvmci-23.0-b33-debug", "platformspecific": true },
+ "labsjdk-ee-17-llvm": {"name": "labsjdk", "version": "ee-17.0.11+6-jvmci-23.0-b33-sulong", "platformspecific": true },
"oraclejdk19": {"name": "jpg-jdk", "version": "19", "build_id": "26", "release": true, "platformspecific": true, "extrabundles": ["static-libs"]},
"labsjdk-ce-19": {"name": "labsjdk", "version": "ce-19.0.1+10-jvmci-23.0-b04", "platformspecific": true },
diff --git a/compiler/mx.compiler/suite.py b/compiler/mx.compiler/suite.py
index 5cbcca5d61fb..8bffabbefbec 100644
--- a/compiler/mx.compiler/suite.py
+++ b/compiler/mx.compiler/suite.py
@@ -4,7 +4,7 @@
"sourceinprojectwhitelist" : [],
"groupId" : "org.graalvm.compiler",
- "version" : "23.0.3.1",
+ "version" : "23.0.4.0",
"release" : False,
"url" : "http://www.graalvm.org/",
"developer" : {
diff --git a/compiler/src/org.graalvm.compiler.code/src/org/graalvm/compiler/code/CompilationResult.java b/compiler/src/org.graalvm.compiler.code/src/org/graalvm/compiler/code/CompilationResult.java
index 30d94a7b3781..4ca8f3a6e6eb 100644
--- a/compiler/src/org.graalvm.compiler.code/src/org/graalvm/compiler/code/CompilationResult.java
+++ b/compiler/src/org.graalvm.compiler.code/src/org/graalvm/compiler/code/CompilationResult.java
@@ -257,7 +257,7 @@ public String toString() {
private Assumption[] assumptions;
/**
- * The list of the methods whose bytecodes were used as input to the compilation. If
+ * The set of the methods whose bytecodes were used as input to the compilation. If
* {@code null}, then the compilation did not record method dependencies. Otherwise, the first
* element of this array is the root method of the compilation.
*/
@@ -361,45 +361,36 @@ public Assumption[] getAssumptions() {
}
/**
- * Sets the methods whose bytecodes were used as input to the compilation.
+ * Records the set of methods whose bytecodes were used as input to the compilation.
*
* @param rootMethod the root method of the compilation
- * @param inlinedMethods the methods inlined during compilation
+ * @param inlinedMethods the methods inlined during compilation (may contain duplicates)
*/
public void setMethods(ResolvedJavaMethod rootMethod, Collection inlinedMethods) {
checkOpen();
assert rootMethod != null;
assert inlinedMethods != null;
- if (inlinedMethods.contains(rootMethod)) {
- methods = inlinedMethods.toArray(new ResolvedJavaMethod[inlinedMethods.size()]);
- for (int i = 0; i < methods.length; i++) {
- if (methods[i].equals(rootMethod)) {
- if (i != 0) {
- ResolvedJavaMethod tmp = methods[0];
- methods[0] = methods[i];
- methods[i] = tmp;
- }
- break;
- }
- }
- } else {
- methods = new ResolvedJavaMethod[1 + inlinedMethods.size()];
- methods[0] = rootMethod;
- int i = 1;
- for (ResolvedJavaMethod m : inlinedMethods) {
- methods[i++] = m;
- }
+
+ EconomicSet methodSet = EconomicSet.create(inlinedMethods.size());
+ methodSet.addAll(inlinedMethods);
+ methodSet.remove(rootMethod);
+ methods = new ResolvedJavaMethod[1 + methodSet.size()];
+ methods[0] = rootMethod;
+ int i = 1;
+ for (ResolvedJavaMethod m : methodSet) {
+ methods[i++] = m;
}
}
/**
- * Gets the methods whose bytecodes were used as input to the compilation.
+ * Gets the set of methods whose bytecodes were used as input to the compilation.
*
* The caller must not modify the contents of the returned array.
*
* @return {@code null} if the compilation did not record method dependencies otherwise the
* methods whose bytecodes were used as input to the compilation with the first element
- * being the root method of the compilation
+ * being the root method of the compilation. The entries in a non-null returned array
+ * are guaranteed to be unique.
*/
public ResolvedJavaMethod[] getMethods() {
return methods;
diff --git a/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/type/ArithmeticOpTable.java b/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/type/ArithmeticOpTable.java
index 735e0f887d7f..71670f49419e 100644
--- a/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/type/ArithmeticOpTable.java
+++ b/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/type/ArithmeticOpTable.java
@@ -985,7 +985,16 @@ public IntegerConvertOp unwrap() {
}
/**
- * Computes the stamp of the input for the given output stamp.
+ * Computes the stamp of the input for the given output stamp. This method returns
+ * {@code null} if a stamp cannot be inverted for an operation (i.e., {@link Narrow}). When
+ * inverting non-exact stamps, i.e. {@code 0xx0xxxx}, the inversion keeps all available
+ * information. If the stamp to invert contains contradictions regarding the post condition
+ * of the operation, an empty stamp is returned. An example for a contradiction would be a
+ * {@code SignExtend} with both {@code 0} and {@code 1} in the extension.
+ *
+ * @return {@code null} - if stamp inversion is not supported
+ * empty stamp - if the output stamp contains contradictions
+ * inverted output stamp - otherwise
*/
public abstract Stamp invertStamp(int inputBits, int resultBits, Stamp outStamp);
}
diff --git a/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/type/IntegerStamp.java b/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/type/IntegerStamp.java
index db304041b0ca..6e497f67ce20 100644
--- a/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/type/IntegerStamp.java
+++ b/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/type/IntegerStamp.java
@@ -1976,24 +1976,31 @@ public Stamp invertStamp(int inputBits, int resultBits, Stamp outStamp) {
}
/*
- * there is no guarantee that a given result is in the range of the
+ * There is no guarantee that a given result is in the range of the
* input because of holes in ranges resulting from signed / unsigned
* extension, so we must ensure that the extension bits are all zeros
* otherwise we cannot represent the result, and we have to return an
- * unrestricted stamp
+ * empty stamp.
*
* This case is much less likely to happen than the case for SignExtend
* but the following is defensive to ensure that we only perform valid
* inversions.
*/
- long alwaysSetOutputBits = stamp.mustBeSet();
- long alwaysSetExtensionBits = alwaysSetOutputBits >>> inputBits;
- if (alwaysSetExtensionBits != 0) {
- createEmptyStamp(inputBits);
+ long mustBeSetOutputBits = stamp.mustBeSet();
+ long mustBeSetExtensionBits = mustBeSetOutputBits >>> inputBits;
+ if (mustBeSetExtensionBits != 0) {
+ return createEmptyStamp(inputBits);
}
- long inputMask = CodeUtil.mask(inputBits);
- return StampFactory.forUnsignedInteger(inputBits, stamp.lowerBound(), stamp.upperBound(), stamp.mustBeSet() & inputMask, stamp.mayBeSet() & inputMask);
+ /*
+ * The output of a zero extend cannot be negative. Setting the lower
+ * bound > 0 enables inverting stamps like [-8, 16] without having to
+ * return an unrestricted stamp.
+ */
+ long lowerBound = Math.max(stamp.lowerBound(), 0);
+ assert stamp.upperBound() >= 0 : "Cannot invert ZeroExtend for stamp with msb=1, which implies a negative value after ZeroExtend!";
+
+ return StampFactory.forUnsignedInteger(inputBits, lowerBound, stamp.upperBound(), stamp.mustBeSet(), stamp.mayBeSet());
}
},
@@ -2029,11 +2036,11 @@ public Stamp invertStamp(int inputBits, int resultBits, Stamp outStamp) {
}
/*
- * there is no guarantee that a given result bit is in the range of the
+ * There is no guarantee that a given result bit is in the range of the
* input because of holes in ranges resulting from signed / unsigned
* extension, so we must ensure that the extension bits are either all
* zeros or all ones otherwise we cannot represent the result, and we
- * have to return an unrestricted stamp
+ * have to return an empty stamp.
*
* As an example:
* @formatter:off
@@ -2056,21 +2063,75 @@ public Stamp invertStamp(int inputBits, int resultBits, Stamp outStamp) {
* ZeroExtend to get 0xb308, but then we try to invert the SignExtend.
* The sign extend could only have produced 0xff__ or 0x00__ from a byte
* but 0xb308 has 0xb3, and so we cannot invert the stamp. In this case
- * the only sensible inversion is the unrestricted stamp.
+ * the only sensible inversion is the empty stamp.
*/
- long alwaysSetOutputBits = stamp.mustBeSet();
- long alwaysSetExtensionBits = alwaysSetOutputBits >>> inputBits;
- long outputMask = CodeUtil.mask(resultBits);
- if (alwaysSetExtensionBits != 0 && alwaysSetExtensionBits != (outputMask >>> inputBits)) {
+ long mustBeSetExtensionBits = stamp.mustBeSet() >>> inputBits;
+ long mayBeSetExtensionBits = stamp.mayBeSet() >>> inputBits;
+ long extensionMask = CodeUtil.mask(stamp.getBits()) >>> inputBits;
+
+ boolean zeroInExtension = mayBeSetExtensionBits != extensionMask;
+ boolean oneInExtension = mustBeSetExtensionBits != 0;
+ boolean inputMSBOne = significantBit(inputBits, stamp.mustBeSet()) == 1;
+ boolean inputMSBZero = significantBit(inputBits, stamp.mayBeSet()) == 0;
+
+ /*
+ * Checks for contradictions in the extension and returns an empty stamp in such cases.
+ * Examples for contradictions for a stamp after an artificial 4->8 bit sign extension:
+ *
+ * @formatter:off
+ *
+ * 1) 01xx xxxx --> extension cannot contain zeroes and ones
+ * 2) x0xx 1xxx --> extension cannot contain a zero if the MSB of the extended value is 1
+ * 3) xx1x 0xxx --> extension cannot contain a one if the MSB of the extended value is 0
+ *
+ * @formatter:on
+ */
+ if ((zeroInExtension && oneInExtension) || (inputMSBOne && zeroInExtension) || (inputMSBZero && oneInExtension)) {
return createEmptyStamp(inputBits);
}
long inputMask = CodeUtil.mask(inputBits);
- if (inputBits < stamp.getBits() && (stamp.contains(CodeUtil.minValue(inputBits) - 1) || stamp.contains(CodeUtil.maxValue(inputBits) + 1))) {
- // Truncation will cause this value to wrap around
- return create(inputBits).unrestricted();
+ long inputMustBeSet = stamp.mustBeSet() & inputMask;
+ long inputMayBeSet = stamp.mayBeSet() & inputMask;
+
+ if (!inputMSBOne && !inputMSBZero) {
+ /*
+ * Input MSB yet unknown, try to infer it from the extension:
+ *
+ * @formatter:off
+ *
+ * xx0x xxxx implies that the extension is 0000 which implies that the MSB of the input is 0
+ * x1xx xxxx implies that the extension is 1111 which implies that the MSB of the input is 1
+ *
+ * @formatter:on
+ */
+ if (zeroInExtension) {
+ long msbZeroMask = ~(1 << (inputBits - 1));
+ inputMustBeSet &= msbZeroMask;
+ inputMayBeSet &= msbZeroMask;
+ } else if (oneInExtension) {
+ long msbOneMask = 1 << (inputBits - 1);
+ inputMustBeSet |= msbOneMask;
+ inputMayBeSet |= msbOneMask;
+ }
}
- return StampFactory.forIntegerWithMask(inputBits, stamp.lowerBound(), stamp.upperBound(), stamp.mustBeSet() & inputMask, stamp.mayBeSet() & inputMask);
+
+ // Calculate conservative bounds for the input.
+ long inputUpperBound = maxValueForMasks(inputBits, inputMustBeSet, inputMayBeSet);
+ long inputLowerBound = minValueForMasks(inputBits, inputMustBeSet, inputMayBeSet);
+
+ /*
+ * If the bounds calculated for the input stamp do not overlap with the
+ * bounds for the stamp to invert, return an empty stamp. Otherwise,
+ * refine the conservative stamp for the input.
+ */
+ if ((stamp.upperBound() < inputLowerBound) || (stamp.lowerBound() > inputUpperBound)) {
+ return createEmptyStamp(inputBits);
+ }
+ inputUpperBound = Math.min(inputUpperBound, stamp.upperBound());
+ inputLowerBound = Math.max(inputLowerBound, stamp.lowerBound());
+
+ return StampFactory.forIntegerWithMask(inputBits, inputLowerBound, inputUpperBound, inputMustBeSet, inputMayBeSet);
}
},
diff --git a/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/type/StampFactory.java b/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/type/StampFactory.java
index 13b32d1fd5cc..8da3ce14210c 100644
--- a/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/type/StampFactory.java
+++ b/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/type/StampFactory.java
@@ -161,6 +161,30 @@ public static IntegerStamp forUnsignedInteger(int bits, long unsignedLowerBound,
return forUnsignedInteger(bits, unsignedLowerBound, unsignedUpperBound, 0, CodeUtil.mask(bits));
}
+ /**
+ * Creates an IntegerStamp from the given unsigned bounds and bit masks. This method returns an
+ * empty stamp if the unsigned lower bound is larger than the unsigned upper bound. If the sign
+ * of lower and upper bound differs after sign extension to the specified length ({@code bits}),
+ * this method returns an unrestricted stamp with respect to the bounds. {@code mayBeSet} or
+ * {@code mustBeSet} can restrict the bounds nevertheless. Take the following example when
+ * inverting a zero extended 32bit stamp to the 8bit stamp before extension:
+ *
+ *
+ * 32bit stamp [0, 192] 00...00 xxxxxxx0
+ *
+ * When converting the bounds to 8bit, they have different signs, i.e.:
+ *
+ * lowerUnsigned = 0000 0000 and upperUnsigned = 1100 0000
+ *
+ * In order to respect the (unsigned) boundaries of the extended value, the signed input can be:
+ *
+ * 0000 0000 - 0111 1111, i.e. 0 to 127
+ * or
+ * 1000 0000 - 1100 0000, i.e. -128 to -64
+ *
+ * The resulting interval [-128, -64]u[0, 127] cannot be represented by a single upper and lower
+ * bound. Thus, we have to return an unrestricted stamp, with respect to the bounds.
+ */
public static IntegerStamp forUnsignedInteger(int bits, long unsignedLowerBound, long unsignedUpperBound, long mustBeSet, long mayBeSet) {
if (Long.compareUnsigned(unsignedLowerBound, unsignedUpperBound) > 0) {
return IntegerStamp.createEmptyStamp(bits);
diff --git a/compiler/src/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/ConditionalEliminationStampInversionTest.java b/compiler/src/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/ConditionalEliminationStampInversionTest.java
new file mode 100644
index 000000000000..bb5879b8aab2
--- /dev/null
+++ b/compiler/src/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/ConditionalEliminationStampInversionTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2023, 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 org.graalvm.compiler.core.test;
+
+import org.graalvm.compiler.nodes.ConstantNode;
+import org.graalvm.compiler.nodes.FixedGuardNode;
+import org.graalvm.compiler.nodes.IfNode;
+import org.graalvm.compiler.nodes.LogicNode;
+import org.graalvm.compiler.nodes.NodeView;
+import org.graalvm.compiler.nodes.StructuredGraph;
+import org.graalvm.compiler.nodes.ValueNode;
+import org.graalvm.compiler.nodes.calc.AndNode;
+import org.graalvm.compiler.nodes.calc.IntegerEqualsNode;
+import org.graalvm.compiler.nodes.calc.IntegerTestNode;
+import org.graalvm.compiler.nodes.calc.SignExtendNode;
+import org.graalvm.compiler.nodes.util.GraphUtil;
+import org.graalvm.compiler.phases.common.ConditionalEliminationPhase;
+import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
+import org.junit.Test;
+
+import jdk.vm.ci.code.InvalidInstalledCodeException;
+import jdk.vm.ci.meta.DeoptimizationAction;
+import jdk.vm.ci.meta.DeoptimizationReason;
+
+/**
+ * Tests the correctness of conditional elimination when inverting stamps along
+ * {@link SignExtendNode}. The test artificially creates a graph whith the optimizable pattern:
+ * {@code ((val & CONST) == CONST)}, which provides information about the set bits in val, if the
+ * condition is used in a guard which is assumed to hold. The partial information about bits which
+ * are set in {@code x} are propagated "upwards". A {@link SignExtendNode} must treat the partial
+ * information correctly with respect to the extension bits and handle contradictions.
+ *
+ * If {@code CONST = 1 << 4 = 0001 0000} and we assume the condition holds, we can refine a stamp
+ * {@code xxxx xx11} for {@code val} to {@code xxx1 xx11}. Inverting this refined stamp along an
+ * artificial 4->8 bit sign extension should treat the extension as {@code 1111} and produce an
+ * inverted stamp of {@code xx11} or, even better {@code 1x11}, because we know the MSB has to be
+ * one from the extension.
+ *
+ * If the inversion is performed incorrectly, the inner condition could be considered as always
+ * false and removing it during conditional elimination.
+ */
+public class ConditionalEliminationStampInversionTest extends GraalCompilerTest {
+
+ public static boolean snippet(byte b) {
+ short s = b;
+ int i = s;
+
+ if ((i & (1 << 16)) == (1 << 16)) {
+ if (s == -5) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Test
+ public void test() throws InvalidInstalledCodeException {
+ StructuredGraph g = parseEager("snippet", AllowAssumptions.YES);
+
+ // replace the IntegerTest by an equivalent and/== pattern
+ ValueNode intTest = g.getNodes().filter(IntegerTestNode.class).first();
+ assertTrue("IntegerTestNode expected in graph.", intTest != null);
+
+ ValueNode signExtend = g.getNodes().filter(SignExtendNode.class).first();
+ assertTrue("SignExtendNode expected in graph.", signExtend != null);
+
+ ValueNode and = g.addOrUnique(AndNode.create(signExtend, ConstantNode.forInt(1 << 16, g), NodeView.DEFAULT));
+ and.inferStamp();
+ LogicNode intEq = g.addOrUnique(IntegerEqualsNode.create(and, ConstantNode.forInt(1 << 16, g), NodeView.DEFAULT));
+ intEq.inferStamp();
+
+ intTest.replaceAtUsages(intEq);
+
+ // replace the if by a fixed guard to trigger conditional elimination for the and/== pattern
+ ValueNode ifNode = (ValueNode) intEq.usages().first();
+ assertTrue("IfNode expected as first usage of IntegerEqualsNode.", ifNode != null && ifNode instanceof IfNode);
+
+ FixedGuardNode guard = g.add(new FixedGuardNode(intEq, DeoptimizationReason.ArithmeticException, DeoptimizationAction.InvalidateRecompile));
+ GraphUtil.killCFG(((IfNode) ifNode).trueSuccessor());
+ g.replaceSplitWithFixed((IfNode) ifNode, guard, ((IfNode) ifNode).falseSuccessor());
+
+ new ConditionalEliminationPhase(false).apply(g, getDefaultHighTierContext());
+
+ // the inner condition should still be alive and the following execution return true
+ assert (boolean) getCode(getResolvedJavaMethod("snippet"), g, true, true, getInitialOptions()).executeVarargs((byte) -5);
+ }
+}
diff --git a/compiler/src/org.graalvm.compiler.hotspot.amd64.test/src/org/graalvm/compiler/hotspot/amd64/test/ReadEliminateLowTierTest.java b/compiler/src/org.graalvm.compiler.hotspot.amd64.test/src/org/graalvm/compiler/hotspot/amd64/test/ReadEliminateLowTierTest.java
new file mode 100644
index 000000000000..7bbf7192330e
--- /dev/null
+++ b/compiler/src/org.graalvm.compiler.hotspot.amd64.test/src/org/graalvm/compiler/hotspot/amd64/test/ReadEliminateLowTierTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2024, 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 org.graalvm.compiler.hotspot.amd64.test;
+
+import static org.graalvm.compiler.nodeinfo.NodeCycles.CYCLES_0;
+import static org.graalvm.compiler.nodeinfo.NodeSize.SIZE_0;
+
+import org.junit.Test;
+import org.graalvm.compiler.api.directives.GraalDirectives;
+import org.graalvm.compiler.core.common.type.StampFactory;
+import org.graalvm.compiler.core.test.GraalCompilerTest;
+import org.graalvm.compiler.graph.Node;
+import org.graalvm.compiler.graph.NodeClass;
+import org.graalvm.compiler.nodeinfo.InputType;
+import org.graalvm.compiler.nodeinfo.NodeInfo;
+import org.graalvm.compiler.nodes.FixedWithNextNode;
+import org.graalvm.compiler.nodes.ValueNode;
+import org.graalvm.compiler.nodes.calc.IsNullNode;
+import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.Plugins;
+import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext;
+import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin;
+import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins.Registration;
+import org.graalvm.compiler.nodes.memory.ReadNode;
+import org.graalvm.compiler.nodes.spi.Canonicalizable;
+import org.graalvm.compiler.nodes.spi.CanonicalizerTool;
+import org.graalvm.compiler.phases.common.UseTrappingNullChecksPhase;
+
+import jdk.vm.ci.code.InstalledCode;
+import jdk.vm.ci.code.InvalidInstalledCodeException;
+import jdk.vm.ci.meta.ResolvedJavaMethod;
+
+/**
+ * Test to ensure that late elimination of memory reads preserves necessary null check semantic with
+ * respect to {@link UseTrappingNullChecksPhase}.
+ */
+public class ReadEliminateLowTierTest extends GraalCompilerTest {
+ static class T {
+ int x;
+ int y;
+ int z;
+ }
+
+ public static int trappingSnippet(T t) {
+ if (t == null) {
+ GraalDirectives.deoptimizeAndInvalidate();
+ return -1;
+ }
+ /*
+ * The first read from t here will act as trapping null check for all the others. We must
+ * not remove this read if its used as a null check even if it does not have any usages any
+ * more.
+ */
+ foldAfterTrappingNullChecks(t.x);
+ int result = t.y + t.z;
+ return result;
+ }
+
+ static void foldAfterTrappingNullChecks(@SuppressWarnings("unused") int i) {
+ }
+
+ @Override
+ protected Plugins getDefaultGraphBuilderPlugins() {
+ Plugins p = super.getDefaultGraphBuilderPlugins();
+ Registration r = new Registration(p.getInvocationPlugins(), ReadEliminateLowTierTest.class);
+ r.register(new InvocationPlugin("foldAfterTrappingNullChecks", int.class) {
+ @Override
+ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode arg) {
+ b.append(new FixedUsageUntilFinalCanon(arg));
+ return true;
+ }
+ });
+ return p;
+ }
+
+ /**
+ * Node that gets optimized away be late canonicalization.
+ */
+ @NodeInfo(cycles = CYCLES_0, size = SIZE_0, allowedUsageTypes = {InputType.Anchor})
+ public static class FixedUsageUntilFinalCanon extends FixedWithNextNode implements Canonicalizable {
+ public static final NodeClass TYPE = NodeClass.create(FixedUsageUntilFinalCanon.class);
+
+ @OptionalInput ValueNode object;
+
+ public FixedUsageUntilFinalCanon(ValueNode object) {
+ super(TYPE, StampFactory.forVoid());
+ this.object = object;
+ }
+
+ @Override
+ public Node canonical(CanonicalizerTool tool) {
+ // after trapping nulls
+ if (graph().getNodes().filter(IsNullNode.class).count() == 0) {
+ if (tool.allUsagesAvailable() && object instanceof ReadNode) {
+ ReadNode r = (ReadNode) object;
+ if (r.hasExactlyOneUsage() && r.usages().first().equals(this)) {
+ return null;
+ }
+ }
+ }
+ return this;
+ }
+ }
+
+ @Test
+ public void test() throws InvalidInstalledCodeException {
+ InstalledCode ic = getCode(getResolvedJavaMethod("trappingSnippet"));
+ assert lastCompiledGraph != null;
+ ic.executeVarargs(new T());
+ ic.executeVarargs((Object) null);
+ }
+
+}
diff --git a/compiler/src/org.graalvm.compiler.hotspot/src/org/graalvm/compiler/hotspot/meta/HotSpotGraphBuilderPlugins.java b/compiler/src/org.graalvm.compiler.hotspot/src/org/graalvm/compiler/hotspot/meta/HotSpotGraphBuilderPlugins.java
index 82805b18fc98..94cea781e5fe 100644
--- a/compiler/src/org.graalvm.compiler.hotspot/src/org/graalvm/compiler/hotspot/meta/HotSpotGraphBuilderPlugins.java
+++ b/compiler/src/org.graalvm.compiler.hotspot/src/org/graalvm/compiler/hotspot/meta/HotSpotGraphBuilderPlugins.java
@@ -540,7 +540,7 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec
ValueNode valueLength = b.add(new ArrayLengthNode(value));
ValueNode limit = b.add(new SubNode(valueLength, length));
helper.intrinsicRangeCheck(srcBegin, Condition.GT, limit);
- ValueNode newArray = b.add(new NewArrayNode(b.getMetaAccess().lookupJavaType(Byte.TYPE), b.add(new LeftShiftNode(length, ConstantNode.forInt(1))), false));
+ ValueNode newArray = new NewArrayNode(b.getMetaAccess().lookupJavaType(Byte.TYPE), b.add(new LeftShiftNode(length, ConstantNode.forInt(1))), false);
b.addPush(JavaKind.Object, newArray);
// The stateAfter should include the value pushed, so push it first and then
// perform the call that fills in the array.
@@ -963,7 +963,7 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec
int byteArrayBaseOffset = metaAccess.getArrayBaseOffset(JavaKind.Byte);
ComputeObjectAddressNode srcAddress = b.add(new ComputeObjectAddressNode(src, ConstantNode.forInt(byteArrayBaseOffset)));
ComputeObjectAddressNode dstAddress = b.add(new ComputeObjectAddressNode(dst, ConstantNode.forInt(byteArrayBaseOffset)));
- ForeignCallNode call = b.add(new ForeignCallNode(BASE64_DECODE_BLOCK, srcAddress, sp, sl, dstAddress, dp, isURL, isMime));
+ ForeignCallNode call = new ForeignCallNode(BASE64_DECODE_BLOCK, srcAddress, sp, sl, dstAddress, dp, isURL, isMime);
b.addPush(JavaKind.Int, call);
return true;
}
@@ -976,7 +976,7 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec
int byteArrayBaseOffset = metaAccess.getArrayBaseOffset(JavaKind.Byte);
ComputeObjectAddressNode srcAddress = b.add(new ComputeObjectAddressNode(src, ConstantNode.forInt(byteArrayBaseOffset)));
ComputeObjectAddressNode dstAddress = b.add(new ComputeObjectAddressNode(dst, ConstantNode.forInt(byteArrayBaseOffset)));
- ForeignCallNode call = b.add(new ForeignCallNode(BASE64_DECODE_BLOCK, srcAddress, sp, sl, dstAddress, dp, isURL));
+ ForeignCallNode call = new ForeignCallNode(BASE64_DECODE_BLOCK, srcAddress, sp, sl, dstAddress, dp, isURL);
b.addPush(JavaKind.Int, call);
return true;
}
@@ -1075,7 +1075,7 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec
ValueNode stateStart = helper.arrayStart(stateNotNull, JavaKind.Int);
ValueNode resultStart = helper.arrayStart(resultNotNull, JavaKind.Byte);
- ForeignCallNode call = b.add(new ForeignCallNode(CHACHA20Block, stateStart, resultStart));
+ ForeignCallNode call = new ForeignCallNode(CHACHA20Block, stateStart, resultStart);
b.addPush(JavaKind.Int, call);
}
return true;
@@ -1160,10 +1160,11 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec
ValueNode instanceSize = b.add(AndNode.create(layoutHelper, ConstantNode.forInt(~(Long.BYTES - 1)), NodeView.DEFAULT));
b.add(new IfNode(isArray, arrayLengthNode, instanceBranch, BranchProbabilityData.unknown()));
- MergeNode merge = b.add(new MergeNode());
+ MergeNode merge = b.append(new MergeNode());
merge.addForwardEnd(arrayBranch);
merge.addForwardEnd(instanceBranch);
b.addPush(JavaKind.Long, SignExtendNode.create(new ValuePhiNode(StampFactory.positiveInt(), merge, new ValueNode[]{arraySizeMasked, instanceSize}), 64, NodeView.DEFAULT));
+ b.setStateAfter(merge);
}
return true;
}
diff --git a/compiler/src/org.graalvm.compiler.java/src/org/graalvm/compiler/java/BytecodeParser.java b/compiler/src/org.graalvm.compiler.java/src/org/graalvm/compiler/java/BytecodeParser.java
index 6c7b972246eb..e3984e817e49 100644
--- a/compiler/src/org.graalvm.compiler.java/src/org/graalvm/compiler/java/BytecodeParser.java
+++ b/compiler/src/org.graalvm.compiler.java/src/org/graalvm/compiler/java/BytecodeParser.java
@@ -4406,7 +4406,7 @@ protected void genCheckCast(ResolvedJavaType resolvedType, ValueNode objectIn) {
if (profile.getNullSeen().isFalse()) {
SpeculationLog.Speculation speculation = mayUseTypeProfile();
if (speculation != null) {
- object = nullCheckedValue(object);
+ object = addNonNullCast(object, InvalidateReprofile);
ResolvedJavaType singleType = profile.asSingleType();
if (singleType != null && checkedType.getType().isAssignableFrom(singleType)) {
LogicNode typeCheck = append(createInstanceOf(TypeReference.createExactTrusted(singleType), object, profile));
@@ -4469,7 +4469,7 @@ protected void genInstanceOf(ResolvedJavaType resolvedType, ValueNode objectIn)
LogicNode instanceOfNode = null;
if (profile != null) {
if (profile.getNullSeen().isFalse()) {
- object = nullCheckedValue(object);
+ object = addNonNullCast(object, InvalidateReprofile);
boolean createGuard = true;
ResolvedJavaType singleType = profile.asSingleType();
if (singleType != null) {
diff --git a/compiler/src/org.graalvm.compiler.nodes.test/src/org/graalvm/compiler/nodes/test/CompareZeroExtendWithConstantTest.java b/compiler/src/org.graalvm.compiler.nodes.test/src/org/graalvm/compiler/nodes/test/CompareZeroExtendWithConstantTest.java
index 598fe64fccdc..a0613bf26df9 100644
--- a/compiler/src/org.graalvm.compiler.nodes.test/src/org/graalvm/compiler/nodes/test/CompareZeroExtendWithConstantTest.java
+++ b/compiler/src/org.graalvm.compiler.nodes.test/src/org/graalvm/compiler/nodes/test/CompareZeroExtendWithConstantTest.java
@@ -32,7 +32,7 @@ public class CompareZeroExtendWithConstantTest extends GraalCompilerTest {
public static byte[] a = {};
- public static void snippet() {
+ public static void snippet01() {
for (byte b : a) {
char c = (char) b;
GraalDirectives.blackhole(c);
@@ -43,7 +43,21 @@ public static void snippet() {
}
@Test
- public void testSnippet() {
- test("snippet");
+ public void testSnippet01() {
+ test("snippet01");
+ }
+
+ public static boolean snippet02(boolean p0, long p1) {
+ boolean var0 = p0;
+ byte b = (byte) p1;
+ for (long i = 245799965; i >= 245797839; i = 3) {
+ b = (byte) Character.toUpperCase((char) b);
+ }
+ return var0;
+ }
+
+ @Test
+ public void testSnippet02() {
+ test("snippet02", true, 53069L);
}
}
diff --git a/compiler/src/org.graalvm.compiler.nodes.test/src/org/graalvm/compiler/nodes/test/NarrowPreservesOrderTest.java b/compiler/src/org.graalvm.compiler.nodes.test/src/org/graalvm/compiler/nodes/test/NarrowPreservesOrderTest.java
index c471fcc18c1c..dd941f7f22cc 100644
--- a/compiler/src/org.graalvm.compiler.nodes.test/src/org/graalvm/compiler/nodes/test/NarrowPreservesOrderTest.java
+++ b/compiler/src/org.graalvm.compiler.nodes.test/src/org/graalvm/compiler/nodes/test/NarrowPreservesOrderTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2024, 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
@@ -97,7 +97,7 @@ public void testByte() {
testPreserveOrder(forConstantInt(NumUtil.maxValueUnsigned(8)), 8, LT, false);
testPreserveOrder(forConstantInt(NumUtil.maxValueUnsigned(8)), 8, BT, true);
testPreserveOrder(signExtend(StampFactory.forKind(JavaKind.Byte), 32), 8, LT, true);
- testPreserveOrder(signExtend(StampFactory.forKind(JavaKind.Byte), 32), 8, BT, true);
+ testPreserveOrder(signExtend(StampFactory.forKind(JavaKind.Byte), 32), 8, BT, false);
testPreserveOrder(zeroExtend(StampFactory.forUnsignedInteger(8), 32), 8, LT, false);
testPreserveOrder(zeroExtend(StampFactory.forUnsignedInteger(8), 32), 8, BT, true);
@@ -114,7 +114,7 @@ public void testShort() {
testPreserveOrder(forConstantInt(NumUtil.maxValueUnsigned(16)), 16, LT, false);
testPreserveOrder(forConstantInt(NumUtil.maxValueUnsigned(16)), 16, BT, true);
testPreserveOrder(signExtend(StampFactory.forKind(JavaKind.Short), 32), 16, LT, true);
- testPreserveOrder(signExtend(StampFactory.forKind(JavaKind.Short), 32), 16, BT, true);
+ testPreserveOrder(signExtend(StampFactory.forKind(JavaKind.Short), 32), 16, BT, false);
testPreserveOrder(zeroExtend(StampFactory.forUnsignedInteger(16), 32), 16, LT, false);
testPreserveOrder(zeroExtend(StampFactory.forUnsignedInteger(16), 32), 16, BT, true);
@@ -131,7 +131,7 @@ public void testInt() {
testPreserveOrder(forConstantLong(NumUtil.maxValueUnsigned(32)), 32, LT, false);
testPreserveOrder(forConstantLong(NumUtil.maxValueUnsigned(32)), 32, BT, true);
testPreserveOrder(signExtend(StampFactory.forKind(JavaKind.Int), 64), 32, LT, true);
- testPreserveOrder(signExtend(StampFactory.forKind(JavaKind.Int), 64), 32, BT, true);
+ testPreserveOrder(signExtend(StampFactory.forKind(JavaKind.Int), 64), 32, BT, false);
testPreserveOrder(zeroExtend(StampFactory.forUnsignedInteger(32), 64), 32, LT, false);
testPreserveOrder(zeroExtend(StampFactory.forUnsignedInteger(32), 64), 32, BT, true);
diff --git a/compiler/src/org.graalvm.compiler.nodes.test/src/org/graalvm/compiler/nodes/test/StampInverterTest.java b/compiler/src/org.graalvm.compiler.nodes.test/src/org/graalvm/compiler/nodes/test/StampInverterTest.java
new file mode 100644
index 000000000000..69d14a8a4455
--- /dev/null
+++ b/compiler/src/org.graalvm.compiler.nodes.test/src/org/graalvm/compiler/nodes/test/StampInverterTest.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2023, 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 org.graalvm.compiler.nodes.test;
+
+import static org.junit.Assert.assertEquals;
+
+import org.graalvm.compiler.core.common.type.ArithmeticOpTable;
+import org.graalvm.compiler.core.common.type.IntegerStamp;
+import org.graalvm.compiler.core.common.type.Stamp;
+import org.graalvm.compiler.core.common.type.ArithmeticOpTable.IntegerConvertOp;
+import org.graalvm.compiler.core.common.type.ArithmeticOpTable.IntegerConvertOp.SignExtend;
+import org.graalvm.compiler.core.common.type.ArithmeticOpTable.IntegerConvertOp.ZeroExtend;
+import org.graalvm.compiler.test.GraalTest;
+import org.junit.Test;
+
+import jdk.vm.ci.code.CodeUtil;
+
+/**
+ * Tests the expected behavior of inverting stamps for different operations. During stamp inversion,
+ * the input stamp is calculated from a given output stamp. If a particular stamp cannot be inverted
+ * because of a contradiction regarding the operation's post condition, the inversion process is
+ * supposed to return an empty stamp. An example for a contradiction would be both {@code 0} and
+ * {@code 1} bits in the extension of a {@code SignExtend}.
+ */
+public class StampInverterTest extends GraalTest {
+
+ private static Stamp invertSignExtend(Stamp toInvert) {
+ IntegerConvertOp signExtend = ArithmeticOpTable.forStamp(toInvert).getSignExtend();
+ return signExtend.invertStamp(8, 32, toInvert);
+ }
+
+ @Test
+ public void invertIntegerSignExtend01() {
+ // 32- > 8bit: xx...xx 11xxxxxx -> 11xxxxxx
+ IntegerStamp stamp = IntegerStamp.stampForMask(32, 128 | 64, CodeUtil.mask(32));
+ Stamp expected = IntegerStamp.stampForMask(8, 128 | 64, CodeUtil.mask(8));
+ assertEquals(expected, invertSignExtend(stamp));
+ }
+
+ @Test
+ public void invertIntegerSignExtend02() {
+ // 32 -> 8bit: xx...10 11xxxxxx -> cannot be inverted
+ IntegerStamp stamp = IntegerStamp.stampForMask(32, 512 | 128 | 64, CodeUtil.mask(32) ^ 256);
+ assertTrue("Stamp cannot be inverted and should be empty!", invertSignExtend(stamp).isEmpty());
+ }
+
+ @Test
+ public void invertIntegerSignExtend03() {
+ // 32 -> 8bit: xx...1x 0xxxxxxx -> cannot be inverted
+ IntegerStamp stamp = IntegerStamp.stampForMask(32, 512, CodeUtil.mask(32) ^ 128);
+ assertTrue("Stamp cannot be inverted and should be empty!", invertSignExtend(stamp).isEmpty());
+ }
+
+ @Test
+ public void invertIntegerSignExtend04() {
+ // 32 -> 8bit: xx...x0 1xxxxxxx -> cannot be inverted
+ IntegerStamp stamp = IntegerStamp.stampForMask(32, 128, CodeUtil.mask(32) ^ 256);
+ assertTrue("Stamp cannot be inverted and should be empty!", invertSignExtend(stamp).isEmpty());
+ }
+
+ @Test
+ public void invertIntegerSignExtend05() {
+ // 32 -> 8bit: xx...x0 xxxxxxxx -> 0xxxxxxx (msb has to be 0)
+ IntegerStamp stamp = IntegerStamp.stampForMask(32, 0, CodeUtil.mask(32) ^ 256);
+ Stamp expected = IntegerStamp.stampForMask(8, 0, CodeUtil.mask(7));
+ assertEquals(expected, invertSignExtend(stamp));
+ }
+
+ @Test
+ public void invertIntegerSignExtend06() {
+ // 32 -> 8bit: xx...x1 xxxxxxxx -> 1xxxxxxx (msb has to be 1)
+ IntegerStamp stamp = IntegerStamp.stampForMask(32, 256, CodeUtil.mask(32));
+ Stamp expected = IntegerStamp.stampForMask(8, 128, CodeUtil.mask(8));
+ assertEquals(expected, invertSignExtend(stamp));
+ }
+
+ @Test
+ public void invertIntegerSignExtend07() {
+ // 32 -> 8bit: [-128, 126] xx...xx xxxxxxx0 -> [-128, 126] xxxxxxx0
+ IntegerStamp stamp = IntegerStamp.create(32, -128, 126, 0, CodeUtil.mask(32) ^ 1);
+ Stamp expected = IntegerStamp.create(8, -128, 126, 0, CodeUtil.mask(32) ^ 1);
+ assertEquals(expected, invertSignExtend(stamp));
+ }
+
+ @Test
+ public void invertIntegerSignExtend08() {
+ // 32 -> 8bit: [-256, 126] xx...xx 0xxxxxx0 -> [0, 126] 0xxxxxx0
+ IntegerStamp stamp = IntegerStamp.create(32, -256, 126, 0, CodeUtil.mask(32) ^ (256 | 1));
+ Stamp expected = IntegerStamp.create(8, 0, 126, 0, CodeUtil.mask(32) ^ (256 | 1));
+ assertEquals(expected, invertSignExtend(stamp));
+ }
+
+ @Test
+ public void invertIntegerSignExtend09() {
+ // 32 -> 8bit: [-8, 126] xx...xx xxxxxxx0 -> [-8, 126] xxxxxxx0
+ IntegerStamp stamp = IntegerStamp.create(32, -8, 126, 0, CodeUtil.mask(32) ^ 1);
+ Stamp expected = IntegerStamp.create(8, -8, 126, 0, CodeUtil.mask(32) ^ 1);
+ assertEquals(expected, invertSignExtend(stamp));
+ }
+
+ @Test
+ public void invertIntegerSignExtend10() {
+ // 32 -> 8bit: [int min, -1024] 1x...xx xxxxxxxx -> empty
+ IntegerStamp stamp = IntegerStamp.create(32, Integer.MIN_VALUE, -1024);
+ assertTrue("Stamp cannot be inverted and should be empty!", invertSignExtend(stamp).isEmpty());
+ }
+
+ @Test
+ public void invertIntegerSignExtend11() {
+ // 32 -> 8bit: [1024, int max] 0x...xx xxxxxxxx -> empty
+ IntegerStamp stamp = IntegerStamp.create(32, 1024, Integer.MAX_VALUE);
+ assertTrue("Stamp cannot be inverted and should be empty!", invertSignExtend(stamp).isEmpty());
+ }
+
+ @Test
+ public void invertIntegerSignExtend12() {
+ // 32 -> 8bit: [0, 255] 00...00 xxxxxxxx -> [0, 127] 0xxxxxxx
+ IntegerStamp stamp = IntegerStamp.create(32, 0, 255, 0, CodeUtil.mask(8));
+ Stamp expected = IntegerStamp.create(8, 0, 127, 0, CodeUtil.mask(7));
+ assertEquals(expected, invertSignExtend(stamp));
+ }
+
+ private static Stamp invertZeroExtend(Stamp toInvert) {
+ IntegerConvertOp zeroExtend = ArithmeticOpTable.forStamp(toInvert).getZeroExtend();
+ return zeroExtend.invertStamp(8, 32, toInvert);
+ }
+
+ @Test
+ public void invertIntegerZeroExtend01() {
+ // 32 -> 8bit: xx...xx 11xxxxxx -> 11xxxxxx
+ IntegerStamp stamp = IntegerStamp.stampForMask(32, 128 | 64, CodeUtil.mask(32));
+ Stamp expected = IntegerStamp.stampForMask(8, 128 | 64, CodeUtil.mask(8));
+ assertEquals(expected, invertZeroExtend(stamp));
+ }
+
+ @Test
+ public void invertIntegerZeroExtend02() {
+ // 32 -> 8bit: xx...0x 01xxxxxx -> 01xxxxxx
+ IntegerStamp stamp = IntegerStamp.stampForMask(32, 64, CodeUtil.mask(32) ^ (512 | 128));
+ Stamp expected = IntegerStamp.stampForMask(8, 64, CodeUtil.mask(8) ^ 128);
+ assertEquals(expected, invertZeroExtend(stamp));
+ }
+
+ @Test
+ public void invertIntegerZeroExtend03() {
+ // 32 -> 8bit: xx...1x 01xxxxxx -> cannot be inverted
+ IntegerStamp stamp = IntegerStamp.stampForMask(32, 512 | 64, CodeUtil.mask(32) ^ 128);
+ assertTrue("Stamp cannot be inverted and should be empty!", invertZeroExtend(stamp).isEmpty());
+ }
+
+ @Test
+ public void invertIntegerZeroExtend04() {
+ // 32 -> 8bit: [int min, int max -1] xx...xx xxxxxxx0 -> [-128, 126] xxxxxxx0
+ IntegerStamp stamp = IntegerStamp.create(32, Integer.MIN_VALUE, Integer.MAX_VALUE - 1, 0, CodeUtil.mask(32) ^ 1);
+ Stamp expected = IntegerStamp.create(8, -128, 126, 0, CodeUtil.mask(8) ^ 1);
+ assertEquals(expected, invertZeroExtend(stamp));
+ }
+
+ @Test
+ public void invertIntegerZeroExtend5() {
+ /*
+ * 32 -> 8bit: [0, 192] 00...00 xxxxxxx0 -> [-128, 126] xxxxxxx0
+ *
+ * The 32bit stamp bounds have different signs when converting to 8bit:
+ * lowerUnsigned = 0000 0000, upperUnsigned = 1100 0000.
+ * In order to respect the (unsigned) boundaries of the extended value, the signed input can be:
+ * @formatter:off
+ *
+ * 0000 0000 - 0111 1111, i.e. 0 to 127
+ * or
+ * 1000 0000 - 1100 0000, i.e. -128 to -64
+ *
+ * @formatter:on
+ * The resulting interval [-128, -64]u[0, 127] cannot be represented by a single upper and
+ * lower bound. Thus, we have to return an unrestricted stamp, with respect to the bounds.
+ */
+ IntegerStamp stamp = IntegerStamp.create(32, 0, 192, 0, CodeUtil.mask(8) ^ 1);
+ Stamp expected = IntegerStamp.create(8, -128, 126, 0, CodeUtil.mask(8) ^ 1);
+ assertEquals(expected, invertZeroExtend(stamp));
+ }
+
+ @Test
+ public void invertIntegerZeroExtend06() {
+ /*
+ * 32 -> 8bit: [-8, 16] xx...xx xxxxxxx0 -> [0, 16] xxxxxxx0
+ *
+ * Negative lower bounds can be clamped to 0.
+ */
+ IntegerStamp stamp = IntegerStamp.create(32, -8, 16, 0, CodeUtil.mask(32) ^ 1);
+ Stamp expected = IntegerStamp.create(8, 0, 16, 0, CodeUtil.mask(8) ^ 1);
+ assertEquals(expected, invertZeroExtend(stamp));
+ }
+
+ @Test
+ public void invertIntegerZeroExtend07() {
+ // 32 -> 8bit: [2, 18] 00...00 000xxx10 -> [2, 18] 000xxx10
+ IntegerStamp stamp = IntegerStamp.create(32, 2, 18, 2, CodeUtil.mask(5) ^ 1);
+ Stamp expected = IntegerStamp.create(8, 2, 18, 2, CodeUtil.mask(5) ^ 1);
+ assertEquals(expected, invertZeroExtend(stamp));
+ }
+
+ @Test
+ public void invertIntegerZeroExtend08() {
+ // 32 -> 8bit: [128, 254] 00...00 1xxxxxx0 -> [-128, -2] 1xxxxxx0
+ IntegerStamp stamp = IntegerStamp.create(32, 128, 254, 128, CodeUtil.mask(8) ^ 1);
+ Stamp expected = IntegerStamp.create(8, -128, -2, 128, CodeUtil.mask(8) ^ 1);
+ assertEquals(expected, invertZeroExtend(stamp));
+ }
+
+ @Test
+ public void invertIntegerZeroExtend09() {
+ // 32 -> 8bit: [int min ^ 128, 254] xx...xx xxxxxxx0 -> [-128, 126] xxxxxxx0
+ IntegerStamp stamp = IntegerStamp.create(32, Integer.MIN_VALUE ^ 128, 254, 0, CodeUtil.mask(32) ^ 1);
+ Stamp expected = IntegerStamp.create(8, -128, 126, 0, CodeUtil.mask(8) ^ 1);
+ assertEquals(expected, invertZeroExtend(stamp));
+ }
+}
diff --git a/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/calc/NarrowNode.java b/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/calc/NarrowNode.java
index f325b713a440..caefc754ec4d 100644
--- a/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/calc/NarrowNode.java
+++ b/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/calc/NarrowNode.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2024, 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
@@ -130,10 +130,22 @@ public boolean preservesOrder(CanonicalCondition cond) {
switch (cond) {
case LT:
return isSignedLossless();
+ /*
+ * We may use signed stamps to represent unsigned integers. This narrow preserves order
+ * if it is signed lossless and is being compared to another narrow that is signed
+ * lossless, or if it is unsigned lossless and the other narrow is also unsigned
+ * lossless. We don't have access to the other narrow here, so we must make a
+ * conservative choice. We can rely on the fact that the same computation will be
+ * performed on the other narrow, so it will make the same choice.
+ *
+ * Most Java values are signed, so we expect the signed case to be more relevant for
+ * equals comparison. In contrast, the unsigned case should be more relevant for
+ * unsigned less than comparisons.
+ */
case EQ:
+ return isSignedLossless();
case BT:
- // We may use signed stamps to represent unsigned integers.
- return isSignedLossless() || isUnsignedLossless();
+ return isUnsignedLossless();
default:
throw GraalError.shouldNotReachHere("Unsupported canonical condition."); // ExcludeFromJacocoGeneratedReport
}
diff --git a/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/graphbuilderconf/GraphBuilderContext.java b/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/graphbuilderconf/GraphBuilderContext.java
index 0dc66c98eddb..3e9801a7d18d 100644
--- a/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/graphbuilderconf/GraphBuilderContext.java
+++ b/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/graphbuilderconf/GraphBuilderContext.java
@@ -112,7 +112,10 @@ default ValueNode[] popArguments(int argSize) {
/**
* Adds a node and all its inputs to the graph. If the node is in the graph, returns
* immediately. If the node is a {@link StateSplit} with a null
- * {@linkplain StateSplit#stateAfter() frame state} , the frame state is initialized.
+ * {@linkplain StateSplit#stateAfter() frame state} , the frame state is initialized. A
+ * {@link StateSplit} that will be pushed to the stack using
+ * {@link #addPush(JavaKind, ValueNode)} should not be added using this method,
+ * otherwise its frame state will be initialized with an incorrect stack effect.
*
* @param value the value to add to the graph. The {@code value.getJavaKind()} kind is used when
* type checking this operation.
@@ -123,16 +126,20 @@ default T add(T value) {
assert !(value instanceof StateSplit) || ((StateSplit) value).stateAfter() != null;
return value;
}
- return GraphBuilderContextUtil.setStateAfterIfNecessary(this, append(value));
+ return setStateAfterIfNecessary(this, append(value));
}
default ValueNode addNonNullCast(ValueNode value) {
+ return addNonNullCast(value, DeoptimizationAction.None);
+ }
+
+ default ValueNode addNonNullCast(ValueNode value, DeoptimizationAction action) {
AbstractPointerStamp valueStamp = (AbstractPointerStamp) value.stamp(NodeView.DEFAULT);
if (valueStamp.nonNull()) {
return value;
} else {
LogicNode isNull = add(IsNullNode.create(value));
- FixedGuardNode fixedGuard = add(new FixedGuardNode(isNull, DeoptimizationReason.NullCheckException, DeoptimizationAction.None, true));
+ FixedGuardNode fixedGuard = add(new FixedGuardNode(isNull, DeoptimizationReason.NullCheckException, action, true));
Stamp newStamp = valueStamp.improveWith(StampFactory.objectNonNull());
return add(PiNode.create(value, newStamp, fixedGuard));
}
@@ -141,7 +148,9 @@ default ValueNode addNonNullCast(ValueNode value) {
/**
* Adds a node with a non-void kind to the graph, pushes it to the stack. If the returned node
* is a {@link StateSplit} with a null {@linkplain StateSplit#stateAfter() frame state}, the
- * frame state is initialized.
+ * frame state is initialized. A {@link StateSplit} added using this method should not
+ * be added using {@link #add(ValueNode)} beforehand, otherwise its frame state will be
+ * initialized with an incorrect stack effect.
*
* @param kind the kind to use when type checking this operation
* @param value the value to add to the graph and push to the stack
@@ -150,7 +159,7 @@ default ValueNode addNonNullCast(ValueNode value) {
default T addPush(JavaKind kind, T value) {
T equivalentValue = value.graph() != null ? value : append(value);
push(kind, equivalentValue);
- return GraphBuilderContextUtil.setStateAfterIfNecessary(this, equivalentValue);
+ return setStateAfterIfNecessary(this, equivalentValue);
}
/**
@@ -515,15 +524,18 @@ default void replacePluginWithException(GeneratedInvocationPlugin plugin, Resolv
PluginReplacementWithExceptionNode.ReplacementWithExceptionFunction replacementFunction) {
throw GraalError.unimplemented(); // ExcludeFromJacocoGeneratedReport
}
-}
-class GraphBuilderContextUtil {
static T setStateAfterIfNecessary(GraphBuilderContext b, T value) {
if (value instanceof StateSplit) {
StateSplit stateSplit = (StateSplit) value;
+ FrameState oldState = stateSplit.stateAfter();
if (stateSplit.stateAfter() == null && (stateSplit.hasSideEffect() || stateSplit instanceof AbstractMergeNode)) {
b.setStateAfter(stateSplit);
}
+ FrameState newState = stateSplit.stateAfter();
+ GraalError.guarantee(oldState == null || oldState.equals(newState),
+ "graph builder changed existing state on %s from %s to %s, this indicates multiple calls to add() or addPush() for one node",
+ stateSplit, oldState, newState);
}
return value;
}
diff --git a/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/loop/DerivedConvertedInductionVariable.java b/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/loop/DerivedConvertedInductionVariable.java
index 7bdc2f0d0d83..b54ec052cef3 100644
--- a/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/loop/DerivedConvertedInductionVariable.java
+++ b/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/loop/DerivedConvertedInductionVariable.java
@@ -81,6 +81,16 @@ public long constantStride() {
return base.constantStride();
}
+ @Override
+ public boolean isConstantExtremum() {
+ return base.isConstantExtremum();
+ }
+
+ @Override
+ public long constantExtremum() {
+ return base.constantExtremum();
+ }
+
@Override
public ValueNode extremumNode(boolean assumeLoopEntered, Stamp s) {
// base.extremumNode will already perform any necessary conversion operation based on the
@@ -102,16 +112,6 @@ public ValueNode exitValueNode() {
return op(base.exitValueNode(), true);
}
- @Override
- public boolean isConstantExtremum() {
- return base.isConstantExtremum();
- }
-
- @Override
- public long constantExtremum() {
- return base.constantExtremum();
- }
-
@Override
public void deleteUnusedNodes() {
}
diff --git a/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/loop/DerivedOffsetInductionVariable.java b/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/loop/DerivedOffsetInductionVariable.java
index 0dbed91f6044..65cf53f5fb29 100644
--- a/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/loop/DerivedOffsetInductionVariable.java
+++ b/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/loop/DerivedOffsetInductionVariable.java
@@ -71,7 +71,15 @@ public ValueNode valueNode() {
@Override
public boolean isConstantInit() {
- return offset.isConstant() && base.isConstantInit();
+ try {
+ if (offset.isConstant() && base.isConstantInit()) {
+ constantInitSafe();
+ return true;
+ }
+ } catch (ArithmeticException e) {
+ // fall through to return false
+ }
+ return false;
}
@Override
@@ -81,17 +89,47 @@ public boolean isConstantStride() {
@Override
public long constantInit() {
- return op(base.constantInit(), offset.asJavaConstant().asLong());
+ return constantInitSafe();
+ }
+
+ private long constantInitSafe() throws ArithmeticException {
+ return opSafe(base.constantInit(), offset.asJavaConstant().asLong());
}
@Override
public long constantStride() {
+ return constantStrideSafe();
+ }
+
+ private long constantStrideSafe() throws ArithmeticException {
if (value instanceof SubNode && base.valueNode() == value.getY()) {
- return -base.constantStride();
+ return Math.multiplyExact(base.constantStride(), -1);
}
return base.constantStride();
}
+ @Override
+ public boolean isConstantExtremum() {
+ try {
+ if (offset.isConstant() && base.isConstantExtremum()) {
+ constantExtremumSafe();
+ return true;
+ }
+ } catch (ArithmeticException e) {
+ // fall through to return false
+ }
+ return false;
+ }
+
+ @Override
+ public long constantExtremum() {
+ return constantExtremumSafe();
+ }
+
+ private long constantExtremumSafe() throws ArithmeticException {
+ return opSafe(base.constantExtremum(), offset.asJavaConstant().asLong());
+ }
+
@Override
public ValueNode initNode() {
return op(base.initNode(), offset);
@@ -120,26 +158,16 @@ public ValueNode exitValueNode() {
return op(base.exitValueNode(), offset);
}
- @Override
- public boolean isConstantExtremum() {
- return offset.isConstant() && base.isConstantExtremum();
- }
-
- @Override
- public long constantExtremum() {
- return op(base.constantExtremum(), offset.asJavaConstant().asLong());
- }
-
- private long op(long b, long o) {
+ private long opSafe(long b, long o) throws ArithmeticException {
if (value instanceof AddNode) {
- return b + o;
+ return Math.addExact(b, o);
}
if (value instanceof SubNode) {
if (base.valueNode() == value.getX()) {
- return b - o;
+ return Math.subtractExact(b, o);
} else {
- assert base.valueNode() == value.getY();
- return o - b;
+ assert base.valueNode() == value.getY() : String.format("[base]=%s;[value]=%s", base.valueNode(), value.getY());
+ return Math.subtractExact(b, o);
}
}
throw GraalError.shouldNotReachHere(); // ExcludeFromJacocoGeneratedReport
diff --git a/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/loop/DerivedScaledInductionVariable.java b/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/loop/DerivedScaledInductionVariable.java
index d40266243a42..bd4f7abbd98e 100644
--- a/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/loop/DerivedScaledInductionVariable.java
+++ b/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/loop/DerivedScaledInductionVariable.java
@@ -91,47 +91,83 @@ public ValueNode strideNode() {
@Override
public boolean isConstantInit() {
- return scale.isConstant() && base.isConstantInit();
+ try {
+ if (scale.isConstant() && base.isConstantInit()) {
+ constantInitSafe();
+ return true;
+ }
+ } catch (ArithmeticException e) {
+ // fall through to return false
+ }
+ return false;
}
@Override
public boolean isConstantStride() {
- return scale.isConstant() && base.isConstantStride();
+ try {
+ if (scale.isConstant() && base.isConstantStride()) {
+ constantStrideSafe();
+ return true;
+ }
+ } catch (ArithmeticException e) {
+ // fall through to return false
+ }
+ return false;
}
@Override
public long constantInit() {
- return base.constantInit() * scale.asJavaConstant().asLong();
+ return constantInitSafe();
+ }
+
+ private long constantInitSafe() throws ArithmeticException {
+ return Math.multiplyExact(base.constantInit(), scale.asJavaConstant().asLong());
}
@Override
public long constantStride() {
- return base.constantStride() * scale.asJavaConstant().asLong();
+ return constantStrideSafe();
+ }
+
+ private long constantStrideSafe() throws ArithmeticException {
+ return Math.multiplyExact(base.constantStride(), scale.asJavaConstant().asLong());
}
@Override
- public ValueNode extremumNode(boolean assumeLoopEntered, Stamp stamp) {
- return mul(graph(), base.extremumNode(assumeLoopEntered, stamp), IntegerConvertNode.convert(scale, stamp, graph(), NodeView.DEFAULT));
+ public boolean isConstantExtremum() {
+ try {
+ if (scale.isConstant() && base.isConstantExtremum()) {
+ constantExtremumSafe();
+ return true;
+ }
+ } catch (ArithmeticException e) {
+ // fall through to return false
+ }
+ return false;
}
@Override
- public ValueNode extremumNode(boolean assumeLoopEntered, Stamp stamp, ValueNode maxTripCount) {
- return mul(graph(), base.extremumNode(assumeLoopEntered, stamp, maxTripCount), IntegerConvertNode.convert(scale, stamp, graph(), NodeView.DEFAULT));
+ public long constantExtremum() {
+ return constantExtremumSafe();
+ }
+
+ private long constantExtremumSafe() throws ArithmeticException {
+ return Math.multiplyExact(base.constantExtremum(), scale.asJavaConstant().asLong());
}
@Override
- public ValueNode exitValueNode() {
- return mul(graph(), base.exitValueNode(), scale);
+ public ValueNode extremumNode(boolean assumeLoopEntered, Stamp stamp) {
+ return mul(graph(), base.extremumNode(assumeLoopEntered, stamp), IntegerConvertNode.convert(scale, stamp, graph(), NodeView.DEFAULT));
}
@Override
- public boolean isConstantExtremum() {
- return scale.isConstant() && base.isConstantExtremum();
+ public ValueNode extremumNode(boolean assumeLoopEntered, Stamp stamp, ValueNode maxTripCount) {
+ return mul(graph(), base.extremumNode(assumeLoopEntered, stamp, maxTripCount), IntegerConvertNode.convert(scale, stamp, graph(), NodeView.DEFAULT));
}
@Override
- public long constantExtremum() {
- return base.constantExtremum() * scale.asJavaConstant().asLong();
+ public ValueNode exitValueNode() {
+ return mul(graph(), base.exitValueNode(), scale);
}
@Override
diff --git a/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/loop/LoopFragmentInside.java b/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/loop/LoopFragmentInside.java
index 34abba7e7d98..18b814e2b643 100644
--- a/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/loop/LoopFragmentInside.java
+++ b/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/loop/LoopFragmentInside.java
@@ -285,6 +285,8 @@ protected CompareNode placeNewSegmentAndCleanup(LoopEx loop, EconomicMap
+ * readWithNullCheck(object.a);
+ * read(object.b);
+ * read(object.c);
+ *
+ *
+ * In this pattern the first read is the null check for the dominated reads of b and c.
+ * Thus, the read of a must not be removed after fix reads phase even if it has no
+ * usages.
+ */
return null;
}
if (!getUsedAsNullCheck() && !extendsAccess()) {
diff --git a/compiler/src/org.graalvm.compiler.phases.common/src/org/graalvm/compiler/phases/common/CanonicalizerPhase.java b/compiler/src/org.graalvm.compiler.phases.common/src/org/graalvm/compiler/phases/common/CanonicalizerPhase.java
index ae865f66af86..cc15de67787c 100644
--- a/compiler/src/org.graalvm.compiler.phases.common/src/org/graalvm/compiler/phases/common/CanonicalizerPhase.java
+++ b/compiler/src/org.graalvm.compiler.phases.common/src/org/graalvm/compiler/phases/common/CanonicalizerPhase.java
@@ -646,10 +646,19 @@ private static boolean performReplacement(final Node node, Node newCanonical, To
node.replaceAtUsages(null);
GraphUtil.unlinkAndKillExceptionEdge(withException);
GraphUtil.killWithUnusedFloatingInputs(withException);
- } else if (canonical instanceof FloatingNode) {
+ } else if (canonical instanceof FloatingNode floating) {
// case 4
- withException.killExceptionEdge();
- graph.replaceSplitWithFloating(withException, (FloatingNode) canonical, withException.next());
+ /*
+ * In corner cases it is possible for the killing of the exception edge to
+ * trigger the killing of the replacement node. We therefore wait to kill
+ * the exception edge until after replacing the WithException node.
+ */
+ var exceptionEdge = withException.exceptionEdge();
+ withException.setExceptionEdge(null);
+ graph.replaceSplitWithFloating(withException, floating, withException.next());
+ if (exceptionEdge != null) {
+ GraphUtil.killCFG(exceptionEdge);
+ }
} else {
assert canonical instanceof FixedNode;
if (canonical.predecessor() == null) {
diff --git a/compiler/src/org.graalvm.compiler.phases/src/org/graalvm/compiler/phases/schedule/SchedulePhase.java b/compiler/src/org.graalvm.compiler.phases/src/org/graalvm/compiler/phases/schedule/SchedulePhase.java
index 967947d3c6d8..401deb18b098 100644
--- a/compiler/src/org.graalvm.compiler.phases/src/org/graalvm/compiler/phases/schedule/SchedulePhase.java
+++ b/compiler/src/org.graalvm.compiler.phases/src/org/graalvm/compiler/phases/schedule/SchedulePhase.java
@@ -578,7 +578,7 @@ protected void calcLatestBlock(HIRBlock earliestBlock, SchedulingStrategy strate
*/
continue;
}
- latestBlock = calcBlockForUsage(currentNode, usage, latestBlock, currentNodeMap, moveInputsIntoDominator);
+ latestBlock = calcLatestBlockForUsage(currentNode, usage, earliestBlock, latestBlock, currentNodeMap, moveInputsIntoDominator);
}
assert latestBlock != null : currentNode;
@@ -678,9 +678,10 @@ private static Node getUnproxifiedUncompressed(Node node) {
return result;
}
- private static HIRBlock calcBlockForUsage(Node node, Node usage, HIRBlock startBlock, NodeMap currentNodeMap, NodeBitMap moveInputsToDominator) {
+ private static HIRBlock calcLatestBlockForUsage(Node node, Node usage, HIRBlock earliestBlock, HIRBlock initialLatestBlock, NodeMap currentNodeMap,
+ NodeBitMap moveInputsToDominator) {
assert !(node instanceof PhiNode);
- HIRBlock currentBlock = startBlock;
+ HIRBlock currentBlock = initialLatestBlock;
if (usage instanceof PhiNode) {
// An input to a PhiNode is used at the end of the predecessor block that
// corresponds to the PhiNode input. One PhiNode can use an input multiple times.
@@ -702,7 +703,16 @@ private static HIRBlock calcBlockForUsage(Node node, Node usage, HIRBlock startB
}
if (!(node instanceof VirtualState) && !moveInputsToDominator.isNew(usage) && moveInputsToDominator.isMarked(usage)) {
- otherBlock = otherBlock.getDominator();
+ /*
+ * The usage is marked as forcing its inputs into the dominator. Respect that if
+ * we can, but the dominator might not be a legal position for the node. This is
+ * the case for loop-variant floating nodes between a loop phi and a virtual
+ * state on the loop begin.
+ */
+ HIRBlock dominator = otherBlock.getDominator();
+ if (AbstractControlFlowGraph.dominates(earliestBlock, dominator)) {
+ otherBlock = dominator;
+ }
GraalError.guarantee(otherBlock != null, "Dominators need to be computed in the CFG");
}
diff --git a/compiler/src/org.graalvm.compiler.phases/src/org/graalvm/compiler/phases/util/GraphOrder.java b/compiler/src/org.graalvm.compiler.phases/src/org/graalvm/compiler/phases/util/GraphOrder.java
index 5dd05724f255..ffe9c02843e0 100644
--- a/compiler/src/org.graalvm.compiler.phases/src/org/graalvm/compiler/phases/util/GraphOrder.java
+++ b/compiler/src/org.graalvm.compiler.phases/src/org/graalvm/compiler/phases/util/GraphOrder.java
@@ -29,6 +29,7 @@
import org.graalvm.collections.EconomicMap;
import org.graalvm.collections.Equivalence;
+import org.graalvm.compiler.debug.Assertions;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.debug.GraalError;
import org.graalvm.compiler.graph.GraalGraphError;
@@ -150,12 +151,24 @@ private static void visitForward(ArrayList nodes, NodeBitMap visited, Node
}
}
+ public static boolean assertSchedulableGraph(StructuredGraph g) {
+ assert GraphOrder.assertNonCyclicGraph(g);
+ assert g.getGuardsStage() == GuardsStage.AFTER_FSA || GraphOrder.assertScheduleableBeforeFSA(g);
+ if (g.getGuardsStage() == GuardsStage.AFTER_FSA && Assertions.detailedAssertionsEnabled(g.getOptions())) {
+ // we still want to do a memory verification of the schedule even if we can
+ // no longer use assertSchedulableGraph after the floating reads phase
+ SchedulePhase.runWithoutContextOptimizations(g, SchedulePhase.SchedulingStrategy.LATEST_OUT_OF_LOOPS, true);
+ }
+ assert g.verify();
+ return true;
+ }
+
/**
* This method schedules the graph and makes sure that, for every node, all inputs are available
* at the position where it is scheduled. This is a very expensive assertion.
*/
@SuppressWarnings("try")
- public static boolean assertSchedulableGraph(final StructuredGraph graph) {
+ private static boolean assertScheduleableBeforeFSA(final StructuredGraph graph) {
assert graph.getGuardsStage() != GuardsStage.AFTER_FSA : "Cannot use the BlockIteratorClosure after FrameState Assignment, HIR Loop Data Structures are no longer valid.";
try (DebugContext.Scope s = graph.getDebug().scope("AssertSchedulableGraph")) {
SchedulePhase.runWithoutContextOptimizations(graph, getSchedulingPolicy(graph), true);
diff --git a/compiler/src/org.graalvm.compiler.replacements.test/src/org/graalvm/compiler/replacements/test/HotSpotReflectionGetCallerClassTest.java b/compiler/src/org.graalvm.compiler.replacements.test/src/org/graalvm/compiler/replacements/test/HotSpotReflectionGetCallerClassTest.java
new file mode 100644
index 000000000000..3f0c393744d6
--- /dev/null
+++ b/compiler/src/org.graalvm.compiler.replacements.test/src/org/graalvm/compiler/replacements/test/HotSpotReflectionGetCallerClassTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2024, 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 org.graalvm.compiler.replacements.test;
+
+import org.junit.Test;
+
+public class HotSpotReflectionGetCallerClassTest extends MethodSubstitutionTest {
+ @Test
+ public void regressionTest() throws Exception {
+ test("getInstance");
+ }
+
+ // Replicates the code pattern from sun.net.ext.ExtendedSocketOptions.getInstance
+ // while avoiding a dependency on that class.
+ public static HotSpotReflectionGetCallerClassTest getInstance() {
+ HotSpotReflectionGetCallerClassTest ext = instance;
+ if (ext != null) {
+ return ext;
+ }
+ try {
+ Class.forName("org.graalvm.compiler.replacements.test.HotSpotReflectionGetCallerClassTest");
+ ext = instance;
+ } catch (ClassNotFoundException e) {
+ synchronized (HotSpotReflectionGetCallerClassTest.class) {
+ ext = instance;
+ if (ext != null) {
+ return ext;
+ }
+ ext = instance = new HotSpotReflectionGetCallerClassTest();
+ }
+ }
+ return ext;
+ }
+
+ private static HotSpotReflectionGetCallerClassTest instance;
+}
diff --git a/compiler/src/org.graalvm.compiler.replacements/src/org/graalvm/compiler/replacements/StandardGraphBuilderPlugins.java b/compiler/src/org.graalvm.compiler.replacements/src/org/graalvm/compiler/replacements/StandardGraphBuilderPlugins.java
index 5439cafd59e1..f9e9db445898 100644
--- a/compiler/src/org.graalvm.compiler.replacements/src/org/graalvm/compiler/replacements/StandardGraphBuilderPlugins.java
+++ b/compiler/src/org.graalvm.compiler.replacements/src/org/graalvm/compiler/replacements/StandardGraphBuilderPlugins.java
@@ -2317,8 +2317,16 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode x, ValueNode len, ValueNode z, ValueNode zlen) {
try (InvocationPluginHelper helper = new InvocationPluginHelper(b, targetMethod)) {
- b.add(new BigIntegerSquareToLenNode(helper.arrayStart(x, JavaKind.Int), len, helper.arrayStart(z, JavaKind.Int), zlen));
+ /*
+ * The intrinsified method takes the z array as a parameter, performs
+ * side-effects on its contents, then returns the same reference to z. Our
+ * intrinsic only performs the side-effects, we set z as the result directly.
+ * The stateAfter for the intrinsic should include this value on the stack, so
+ * push it first and only compute the state afterwards.
+ */
+ BigIntegerSquareToLenNode squareToLen = b.append(new BigIntegerSquareToLenNode(helper.arrayStart(x, JavaKind.Int), len, helper.arrayStart(z, JavaKind.Int), zlen));
b.push(JavaKind.Object, z);
+ b.setStateAfter(squareToLen);
return true;
}
}
diff --git a/espresso/mx.espresso/suite.py b/espresso/mx.espresso/suite.py
index 260a54830396..f2cca92e302d 100644
--- a/espresso/mx.espresso/suite.py
+++ b/espresso/mx.espresso/suite.py
@@ -23,7 +23,7 @@
suite = {
"mxversion": "6.17.0",
"name": "espresso",
- "version" : "23.0.3.1",
+ "version" : "23.0.4.0",
"release" : False,
"groupId" : "org.graalvm.espresso",
"url" : "https://www.graalvm.org/reference-manual/java-on-truffle/",
diff --git a/java-benchmarks/mx.java-benchmarks/mx_java_benchmarks.py b/java-benchmarks/mx.java-benchmarks/mx_java_benchmarks.py
index 4714ef318ea0..ba349933e9ba 100644
--- a/java-benchmarks/mx.java-benchmarks/mx_java_benchmarks.py
+++ b/java-benchmarks/mx.java-benchmarks/mx_java_benchmarks.py
@@ -408,7 +408,10 @@ def extra_image_build_argument(self, benchmark, args):
if mx.get_jdk().version < expectedJdkVersion:
mx.abort(benchmark + " needs at least JDK version " + str(expectedJdkVersion))
- return super(BaseTikaBenchmarkSuite, self).extra_image_build_argument(benchmark, args)
+ return [
+ # Workaround for wrong class initialization configuration in Quarkus Tika
+ '--initialize-at-build-time=org.apache.pdfbox.rendering.ImageType,org.apache.pdfbox.rendering.ImageType$1,org.apache.pdfbox.rendering.ImageType$2,org.apache.pdfbox.rendering.ImageType$3,org.apache.pdfbox.rendering.ImageType$4',
+ ] + super(BaseTikaBenchmarkSuite, self).extra_image_build_argument(benchmark, args)
class TikaWrkBenchmarkSuite(BaseTikaBenchmarkSuite, mx_sdk_benchmark.BaseWrkBenchmarkSuite):
@@ -478,6 +481,9 @@ def build_assertions(self, benchmark, is_gate):
def extra_image_build_argument(self, benchmark, args):
return [
'--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk=ALL-UNNAMED',
+ # Workaround for wrong class initialization configuration in Micronaut 3.9
+ '--initialize-at-build-time=io.netty.handler.codec.http.cookie.ServerCookieEncoder',
+ '--initialize-at-build-time=org.xml.sax.helpers.AttributesImpl,org.xml.sax.helpers.LocatorImpl',
] + super(BaseMicronautBenchmarkSuite, self).extra_image_build_argument(benchmark, args)
def default_stages(self):
diff --git a/regex/mx.regex/suite.py b/regex/mx.regex/suite.py
index 36ce98d16d77..bf4c37d9229b 100644
--- a/regex/mx.regex/suite.py
+++ b/regex/mx.regex/suite.py
@@ -43,7 +43,7 @@
"name" : "regex",
- "version" : "23.0.3.1",
+ "version" : "23.0.4.0",
"release" : False,
"groupId" : "org.graalvm.regex",
"url" : "http://www.graalvm.org/",
diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/RegexLanguage.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/RegexLanguage.java
index 4f36751d8817..51fb50958390 100644
--- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/RegexLanguage.java
+++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/RegexLanguage.java
@@ -40,6 +40,8 @@
*/
package com.oracle.truffle.regex;
+import org.graalvm.polyglot.SandboxPolicy;
+
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
@@ -116,7 +118,7 @@
* // result2.getStart(...) and result2.getEnd(...) are undefined
* }
*
- *
+ *
* Debug loggers: {@link com.oracle.truffle.regex.tregex.util.Loggers}.
*
* @see RegexOptions
@@ -130,6 +132,7 @@
contextPolicy = TruffleLanguage.ContextPolicy.SHARED, //
internal = true, //
interactive = false, //
+ sandbox = SandboxPolicy.UNTRUSTED, //
website = "https://github.com/oracle/graal/tree/master/regex")
@ProvidedTags(StandardTags.RootTag.class)
public final class RegexLanguage extends TruffleLanguage {
diff --git a/sdk/mx.sdk/suite.py b/sdk/mx.sdk/suite.py
index ade87d0a2a6a..8e241b46cbda 100644
--- a/sdk/mx.sdk/suite.py
+++ b/sdk/mx.sdk/suite.py
@@ -41,7 +41,7 @@
suite = {
"mxversion": "6.17.0",
"name" : "sdk",
- "version" : "23.0.3.1",
+ "version" : "23.0.4.0",
"release" : False,
"sourceinprojectwhitelist" : [],
"url" : "https://github.com/oracle/graal",
diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/Platform.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/Platform.java
index dee4d153f9ad..b95e2ca9fbb9 100644
--- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/Platform.java
+++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/Platform.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
@@ -179,7 +179,7 @@ interface LINUX extends InternalPlatform.PLATFORM_JNI, InternalPlatform.NATIVE_O
* @since 21.0
*/
default String getOS() {
- return LINUX.class.getSimpleName().toLowerCase();
+ return "linux";
}
}
@@ -196,7 +196,7 @@ interface ANDROID extends LINUX {
* @since 21.0
*/
default String getOS() {
- return ANDROID.class.getSimpleName().toLowerCase();
+ return "android";
}
}
@@ -221,7 +221,7 @@ interface IOS extends DARWIN {
* @since 21.0
*/
default String getOS() {
- return IOS.class.getSimpleName().toLowerCase();
+ return "ios";
}
}
@@ -255,7 +255,7 @@ interface WINDOWS extends InternalPlatform.PLATFORM_JNI, InternalPlatform.NATIVE
* @since 21.0
*/
default String getOS() {
- return WINDOWS.class.getSimpleName().toLowerCase();
+ return "windows";
}
}
diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py
index 92a5d25ba5fe..2c245b59282a 100644
--- a/substratevm/mx.substratevm/suite.py
+++ b/substratevm/mx.substratevm/suite.py
@@ -2,7 +2,7 @@
suite = {
"mxversion": "6.17.0",
"name": "substratevm",
- "version" : "23.0.3.1",
+ "version" : "23.0.4.0",
"release" : False,
"url" : "https://github.com/oracle/graal/tree/master/substratevm",
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java
index 748842490bfb..1b1ab635d6ba 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2023, 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
@@ -1184,14 +1184,14 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
DwarfRangesSectionImpl elfRangesSectionImpl = dwarfSections.getRangesSectionImpl();
DwarfLineSectionImpl elfLineSectionImpl = dwarfSections.getLineSectionImpl();
/* Now we can create the section elements with empty content. */
- newUserDefinedSection(elfStrSectionImpl.getSectionName(), elfStrSectionImpl);
- newUserDefinedSection(elfAbbrevSectionImpl.getSectionName(), elfAbbrevSectionImpl);
- newUserDefinedSection(frameSectionImpl.getSectionName(), frameSectionImpl);
- newUserDefinedSection(elfLocSectionImpl.getSectionName(), elfLocSectionImpl);
- newUserDefinedSection(elfInfoSectionImpl.getSectionName(), elfInfoSectionImpl);
- newUserDefinedSection(elfARangesSectionImpl.getSectionName(), elfARangesSectionImpl);
- newUserDefinedSection(elfRangesSectionImpl.getSectionName(), elfRangesSectionImpl);
- newUserDefinedSection(elfLineSectionImpl.getSectionName(), elfLineSectionImpl);
+ newDebugSection(elfStrSectionImpl.getSectionName(), elfStrSectionImpl);
+ newDebugSection(elfAbbrevSectionImpl.getSectionName(), elfAbbrevSectionImpl);
+ newDebugSection(frameSectionImpl.getSectionName(), frameSectionImpl);
+ newDebugSection(elfLocSectionImpl.getSectionName(), elfLocSectionImpl);
+ newDebugSection(elfInfoSectionImpl.getSectionName(), elfInfoSectionImpl);
+ newDebugSection(elfARangesSectionImpl.getSectionName(), elfARangesSectionImpl);
+ newDebugSection(elfRangesSectionImpl.getSectionName(), elfRangesSectionImpl);
+ newDebugSection(elfLineSectionImpl.getSectionName(), elfLineSectionImpl);
/*
* Add symbols for the base of all DWARF sections whose content may need to be referenced
* using a section global offset. These need to be written using a base relative reloc so
diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/tracing/TraceFileWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/tracing/TraceFileWriter.java
index 8dc3bdf164fd..391bd6e20ba8 100644
--- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/tracing/TraceFileWriter.java
+++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/tracing/TraceFileWriter.java
@@ -98,13 +98,13 @@ private static void printArray(JsonWriter json, Object[] array) throws IOExcepti
}
private static void printValue(JsonWriter json, Object value) throws IOException {
- String s = null;
+ Object s = null;
if (value instanceof byte[]) {
s = Base64.getEncoder().encodeToString((byte[]) value);
} else if (value != null) {
- s = value.toString();
+ s = value;
}
- json.quote(s);
+ json.printValue(s);
}
private void traceEntry(String s) throws IOException {
diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/command/ConfigurationGenerateCommand.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/command/ConfigurationGenerateCommand.java
index 173a443c33ce..07f7f1e43010 100644
--- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/command/ConfigurationGenerateCommand.java
+++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/command/ConfigurationGenerateCommand.java
@@ -145,6 +145,7 @@ protected static void generate(Iterator argumentsIterator, boolean accep
boolean builtinCallerFilter = true;
boolean builtinHeuristicFilter = true;
List callerFilters = new ArrayList<>();
+ List accessFilters = new ArrayList<>();
ConfigurationFileCollection omittedCollection = new ConfigurationFileCollection();
ConfigurationFileCollection inputCollection = new ConfigurationFileCollection();
@@ -219,6 +220,9 @@ protected static void generate(Iterator argumentsIterator, boolean accep
case "--caller-filter-file":
callerFilters.add(requirePathUri(option, value));
break;
+ case "--access-filter-file":
+ accessFilters.add(requirePathUri(option, value));
+ break;
case "--":
if (acceptTraceFileArgs) {
argumentsIterator.forEachRemaining(arg -> traceInputs.add(Paths.get(arg).toUri()));
@@ -247,15 +251,12 @@ protected static void generate(Iterator argumentsIterator, boolean accep
callersFilterHierarchyFilterNode = AccessAdvisor.copyBuiltinCallerFilterTree();
callersFilter = new ComplexFilter(callersFilterHierarchyFilterNode);
}
- for (URI uri : callerFilters) {
- try {
- FilterConfigurationParser parser = new FilterConfigurationParser(callersFilter);
- parser.parseAndRegister(uri);
- } catch (Exception e) {
- throw new ConfigurationUsageException("Cannot parse filter file " + uri + ": " + e);
- }
- }
- callersFilter.getHierarchyFilterNode().removeRedundantNodes();
+ parseFilterFiles(callersFilter, callerFilters);
+ }
+ ComplexFilter accessFilter = null;
+ if (!accessFilters.isEmpty()) {
+ accessFilter = new ComplexFilter(AccessAdvisor.copyBuiltinAccessFilterTree());
+ parseFilterFiles(accessFilter, accessFilters);
}
ConfigurationSet configurationSet;
@@ -286,6 +287,9 @@ protected static void generate(Iterator argumentsIterator, boolean accep
if (callersFilter != null) {
advisor.setCallerFilterTree(callersFilter);
}
+ if (accessFilter != null) {
+ advisor.setAccessFilterTree(accessFilter);
+ }
TraceProcessor processor = new TraceProcessor(advisor);
for (URI uri : traceInputs) {
@@ -330,6 +334,17 @@ protected static void generate(Iterator argumentsIterator, boolean accep
}
}
+ private static void parseFilterFiles(ComplexFilter filter, List filterFiles) {
+ for (URI uri : filterFiles) {
+ try {
+ new FilterConfigurationParser(filter).parseAndRegister(uri);
+ } catch (Exception e) {
+ throw new ConfigurationUsageException("Cannot parse filter file " + uri + ": " + e);
+ }
+ }
+ filter.getHierarchyFilterNode().removeRedundantNodes();
+ }
+
@SuppressWarnings("fallthrough")
private static void failIfAgentLockFilesPresent(ConfigurationFileCollection... collections) {
Set paths = null;
diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AggressiveShrinkCollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AggressiveShrinkCollectionPolicy.java
index a7132140e4b0..5c006955e4e9 100644
--- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AggressiveShrinkCollectionPolicy.java
+++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AggressiveShrinkCollectionPolicy.java
@@ -29,7 +29,7 @@
import org.graalvm.word.WordFactory;
import com.oracle.svm.core.heap.GCCause;
-import com.oracle.svm.core.option.HostedOptionKey;
+import com.oracle.svm.core.option.RuntimeOptionKey;
import com.oracle.svm.core.util.UnsignedUtils;
/**
@@ -41,14 +41,16 @@ class AggressiveShrinkCollectionPolicy extends AdaptiveCollectionPolicy {
public static final class Options {
@Option(help = "Ratio of used bytes to total allocated bytes for eden space. Setting it to a smaller value " +
"will trade more triggered hinted GCs for less resident set size.") //
- public static final HostedOptionKey UsedEdenProportionThreshold = new HostedOptionKey<>(0.75D);
+ public static final RuntimeOptionKey UsedEdenProportionThreshold = new RuntimeOptionKey<>(0.75D);
@Option(help = "Soft upper limit for used eden size. The hinted GC will be performed if the used eden size " +
"exceeds this value.") //
- public static final HostedOptionKey ExpectedEdenSize = new HostedOptionKey<>(32 * 1024 * 1024);
+ public static final RuntimeOptionKey ExpectedEdenSize = new RuntimeOptionKey<>(32L * 1024L * 1024L);
}
- protected static final UnsignedWord INITIAL_HEAP_SIZE = WordFactory.unsigned(64 * 1024 * 1024);
- protected static final UnsignedWord FULL_GC_BONUS = WordFactory.unsigned(2 * 1024 * 1024);
+ protected static final UnsignedWord INITIAL_HEAP_SIZE = WordFactory.unsigned(64L * 1024L * 1024L);
+ protected static final UnsignedWord FULL_GC_BONUS = WordFactory.unsigned(2L * 1024L * 1024L);
+
+ protected static final UnsignedWord MAXIMUM_HEAP_SIZE = WordFactory.unsigned(16L * 1024L * 1024L * 1024L);
private UnsignedWord sizeBefore = WordFactory.zero();
private GCCause lastGCCause = null;
@@ -73,7 +75,7 @@ public boolean shouldCollectOnRequest(GCCause cause, boolean fullGC) {
// memory usage point.
edenUsedBytes = edenUsedBytes.add(FULL_GC_BONUS);
}
- return edenUsedBytes.aboveOrEqual(Options.ExpectedEdenSize.getValue()) ||
+ return edenUsedBytes.aboveOrEqual(WordFactory.unsigned(Options.ExpectedEdenSize.getValue())) ||
(UnsignedUtils.toDouble(edenUsedBytes) / UnsignedUtils.toDouble(edenSize) >= Options.UsedEdenProportionThreshold.getValue());
}
return super.shouldCollectOnRequest(cause, fullGC);
@@ -84,6 +86,15 @@ protected UnsignedWord getInitialHeapSize() {
return INITIAL_HEAP_SIZE;
}
+ @Override
+ public UnsignedWord getMaximumHeapSize() {
+ UnsignedWord initialSetup = super.getMaximumHeapSize();
+ if (initialSetup.aboveThan(MAXIMUM_HEAP_SIZE)) {
+ return MAXIMUM_HEAP_SIZE;
+ }
+ return initialSetup;
+ }
+
@Override
public void onCollectionBegin(boolean completeCollection, long requestingNanoTime) {
sizeBefore = GCImpl.getChunkBytes();
@@ -125,7 +136,10 @@ protected void computeEdenSpaceSize(boolean completeCollection, GCCause cause) {
} else {
UnsignedWord sizeAfter = GCImpl.getChunkBytes();
if (sizeBefore.notEqual(0) && sizeBefore.belowThan(sizeAfter.multiply(2))) {
- edenSize = alignUp(edenSize.multiply(2));
+ UnsignedWord newEdenSize = UnsignedUtils.min(getMaximumEdenSize(), alignUp(edenSize.multiply(2)));
+ if (edenSize.belowThan(newEdenSize)) {
+ edenSize = newEdenSize;
+ }
} else {
super.computeEdenSpaceSize(completeCollection, cause);
}
diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkProvider.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkProvider.java
index 6db2eec7d7bc..52696391504c 100644
--- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkProvider.java
+++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkProvider.java
@@ -24,7 +24,6 @@
*/
package com.oracle.svm.core.genscavenge;
-import com.oracle.svm.core.heap.OutOfMemoryUtil;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
import org.graalvm.word.Pointer;
@@ -32,14 +31,15 @@
import org.graalvm.word.WordBase;
import org.graalvm.word.WordFactory;
+import com.oracle.svm.core.AlwaysInline;
import com.oracle.svm.core.FrameAccess;
import com.oracle.svm.core.MemoryWalker;
import com.oracle.svm.core.SubstrateOptions;
-import com.oracle.svm.core.AlwaysInline;
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.genscavenge.AlignedHeapChunk.AlignedHeader;
import com.oracle.svm.core.genscavenge.HeapChunk.Header;
import com.oracle.svm.core.genscavenge.UnalignedHeapChunk.UnalignedHeader;
+import com.oracle.svm.core.heap.OutOfMemoryUtil;
import com.oracle.svm.core.jdk.UninterruptibleUtils;
import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicUnsigned;
import com.oracle.svm.core.log.Log;
diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java
index f41076dd6ed1..c2e30ce30532 100644
--- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java
+++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java
@@ -32,6 +32,7 @@
import org.graalvm.compiler.api.replacements.Fold;
import org.graalvm.compiler.core.common.NumUtil;
import org.graalvm.compiler.core.common.SuppressFBWarnings;
+import org.graalvm.compiler.nodes.extended.MembarNode;
import org.graalvm.compiler.nodes.memory.address.OffsetAddressNode;
import org.graalvm.compiler.word.Word;
import org.graalvm.nativeimage.CurrentIsolate;
@@ -42,17 +43,16 @@
import org.graalvm.word.UnsignedWord;
import com.oracle.svm.core.MemoryWalker;
+import com.oracle.svm.core.NeverInline;
import com.oracle.svm.core.SubstrateDiagnostics;
import com.oracle.svm.core.SubstrateDiagnostics.DiagnosticThunk;
import com.oracle.svm.core.SubstrateDiagnostics.DiagnosticThunkRegistry;
import com.oracle.svm.core.SubstrateDiagnostics.ErrorContext;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.SubstrateUtil;
-import com.oracle.svm.core.NeverInline;
-import com.oracle.svm.core.heap.RestrictHeapAccess;
+import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
-import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.config.ConfigurationValues;
import com.oracle.svm.core.genscavenge.AlignedHeapChunk.AlignedHeader;
import com.oracle.svm.core.genscavenge.ThreadLocalAllocation.Descriptor;
@@ -69,6 +69,7 @@
import com.oracle.svm.core.heap.ReferenceHandler;
import com.oracle.svm.core.heap.ReferenceHandlerThread;
import com.oracle.svm.core.heap.ReferenceInternals;
+import com.oracle.svm.core.heap.RestrictHeapAccess;
import com.oracle.svm.core.heap.RuntimeCodeInfoGCSupport;
import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicReference;
import com.oracle.svm.core.locks.VMCondition;
@@ -290,39 +291,42 @@ void logImageHeapPartitionBoundaries(Log log) {
log.string("Native image heap boundaries:").indent(true);
imageHeapInfo.print(log);
log.indent(false);
+
+ if (AuxiliaryImageHeap.isPresent()) {
+ ImageHeapInfo auxHeapInfo = AuxiliaryImageHeap.singleton().getImageHeapInfo();
+ if (auxHeapInfo != null) {
+ log.string("Auxiliary image heap boundaries:").indent(true);
+ auxHeapInfo.print(log);
+ log.indent(false);
+ }
+ }
}
/** Log the zap values to make it easier to search for them. */
- static Log zapValuesToLog(Log log) {
+ static void logZapValues(Log log) {
if (HeapParameters.getZapProducedHeapChunks() || HeapParameters.getZapConsumedHeapChunks()) {
- log.string("[Heap Chunk zap values: ").indent(true);
/* Padded with spaces so the columns line up between the int and word variants. */
if (HeapParameters.getZapProducedHeapChunks()) {
- log.string(" producedHeapChunkZapInt: ")
- .string(" hex: ").spaces(8).hex(HeapParameters.getProducedHeapChunkZapInt())
- .string(" signed: ").spaces(9).signed(HeapParameters.getProducedHeapChunkZapInt())
- .string(" unsigned: ").spaces(10).unsigned(HeapParameters.getProducedHeapChunkZapInt()).newline();
- log.string(" producedHeapChunkZapWord:")
- .string(" hex: ").hex(HeapParameters.getProducedHeapChunkZapWord())
- .string(" signed: ").signed(HeapParameters.getProducedHeapChunkZapWord())
- .string(" unsigned: ").unsigned(HeapParameters.getProducedHeapChunkZapWord());
- if (HeapParameters.getZapConsumedHeapChunks()) {
- log.newline();
- }
+ log.string("producedHeapChunkZapInt: ")
+ .string(" hex: ").spaces(8).hex(HeapParameters.getProducedHeapChunkZapInt())
+ .string(" signed: ").spaces(9).signed(HeapParameters.getProducedHeapChunkZapInt())
+ .string(" unsigned: ").spaces(10).unsigned(HeapParameters.getProducedHeapChunkZapInt()).newline();
+ log.string("producedHeapChunkZapWord:")
+ .string(" hex: ").hex(HeapParameters.getProducedHeapChunkZapWord())
+ .string(" signed: ").signed(HeapParameters.getProducedHeapChunkZapWord())
+ .string(" unsigned: ").unsigned(HeapParameters.getProducedHeapChunkZapWord()).newline();
}
if (HeapParameters.getZapConsumedHeapChunks()) {
- log.string(" consumedHeapChunkZapInt: ")
- .string(" hex: ").spaces(8).hex(HeapParameters.getConsumedHeapChunkZapInt())
- .string(" signed: ").spaces(10).signed(HeapParameters.getConsumedHeapChunkZapInt())
- .string(" unsigned: ").spaces(10).unsigned(HeapParameters.getConsumedHeapChunkZapInt()).newline();
- log.string(" consumedHeapChunkZapWord:")
- .string(" hex: ").hex(HeapParameters.getConsumedHeapChunkZapWord())
- .string(" signed: ").signed(HeapParameters.getConsumedHeapChunkZapWord())
- .string(" unsigned: ").unsigned(HeapParameters.getConsumedHeapChunkZapWord());
+ log.string("consumedHeapChunkZapInt: ")
+ .string(" hex: ").spaces(8).hex(HeapParameters.getConsumedHeapChunkZapInt())
+ .string(" signed: ").spaces(10).signed(HeapParameters.getConsumedHeapChunkZapInt())
+ .string(" unsigned: ").spaces(10).unsigned(HeapParameters.getConsumedHeapChunkZapInt()).newline();
+ log.string("consumedHeapChunkZapWord:")
+ .string(" hex: ").hex(HeapParameters.getConsumedHeapChunkZapWord())
+ .string(" signed: ").signed(HeapParameters.getConsumedHeapChunkZapWord())
+ .string(" unsigned: ").unsigned(HeapParameters.getConsumedHeapChunkZapWord()).newline();
}
- log.redent(false).string("]");
}
- return log;
}
@Override
@@ -337,6 +341,9 @@ protected List> getAllClasses() {
ArrayList> list = new ArrayList<>(1024);
ImageHeapWalker.walkRegions(imageHeapInfo, new ClassListBuilderVisitor(list));
list.trimToSize();
+
+ /* Ensure that other threads see consistent values once the list is published. */
+ MembarNode.memoryBarrier(MembarNode.FenceKind.STORE_STORE);
classList = list;
}
assert classList.size() == imageHeapInfo.dynamicHubCount;
@@ -444,12 +451,6 @@ public int getImageHeapNullRegionSize() {
return 0;
}
- @Fold
- @Override
- public boolean allowPageSizeMismatch() {
- return true;
- }
-
@Override
public boolean walkImageHeapObjects(ObjectVisitor visitor) {
VMOperation.guaranteeInProgressAtSafepoint("Must only be called at a safepoint");
@@ -645,7 +646,7 @@ public boolean printLocationInfo(Log log, UnsignedWord value, boolean allowJavaH
if (printLocationInfo(log, ptr, allowJavaHeapAccess, allowUnsafeOperations)) {
if (allowJavaHeapAccess && objectHeaderImpl.pointsToObjectHeader(ptr)) {
log.indent(true);
- SubstrateDiagnostics.printObjectInfo(log, ptr);
+ SubstrateDiagnostics.printObjectInfo(log, ptr.toObject());
log.redent(false);
}
return true;
@@ -689,7 +690,7 @@ public long getThreadAllocatedMemory(IsolateThread thread) {
@Override
@Uninterruptible(reason = "Ensure that no GC can occur between modification of the object and this call.", callerMustBe = true)
public void dirtyAllReferencesOf(Object obj) {
- if (obj != null) {
+ if (SubstrateOptions.useRememberedSet() && obj != null) {
ForcedSerialPostWriteBarrier.force(OffsetAddressNode.address(obj, 0), false);
}
}
@@ -866,19 +867,23 @@ public int maxInvocationCount() {
@Override
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.")
public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) {
- GCImpl gc = GCImpl.getGCImpl();
-
log.string("Heap settings and statistics:").indent(true);
log.string("Supports isolates: ").bool(SubstrateOptions.SpawnIsolates.getValue()).newline();
if (SubstrateOptions.SpawnIsolates.getValue()) {
log.string("Heap base: ").zhex(KnownIntrinsics.heapBase()).newline();
}
log.string("Object reference size: ").signed(ConfigurationValues.getObjectLayout().getReferenceSize()).newline();
+ log.string("Reserved object header bits: 0b").number(Heap.getHeap().getObjectHeader().getReservedBitsMask(), 2, false).newline();
+
log.string("Aligned chunk size: ").unsigned(HeapParameters.getAlignedHeapChunkSize()).newline();
+ log.string("Large array threshold: ").unsigned(HeapParameters.getLargeArrayThreshold()).newline();
- GCAccounting accounting = gc.getAccounting();
+ GCAccounting accounting = GCImpl.getGCImpl().getAccounting();
log.string("Incremental collections: ").unsigned(accounting.getIncrementalCollectionCount()).newline();
log.string("Complete collections: ").unsigned(accounting.getCompleteCollectionCount()).newline();
+
+ logZapValues(log);
+
log.indent(false);
}
}
@@ -894,7 +899,8 @@ public int maxInvocationCount() {
public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) {
HeapImpl heap = HeapImpl.getHeapImpl();
heap.logImageHeapPartitionBoundaries(log);
- zapValuesToLog(log).newline();
+ log.newline();
+
heap.report(log, true);
log.newline();
}
diff --git a/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/SubstrateAArch64Backend.java b/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/SubstrateAArch64Backend.java
index 9ef88c2646d0..2b82aa5e9b66 100755
--- a/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/SubstrateAArch64Backend.java
+++ b/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/SubstrateAArch64Backend.java
@@ -293,7 +293,7 @@ public void emitCode(CompilationResultBuilder crb, AArch64MacroAssembler masm) {
Register immediateScratch = sc1.getRegister();
Register addressScratch = sc2.getRegister();
- CompressEncoding compressEncoding = ReferenceAccess.singleton().getCompressEncoding();
+ int compressionShift = ReferenceAccess.singleton().getCompressionShift();
Register computeRegister = asRegister(addressBase);
int addressBitSize = addressBase.getPlatformKind().getSizeInBytes() * Byte.SIZE;
boolean nextMemoryAccessNeedsDecompress = false;
@@ -317,7 +317,7 @@ public void emitCode(CompilationResultBuilder crb, AArch64MacroAssembler masm) {
* currently in use: references are relative to the heap base register,
* with an optional shift.
*/
- masm.add(64, addressScratch, ReservedRegisters.singleton().getHeapBaseRegister(), computeRegister, ShiftType.LSL, compressEncoding.getShift());
+ masm.add(64, addressScratch, ReservedRegisters.singleton().getHeapBaseRegister(), computeRegister, ShiftType.LSL, compressionShift);
memoryAddress = masm.makeAddress(addressBitSize, addressScratch, field.getOffset(), immediateScratch);
} else {
memoryAddress = masm.makeAddress(addressBitSize, computeRegister, field.getOffset(), immediateScratch);
@@ -346,7 +346,7 @@ public void emitCode(CompilationResultBuilder crb, AArch64MacroAssembler masm) {
} else {
masm.mov(addressScratch, 0xDEADDEADDEADDEADL, true);
}
- masm.add(64, addressScratch, ReservedRegisters.singleton().getHeapBaseRegister(), addressScratch, ShiftType.LSL, compressEncoding.getShift());
+ masm.add(64, addressScratch, ReservedRegisters.singleton().getHeapBaseRegister(), addressScratch, ShiftType.LSL, compressionShift);
memoryAddress = masm.makeAddress(addressBitSize, addressScratch, field.getOffset(), immediateScratch);
} else {
memoryAddress = masm.makeAddress(addressBitSize, ReservedRegisters.singleton().getHeapBaseRegister(), field.getOffset() + constantReflection.getImageHeapOffset(object),
diff --git a/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java b/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java
index d791b3a9fe33..1ba419235451 100644
--- a/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java
+++ b/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java
@@ -322,7 +322,7 @@ public SubstrateAMD64ComputedIndirectCallOp(ResolvedJavaMethod callTarget, Value
public void emitCode(CompilationResultBuilder crb, AMD64MacroAssembler masm) {
VMError.guarantee(SubstrateOptions.SpawnIsolates.getValue(), "Memory access without isolates is not implemented");
- CompressEncoding compressEncoding = ReferenceAccess.singleton().getCompressEncoding();
+ int compressionShift = ReferenceAccess.singleton().getCompressionShift();
Register computeRegister = asRegister(addressBase);
AMD64BaseAssembler.OperandSize lastOperandSize = AMD64BaseAssembler.OperandSize.get(addressBase.getPlatformKind());
boolean nextMemoryAccessNeedsDecompress = false;
@@ -346,7 +346,7 @@ public void emitCode(CompilationResultBuilder crb, AMD64MacroAssembler masm) {
* an optional shift that is known to be a valid addressing mode.
*/
memoryAddress = new AMD64Address(ReservedRegisters.singleton().getHeapBaseRegister(),
- computeRegister, Stride.fromLog2(compressEncoding.getShift()),
+ computeRegister, Stride.fromLog2(compressionShift),
field.getOffset());
} else {
memoryAddress = new AMD64Address(computeRegister, field.getOffset());
diff --git a/substratevm/src/com.oracle.svm.core.graal.llvm/src/com/oracle/svm/core/graal/llvm/LLVMNativeImageCodeCache.java b/substratevm/src/com.oracle.svm.core.graal.llvm/src/com/oracle/svm/core/graal/llvm/LLVMNativeImageCodeCache.java
index d152e278bd8d..e4e50e6840a1 100644
--- a/substratevm/src/com.oracle.svm.core.graal.llvm/src/com/oracle/svm/core/graal/llvm/LLVMNativeImageCodeCache.java
+++ b/substratevm/src/com.oracle.svm.core.graal.llvm/src/com/oracle/svm/core/graal/llvm/LLVMNativeImageCodeCache.java
@@ -145,7 +145,7 @@ public void layoutMethods(DebugContext debug, BigBang bb, ForkJoinPool threadPoo
compileBitcodeBatches(executor, debug, numBatches);
}
try (StopTimer t = TimerCollection.createTimerAndStart("(postlink)")) {
- linkCompiledBatches(executor, debug, numBatches);
+ linkCompiledBatches(debug, threadPool, executor, numBatches);
}
}
}
@@ -216,7 +216,7 @@ private void compileBitcodeBatches(BatchExecutor executor, DebugContext debug, i
});
}
- private void linkCompiledBatches(BatchExecutor executor, DebugContext debug, int numBatches) {
+ private void linkCompiledBatches(DebugContext debug, ForkJoinPool threadPool, BatchExecutor executor, int numBatches) {
List compiledBatches = IntStream.range(0, numBatches).mapToObj(this::getBatchCompiledFilename).collect(Collectors.toList());
nativeLink(debug, getLinkedFilename(), compiledBatches);
@@ -240,7 +240,7 @@ private void linkCompiledBatches(BatchExecutor executor, DebugContext debug, int
llvmCleanupStackMaps(debug, getLinkedFilename());
codeAreaSize = textSectionInfo.getCodeSize();
- buildRuntimeMetadata(new MethodPointer(getFirstCompilation().getLeft()), WordFactory.signed(codeAreaSize));
+ buildRuntimeMetadata(threadPool, new MethodPointer(getFirstCompilation().getLeft()), WordFactory.signed(codeAreaSize));
}
private void llvmOptimize(DebugContext debug, String outputPath, String inputPath) {
diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSegfaultHandler.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSegfaultHandler.java
index 1c1cdd0c4abe..1084a4459db2 100644
--- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSegfaultHandler.java
+++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSegfaultHandler.java
@@ -28,6 +28,7 @@
import org.graalvm.nativeimage.c.function.CEntryPoint.Publish;
import org.graalvm.nativeimage.c.function.CEntryPointLiteral;
import org.graalvm.nativeimage.c.struct.SizeOf;
+import org.graalvm.nativeimage.c.type.VoidPointer;
import org.graalvm.word.PointerBase;
import org.graalvm.word.WordFactory;
@@ -65,6 +66,9 @@ private static void dispatch(@SuppressWarnings("unused") int signalNumber, @Supp
dump(sigInfo, uContext);
throw VMError.shouldNotReachHere();
}
+
+ /* Attach failed - kill the process because the segfault handler must not return. */
+ LibC.abort();
}
@Override
@@ -77,7 +81,10 @@ protected void printSignalInfo(Log log, PointerBase signalInfo) {
if (sigInfo.si_errno() != 0) {
log.string(", si_errno: ").signed(sigInfo.si_errno());
}
- log.string(", si_addr: ").zhex(sigInfo.si_addr());
+
+ VoidPointer addr = sigInfo.si_addr();
+ log.string(", si_addr: ");
+ printSegfaultAddressInfo(log, addr.rawValue());
log.newline();
}
}
diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Fcntl.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Fcntl.java
index 185b9a2b9c76..4c06021f338e 100644
--- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Fcntl.java
+++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Fcntl.java
@@ -65,7 +65,7 @@ public static class NoTransitions {
@CFunction(value = "openSII", transition = NO_TRANSITION)
public static native int open(CCharPointer pathname, int flags, int mode);
- @CFunction(transition = NO_TRANSITION)
+ @CFunction(value = "openatISII", transition = NO_TRANSITION)
public static native int openat(int dirfd, @CConst CCharPointer pathname, int flags, int mode);
@CFunction(transition = NO_TRANSITION)
diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/pthread/PthreadConditionUtils.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/pthread/PthreadConditionUtils.java
index c66f1da01112..4d177086aee1 100644
--- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/pthread/PthreadConditionUtils.java
+++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/pthread/PthreadConditionUtils.java
@@ -87,7 +87,7 @@ public static int initConditionWithRelativeTime(pthread_cond_t cond) {
if (status == 0) {
try {
if (useMonotonicClockForRelativeWait()) {
- LinuxPthread.pthread_condattr_setclock(attr, LinuxTime.CLOCK_MONOTONIC());
+ status = LinuxPthread.pthread_condattr_setclock(attr, LinuxTime.CLOCK_MONOTONIC());
if (status != 0) {
return status;
}
diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/thread/PosixPlatformThreads.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/thread/PosixPlatformThreads.java
index fdc2cc341257..de01408eecb1 100644
--- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/thread/PosixPlatformThreads.java
+++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/thread/PosixPlatformThreads.java
@@ -26,17 +26,12 @@
import org.graalvm.compiler.core.common.SuppressFBWarnings;
import org.graalvm.nativeimage.ImageSingletons;
-import org.graalvm.nativeimage.ObjectHandle;
import org.graalvm.nativeimage.Platform.HOSTED_ONLY;
import org.graalvm.nativeimage.Platforms;
import org.graalvm.nativeimage.StackValue;
import org.graalvm.nativeimage.UnmanagedMemory;
-import org.graalvm.nativeimage.c.function.CEntryPoint;
-import org.graalvm.nativeimage.c.function.CEntryPoint.Publish;
-import org.graalvm.nativeimage.c.function.CEntryPointLiteral;
import org.graalvm.nativeimage.c.function.CFunctionPointer;
import org.graalvm.nativeimage.c.struct.SizeOf;
-import org.graalvm.nativeimage.c.type.CCharPointer;
import org.graalvm.nativeimage.c.type.CTypeConversion;
import org.graalvm.nativeimage.c.type.CTypeConversion.CCharPointerHolder;
import org.graalvm.nativeimage.c.type.WordPointer;
@@ -44,19 +39,13 @@
import org.graalvm.word.Pointer;
import org.graalvm.word.PointerBase;
import org.graalvm.word.UnsignedWord;
-import org.graalvm.word.WordBase;
import org.graalvm.word.WordFactory;
+import com.oracle.svm.core.NeverInline;
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.annotate.Inject;
import com.oracle.svm.core.annotate.RecomputeFieldValue;
import com.oracle.svm.core.annotate.TargetClass;
-import com.oracle.svm.core.c.CGlobalData;
-import com.oracle.svm.core.c.CGlobalDataFactory;
-import com.oracle.svm.core.c.function.CEntryPointActions;
-import com.oracle.svm.core.c.function.CEntryPointErrors;
-import com.oracle.svm.core.c.function.CEntryPointOptions;
-import com.oracle.svm.core.c.function.CEntryPointSetup.LeaveDetachThreadEpilogue;
import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton;
import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue;
import com.oracle.svm.core.os.IsDefined;
@@ -76,6 +65,7 @@
import com.oracle.svm.core.thread.Parker;
import com.oracle.svm.core.thread.Parker.ParkerFactory;
import com.oracle.svm.core.thread.PlatformThreads;
+import com.oracle.svm.core.thread.VMThreads.OSThreadHandle;
import com.oracle.svm.core.util.UnsignedUtils;
import com.oracle.svm.core.util.VMError;
@@ -117,18 +107,34 @@ protected boolean doStartThread(Thread thread, long stackSize) {
}
}
- ThreadStartData startData = prepareStart(thread, SizeOf.get(ThreadStartData.class));
+ /*
+ * Prevent stack overflow errors so that starting the thread and reverting back to a
+ * safe state (in case of an error) works reliably.
+ */
+ StackOverflowCheck.singleton().makeYellowZoneAvailable();
+ try {
+ return doStartThread0(thread, attributes);
+ } finally {
+ StackOverflowCheck.singleton().protectYellowZone();
+ }
+ } finally {
+ Pthread.pthread_attr_destroy(attributes);
+ }
+ }
+ /** Starts a thread to the point so that it is executing. */
+ @NeverInline("Workaround for GR-51925 - prevent that reads float from this method into the caller.")
+ private boolean doStartThread0(Thread thread, pthread_attr_t attributes) {
+ ThreadStartData startData = prepareStart(thread, SizeOf.get(ThreadStartData.class));
+ try {
Pthread.pthread_tPointer newThread = UnsafeStackValue.get(Pthread.pthread_tPointer.class);
- if (Pthread.pthread_create(newThread, attributes, PosixPlatformThreads.pthreadStartRoutine.getFunctionPointer(), startData) != 0) {
+ if (Pthread.pthread_create(newThread, attributes, threadStartRoutine.getFunctionPointer(), startData) != 0) {
undoPrepareStartOnError(thread, startData);
return false;
}
-
- setPthreadIdentifier(thread, newThread.read());
return true;
- } finally {
- Pthread.pthread_attr_destroy(attributes);
+ } catch (Throwable e) {
+ throw VMError.shouldNotReachHere("No exception must be thrown after creating the thread start data.", e);
}
}
@@ -154,8 +160,8 @@ static boolean hasThreadIdentifier(Thread thread) {
protected void setNativeName(Thread thread, String name) {
if (!hasThreadIdentifier(thread)) {
/*
- * The thread was not started from Java code, but started from C code and attached
- * manually to SVM. We do not want to interfere with such threads.
+ * The thread was a. not started yet, b. not started from Java code (i.e., only attached
+ * to SVM). We do not want to interfere with such threads.
*/
return;
}
@@ -186,32 +192,11 @@ protected void yieldCurrent() {
Sched.sched_yield();
}
- private static final CEntryPointLiteral pthreadStartRoutine = CEntryPointLiteral.create(PosixPlatformThreads.class, "pthreadStartRoutine", ThreadStartData.class);
-
- private static class PthreadStartRoutinePrologue implements CEntryPointOptions.Prologue {
- private static final CGlobalData errorMessage = CGlobalDataFactory.createCString("Failed to attach a newly launched thread.");
-
- @SuppressWarnings("unused")
- @Uninterruptible(reason = "prologue")
- static void enter(ThreadStartData data) {
- int code = CEntryPointActions.enterAttachThread(data.getIsolate(), true, false);
- if (code != CEntryPointErrors.NO_ERROR) {
- CEntryPointActions.failFatally(code, errorMessage.get());
- }
- }
- }
-
- @CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = Publish.NotPublished)
- @CEntryPointOptions(prologue = PthreadStartRoutinePrologue.class, epilogue = LeaveDetachThreadEpilogue.class)
- static WordBase pthreadStartRoutine(ThreadStartData data) {
- ObjectHandle threadHandle = data.getThreadHandle();
- freeStartData(data);
-
- threadStartRoutine(threadHandle);
-
- return WordFactory.nullPointer();
- }
-
+ /**
+ * Note that this method is only executed for Java threads that are started via
+ * {@link Thread#start()}. It is not executed if an existing native thread is attached to an
+ * isolate.
+ */
@Override
protected void beforeThreadRun(Thread thread) {
/* Complete the initialization of the thread, now that it is (nearly) running. */
@@ -254,7 +239,7 @@ public OSThreadHandle startThreadUnmanaged(CFunctionPointer threadRoutine, Point
return WordFactory.nullPointer();
}
- return (OSThreadHandle) newThread.read();
+ return newThread.read();
} finally {
Pthread.pthread_attr_destroy_no_transition(attributes);
}
@@ -266,13 +251,6 @@ public boolean joinThreadUnmanaged(OSThreadHandle threadHandle, WordPointer thre
int status = Pthread.pthread_join_no_transition((Pthread.pthread_t) threadHandle, threadExitStatus);
return status == 0;
}
-
- @Override
- @SuppressWarnings("unused")
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public void closeOSThreadHandle(OSThreadHandle threadHandle) {
- // pthread_t doesn't need closing
- }
}
@TargetClass(Thread.class)
diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformThreads.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformThreads.java
index bc3a406c228a..bcea0d4764f0 100644
--- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformThreads.java
+++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformThreads.java
@@ -24,37 +24,24 @@
*/
package com.oracle.svm.core.windows;
-import org.graalvm.nativeimage.ObjectHandle;
+import org.graalvm.compiler.core.common.NumUtil;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platform.HOSTED_ONLY;
import org.graalvm.nativeimage.Platforms;
-import org.graalvm.nativeimage.c.function.CEntryPoint;
-import org.graalvm.nativeimage.c.function.CEntryPoint.Publish;
-import org.graalvm.nativeimage.c.function.CEntryPointLiteral;
import org.graalvm.nativeimage.c.function.CFunctionPointer;
-import org.graalvm.nativeimage.c.struct.RawField;
-import org.graalvm.nativeimage.c.struct.RawStructure;
import org.graalvm.nativeimage.c.struct.SizeOf;
-import org.graalvm.nativeimage.c.type.CCharPointer;
import org.graalvm.nativeimage.c.type.CIntPointer;
import org.graalvm.nativeimage.c.type.WordPointer;
import org.graalvm.word.PointerBase;
-import org.graalvm.word.WordBase;
import org.graalvm.word.WordFactory;
import com.oracle.svm.core.Uninterruptible;
-import com.oracle.svm.core.c.CGlobalData;
-import com.oracle.svm.core.c.CGlobalDataFactory;
-import com.oracle.svm.core.c.function.CEntryPointActions;
-import com.oracle.svm.core.c.function.CEntryPointErrors;
-import com.oracle.svm.core.c.function.CEntryPointOptions;
-import com.oracle.svm.core.c.function.CEntryPointSetup.LeaveDetachThreadEpilogue;
import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton;
-import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue;
import com.oracle.svm.core.stack.StackOverflowCheck;
import com.oracle.svm.core.thread.Parker;
import com.oracle.svm.core.thread.Parker.ParkerFactory;
import com.oracle.svm.core.thread.PlatformThreads;
+import com.oracle.svm.core.thread.VMThreads.OSThreadHandle;
import com.oracle.svm.core.util.TimeUtils;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.core.windows.headers.Process;
@@ -70,28 +57,39 @@ public final class WindowsPlatformThreads extends PlatformThreads {
@Override
protected boolean doStartThread(Thread thread, long stackSize) {
- int threadStackSize = (int) stackSize;
- int initFlag = Process.CREATE_SUSPENDED();
-
- WindowsThreadStartData startData = prepareStart(thread, SizeOf.get(WindowsThreadStartData.class));
-
+ int threadStackSize = NumUtil.safeToUInt(stackSize);
+ int initFlag = 0;
// If caller specified a stack size, don't commit it all at once.
if (threadStackSize != 0) {
initFlag |= Process.STACK_SIZE_PARAM_IS_A_RESERVATION();
}
- CIntPointer osThreadID = UnsafeStackValue.get(CIntPointer.class);
- WinBase.HANDLE osThreadHandle = Process._beginthreadex(WordFactory.nullPointer(), threadStackSize,
- WindowsPlatformThreads.osThreadStartRoutine.getFunctionPointer(), startData, initFlag, osThreadID);
- if (osThreadHandle.isNull()) {
- undoPrepareStartOnError(thread, startData);
- return false;
+ /*
+ * Prevent stack overflow errors so that starting the thread and reverting back to a safe
+ * state (in case of an error) works reliably.
+ */
+ StackOverflowCheck.singleton().makeYellowZoneAvailable();
+ try {
+ return doStartThread0(thread, threadStackSize, initFlag);
+ } finally {
+ StackOverflowCheck.singleton().protectYellowZone();
}
- startData.setOSThreadHandle(osThreadHandle);
+ }
- // Start the thread running
- Process.ResumeThread(osThreadHandle);
- return true;
+ /** Starts a thread to the point so that it is executing. */
+ private boolean doStartThread0(Thread thread, int threadStackSize, int initFlag) {
+ ThreadStartData startData = prepareStart(thread, SizeOf.get(ThreadStartData.class));
+ try {
+ WinBase.HANDLE osThreadHandle = Process._beginthreadex(WordFactory.nullPointer(), threadStackSize, threadStartRoutine.getFunctionPointer(), startData, initFlag, WordFactory.nullPointer());
+ if (osThreadHandle.isNull()) {
+ undoPrepareStartOnError(thread, startData);
+ return false;
+ }
+ WinBase.CloseHandle(osThreadHandle);
+ return true;
+ } catch (Throwable e) {
+ throw VMError.shouldNotReachHere("No exception must be thrown after creating the thread start data.", e);
+ }
}
@Override
@@ -106,7 +104,7 @@ public OSThreadHandle startThreadUnmanaged(CFunctionPointer threadRoutine, Point
WinBase.HANDLE osThreadHandle = Process.NoTransitions._beginthreadex(WordFactory.nullPointer(), stackSize,
threadRoutine, userData, initFlag, WordFactory.nullPointer());
- return (PlatformThreads.OSThreadHandle) osThreadHandle;
+ return (OSThreadHandle) osThreadHandle;
}
@Override
@@ -142,51 +140,6 @@ protected void setNativeName(Thread thread, String name) {
protected void yieldCurrent() {
Process.SwitchToThread();
}
-
- @RawStructure
- interface WindowsThreadStartData extends ThreadStartData {
-
- @RawField
- WinBase.HANDLE getOSThreadHandle();
-
- @RawField
- void setOSThreadHandle(WinBase.HANDLE osHandle);
- }
-
- private static final CEntryPointLiteral osThreadStartRoutine = CEntryPointLiteral.create(WindowsPlatformThreads.class, "osThreadStartRoutine", WindowsThreadStartData.class);
-
- private static class OSThreadStartRoutinePrologue implements CEntryPointOptions.Prologue {
- private static final CGlobalData errorMessage = CGlobalDataFactory.createCString("Failed to attach a newly launched thread.");
-
- @SuppressWarnings("unused")
- @Uninterruptible(reason = "prologue")
- static void enter(WindowsThreadStartData data) {
- int code = CEntryPointActions.enterAttachThread(data.getIsolate(), true, false);
- if (code != CEntryPointErrors.NO_ERROR) {
- CEntryPointActions.failFatally(code, errorMessage.get());
- }
- }
- }
-
- @CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = Publish.NotPublished)
- @CEntryPointOptions(prologue = OSThreadStartRoutinePrologue.class, epilogue = LeaveDetachThreadEpilogue.class)
- static WordBase osThreadStartRoutine(WindowsThreadStartData data) {
- ObjectHandle threadHandle = data.getThreadHandle();
- WinBase.HANDLE osThreadHandle = data.getOSThreadHandle();
- freeStartData(data);
-
- try {
- threadStartRoutine(threadHandle);
- } finally {
- /*
- * Note that there is another handle to the thread stored in VMThreads.OSThreadHandleTL.
- * This is necessary to ensure that the operating system does not release the thread
- * resources too early.
- */
- WinBase.CloseHandle(osThreadHandle);
- }
- return WordFactory.nullPointer();
- }
}
/**
diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSubstrateSegfaultHandler.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSubstrateSegfaultHandler.java
index 57c374592a62..11ab74a7ff05 100644
--- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSubstrateSegfaultHandler.java
+++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSubstrateSegfaultHandler.java
@@ -75,8 +75,7 @@ protected void installInternal() {
}
}
- private static final CEntryPointLiteral HANDLER_LITERAL = CEntryPointLiteral.create(WindowsSubstrateSegfaultHandler.class,
- "handler", ErrHandlingAPI.EXCEPTION_POINTERS.class);
+ private static final CEntryPointLiteral HANDLER_LITERAL = CEntryPointLiteral.create(WindowsSubstrateSegfaultHandler.class, "handler", ErrHandlingAPI.EXCEPTION_POINTERS.class);
@CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = Publish.NotPublished)
@CEntryPointOptions(prologue = NoPrologue.class, epilogue = NoEpilogue.class)
@@ -94,7 +93,11 @@ private static int handler(ErrHandlingAPI.EXCEPTION_POINTERS exceptionInfo) {
dump(exceptionInfo, context);
throw shouldNotReachHere();
}
- /* Nothing we can do. */
+
+ /*
+ * Attach failed - nothing we need to do. If there is no other OS-level exception handler
+ * installed, then Windows will abort the process.
+ */
return ErrHandlingAPI.EXCEPTION_CONTINUE_SEARCH();
}
@@ -119,7 +122,8 @@ protected void printSignalInfo(Log log, PointerBase signalInfo) {
} else {
log.string(", ExceptionInformation=").zhex(operation);
}
- log.string(" ").zhex(exInfo.addressOf(1).read());
+ log.string(" ");
+ printSegfaultAddressInfo(log, exInfo.addressOf(1).read());
} else {
if (numParameters > 0) {
log.string(", ExceptionInformation=");
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/CPUFeatureAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/CPUFeatureAccess.java
index 722895055c59..3ba98a900645 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/CPUFeatureAccess.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/CPUFeatureAccess.java
@@ -26,9 +26,17 @@
import java.util.EnumSet;
+import org.graalvm.compiler.api.replacements.Fold;
+import org.graalvm.nativeimage.ImageSingletons;
+
import jdk.vm.ci.code.Architecture;
public interface CPUFeatureAccess {
+ @Fold
+ static CPUFeatureAccess singleton() {
+ return ImageSingletons.lookup(CPUFeatureAccess.class);
+ }
+
int verifyHostSupportsArchitectureEarly();
void verifyHostSupportsArchitectureEarlyOrExit();
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DebugHelper.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DebugHelper.java
index 6520d15983ed..1b348b5cf279 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DebugHelper.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DebugHelper.java
@@ -264,7 +264,7 @@ public static void printHub(@SuppressWarnings("unused") IsolateThread thread, Po
@CEntryPoint(name = "svm_dbg_print_obj", include = IncludeDebugHelperMethods.class, publishAs = Publish.SymbolOnly)
@CEntryPointOptions(prologue = SetThreadAndHeapBasePrologue.class, epilogue = NoEpilogue.class)
public static void printObject(@SuppressWarnings("unused") IsolateThread thread, Pointer objPtr) {
- SubstrateDiagnostics.printObjectInfo(Log.log(), objPtr);
+ SubstrateDiagnostics.printObjectInfo(Log.log(), objPtr.toObject());
}
@Uninterruptible(reason = "Called with a raw object pointer.", calleeMustBe = false)
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentParser.java
index 73d52698a480..9adaa07d8008 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentParser.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentParser.java
@@ -183,8 +183,22 @@ public void persistOptions(CLongPointer parsedArgs) {
public void verifyOptionValues() {
for (int i = 0; i < OPTION_COUNT; i++) {
- validate(OPTIONS[i], getOptionValue(i));
+ RuntimeOptionKey> option = OPTIONS[i];
+ if (shouldValidate(option)) {
+ validate(option, getOptionValue(i));
+ }
+ }
+ }
+
+ private static boolean shouldValidate(RuntimeOptionKey> option) {
+ if (!option.hasBeenSet()) {
+ /* Workaround for one specific Truffle language that does something weird. */
+ return false;
+ } else if (SubstrateOptions.UseSerialGC.getValue()) {
+ /* The serial GC supports changing the heap size at run-time to some degree. */
+ return option != SubstrateGCOptions.MinHeapSize && option != SubstrateGCOptions.MaxHeapSize && option != SubstrateGCOptions.MaxNewSize;
}
+ return true;
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
@@ -218,12 +232,10 @@ private static Object getOptionValue(int index) {
}
private static void validate(RuntimeOptionKey> option, Object oldValue) {
- if (option.hasBeenSet()) {
- Object newValue = option.getValue();
- if (newValue == null || !newValue.equals(oldValue)) {
- throw new IllegalArgumentException(
- "The option '" + option.getName() + "' can't be changed after isolate creation. Old value: " + oldValue + ", new value: " + newValue);
- }
+ Object newValue = option.getValue();
+ if (newValue == null || !newValue.equals(oldValue)) {
+ throw new IllegalArgumentException(
+ "The option '" + option.getName() + "' can't be changed after isolate creation. Old value: " + oldValue + ", new value: " + newValue);
}
}
@@ -332,6 +344,8 @@ private static boolean atojulong(CCharPointer s, CLongPointer result) {
}
CCharPointerPointer tailPtr = (CCharPointerPointer) StackValue.get(CCharPointer.class);
+
+ LibC.setErrno(0);
UnsignedWord n = LibC.strtoull(s, tailPtr, 10);
if (LibC.errno() != 0) {
return false;
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/Isolates.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/Isolates.java
index 6d2ecb783f73..a1dbaef5fe6f 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/Isolates.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/Isolates.java
@@ -58,6 +58,8 @@ public class Isolates {
public static final CGlobalData IMAGE_HEAP_WRITABLE_BEGIN = CGlobalDataFactory.forSymbol(IMAGE_HEAP_WRITABLE_BEGIN_SYMBOL_NAME);
public static final CGlobalData IMAGE_HEAP_WRITABLE_END = CGlobalDataFactory.forSymbol(IMAGE_HEAP_WRITABLE_END_SYMBOL_NAME);
+ private static long startTimeMillis;
+ private static long startNanoTime;
private static Boolean isCurrentFirst;
/**
@@ -77,6 +79,31 @@ public static void setCurrentIsFirstIsolate(boolean value) {
isCurrentFirst = value;
}
+ public static void assignCurrentStartTime() {
+ assert startTimeMillis == 0 : startTimeMillis;
+ assert startNanoTime == 0 : startNanoTime;
+ startTimeMillis = System.currentTimeMillis();
+ startNanoTime = System.nanoTime();
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static long getCurrentStartTimeMillis() {
+ assert startTimeMillis != 0;
+ return startTimeMillis;
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static long getCurrentUptimeMillis() {
+ assert startTimeMillis != 0;
+ return System.currentTimeMillis() - startTimeMillis;
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static long getCurrentStartNanoTime() {
+ assert startNanoTime != 0;
+ return startNanoTime;
+ }
+
@Uninterruptible(reason = "Thread state not yet set up.")
public static int checkIsolate(Isolate isolate) {
if (SubstrateOptions.SpawnIsolates.getValue()) {
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java
index e05ac0ac79c1..c8240293ef87 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java
@@ -71,9 +71,10 @@
import com.oracle.svm.core.log.Log;
import com.oracle.svm.core.thread.JavaThreads;
import com.oracle.svm.core.thread.PlatformThreads;
-import com.oracle.svm.core.thread.ThreadListenerSupport;
+import com.oracle.svm.core.thread.ThreadingSupportImpl;
import com.oracle.svm.core.thread.VMThreads;
-import com.oracle.svm.core.util.Counter;
+import com.oracle.svm.core.util.CounterSupport;
+import com.oracle.svm.core.thread.VMThreads.OSThreadHandle;
import com.oracle.svm.core.util.VMError;
@InternalVMMethod
@@ -164,8 +165,6 @@ private static int runCore0() {
}
}
- ThreadListenerSupport.get().beforeThreadRun();
-
// Ensure that native code using JNI_GetCreatedJavaVMs finds this isolate.
JNIJavaVMList.addJavaVM(JNIFunctionTables.singleton().getGlobalJavaVM());
@@ -191,22 +190,32 @@ private static int runCore0() {
@Uninterruptible(reason = "The caller initialized the thread state, so the callees do not need to be uninterruptible.", calleeMustBe = false)
private static void runShutdown() {
+ ThreadingSupportImpl.pauseRecurringCallback("Recurring callbacks can't be executed during shutdown.");
runShutdown0();
}
private static void runShutdown0() {
- PlatformThreads.ensureCurrentAssigned("DestroyJavaVM", null, false);
+ try {
+ PlatformThreads.ensureCurrentAssigned("DestroyJavaVM", null, false);
+ } catch (Throwable e) {
+ Log.log().string("PlatformThreads.ensureCurrentAssigned() failed during shutdown: ").exception(e).newline();
+ return;
+ }
- // Shutdown sequence: First wait for all non-daemon threads to exit.
+ /* Wait for all non-daemon threads to exit. */
PlatformThreads.singleton().joinAllNonDaemons();
- /*
- * Run shutdown hooks (both our own hooks and application-registered hooks. Note that this
- * can start new non-daemon threads. We are not responsible to wait until they have exited.
- */
- RuntimeSupport.getRuntimeSupport().shutdown();
-
- Counter.logValues(Log.log());
+ try {
+ /*
+ * Run shutdown hooks (both our own hooks and application-registered hooks). Note that
+ * this can start new non-daemon threads. We are not responsible to wait until they have
+ * exited.
+ */
+ RuntimeSupport.getRuntimeSupport().shutdown();
+ CounterSupport.singleton().logValues(Log.log());
+ } catch (Throwable e) {
+ Log.log().string("Exception occurred while executing shutdown hooks: ").exception(e).newline();
+ }
}
@Uninterruptible(reason = "Thread state not set up yet.")
@@ -225,6 +234,7 @@ private static int doRun(int argc, CCharPointerPointer argv) {
try {
CPUFeatureAccess cpuFeatureAccess = ImageSingletons.lookup(CPUFeatureAccess.class);
cpuFeatureAccess.verifyHostSupportsArchitectureEarlyOrExit();
+
// Create the isolate and attach the current C thread as the main Java thread.
EnterCreateIsolateWithCArgumentsPrologue.enter(argc, argv);
assert !VMThreads.wasStartedByCurrentIsolate(CurrentIsolate.getCurrentThread()) : "re-attach would cause issues otherwise";
@@ -253,7 +263,7 @@ private static int doRunInNewThread(int argc, CCharPointerPointer argv) {
MAIN_ISOLATE_PARAMETERS.get().setArgc(argc);
MAIN_ISOLATE_PARAMETERS.get().setArgv(argv);
long stackSize = SubstrateOptions.StackSize.getHostedValue();
- PlatformThreads.OSThreadHandle osThreadHandle = PlatformThreads.singleton().startThreadUnmanaged(RUN_MAIN_ROUTINE.get(), WordFactory.nullPointer(), (int) stackSize);
+ OSThreadHandle osThreadHandle = PlatformThreads.singleton().startThreadUnmanaged(RUN_MAIN_ROUTINE.get(), WordFactory.nullPointer(), (int) stackSize);
if (osThreadHandle.isNull()) {
CEntryPointActions.failFatally(1, START_THREAD_UNMANAGED_ERROR_MESSAGE.get());
return 1;
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java
index a0840126a6df..1f6ee7bf506b 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java
@@ -26,6 +26,7 @@
import static com.oracle.svm.core.option.RuntimeOptionKey.RuntimeOptionKeyFlag.RelevantForCompilationIsolates;
+import java.util.ArrayList;
import java.util.Arrays;
import org.graalvm.collections.EconomicMap;
@@ -33,9 +34,11 @@
import org.graalvm.compiler.core.common.NumUtil;
import org.graalvm.compiler.core.common.SuppressFBWarnings;
import org.graalvm.compiler.nodes.PauseNode;
+import org.graalvm.compiler.nodes.java.ArrayLengthNode;
import org.graalvm.compiler.options.Option;
import org.graalvm.compiler.options.OptionKey;
import org.graalvm.compiler.options.OptionType;
+import org.graalvm.compiler.word.ObjectAccess;
import org.graalvm.compiler.word.Word;
import org.graalvm.nativeimage.CurrentIsolate;
import org.graalvm.nativeimage.ImageSingletons;
@@ -68,12 +71,17 @@
import com.oracle.svm.core.graal.RuntimeCompilation;
import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue;
import com.oracle.svm.core.heap.Heap;
+import com.oracle.svm.core.heap.PhysicalMemory;
import com.oracle.svm.core.heap.RestrictHeapAccess;
import com.oracle.svm.core.hub.DynamicHub;
+import com.oracle.svm.core.hub.LayoutEncoding;
+import com.oracle.svm.core.jdk.Jvm;
+import com.oracle.svm.core.jdk.UninterruptibleUtils;
import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicWord;
import com.oracle.svm.core.locks.VMLockSupport;
import com.oracle.svm.core.log.Log;
import com.oracle.svm.core.option.RuntimeOptionKey;
+import com.oracle.svm.core.os.VirtualMemoryProvider;
import com.oracle.svm.core.stack.JavaFrameAnchor;
import com.oracle.svm.core.stack.JavaFrameAnchors;
import com.oracle.svm.core.stack.JavaStackWalker;
@@ -89,7 +97,9 @@
import com.oracle.svm.core.threadlocal.FastThreadLocalBytes;
import com.oracle.svm.core.threadlocal.FastThreadLocalFactory;
import com.oracle.svm.core.threadlocal.VMThreadLocalInfos;
-import com.oracle.svm.core.util.Counter;
+import com.oracle.svm.core.util.CounterSupport;
+import com.oracle.svm.core.util.TimeUtils;
+import com.oracle.svm.core.util.VMError;
public class SubstrateDiagnostics {
private static final int MAX_THREADS_TO_PRINT = 100_000;
@@ -144,16 +154,59 @@ public static void printLocationInfo(Log log, UnsignedWord value, boolean allowJ
}
}
- @Uninterruptible(reason = "Called with a raw object pointer.", calleeMustBe = false)
- public static void printObjectInfo(Log log, Pointer ptr) {
- DynamicHub objHub = Heap.getHeap().getObjectHeader().readDynamicHubFromPointer(ptr);
- if (objHub == DynamicHub.fromClass(DynamicHub.class)) {
- // The pointer is already a hub, so print some information about the hub.
- DynamicHub hub = (DynamicHub) ptr.toObject();
+ public static void printObjectInfo(Log log, Object obj) {
+ if (obj instanceof DynamicHub hub) {
+ // The object itself is a dynamic hub, so print some information about the hub.
log.string("is the hub of ").string(hub.getName());
+ return;
+ }
+
+ // Print some information about the object.
+ log.string("is an object of type ");
+
+ if (obj instanceof Throwable e) {
+ log.exception(e, 2);
} else {
- // The pointer is an object, so print some information about the object's hub.
- log.string("is an object of type ").string(objHub.getName());
+ log.string(obj.getClass().getName());
+
+ if (obj instanceof String s) {
+ log.string(": ").string(s, 60);
+ } else {
+ int layoutEncoding = DynamicHub.fromClass(obj.getClass()).getLayoutEncoding();
+
+ if (LayoutEncoding.isArrayLike(layoutEncoding)) {
+ int length = ArrayLengthNode.arrayLength(obj);
+ log.string(" with length ").signed(length).string(": ");
+
+ printSomeArrayData(log, obj, length, layoutEncoding);
+ }
+ }
+ }
+ }
+
+ private static void printSomeArrayData(Log log, Object obj, int length, int layoutEncoding) {
+ int elementSize = LayoutEncoding.getArrayIndexScale(layoutEncoding);
+ int arrayBaseOffset = LayoutEncoding.getArrayBaseOffsetAsInt(layoutEncoding);
+
+ int maxPrintedBytes = elementSize == 1 ? 8 : 16;
+ int printedElements = UninterruptibleUtils.Math.min(length, maxPrintedBytes / elementSize);
+
+ UnsignedWord curOffset = WordFactory.unsigned(arrayBaseOffset);
+ UnsignedWord endOffset = curOffset.add(WordFactory.unsigned(printedElements).multiply(elementSize));
+ while (curOffset.belowThan(endOffset)) {
+ switch (elementSize) {
+ case 1 -> log.zhex(ObjectAccess.readByte(obj, curOffset));
+ case 2 -> log.zhex(ObjectAccess.readShort(obj, curOffset));
+ case 4 -> log.zhex(ObjectAccess.readInt(obj, curOffset));
+ case 8 -> log.zhex(ObjectAccess.readLong(obj, curOffset));
+ default -> throw VMError.shouldNotReachHere();
+ }
+ log.spaces(1);
+ curOffset = curOffset.add(elementSize);
+ }
+
+ if (printedElements < length) {
+ log.string("...");
}
}
@@ -213,15 +266,6 @@ public static boolean printFatalError(Log log, Pointer sp, CodePointer ip) {
*/
public static boolean printFatalError(Log log, Pointer sp, CodePointer ip, RegisterDumper.Context registerContext, boolean frameHasCalleeSavedRegisters) {
log.newline();
- /*
- * Save the state of the initial error so that this state is consistently used, even if
- * further errors occur while printing diagnostics.
- */
- if (!fatalErrorState().trySet(log, sp, ip, registerContext, frameHasCalleeSavedRegisters) && !isFatalErrorHandlingThread()) {
- log.string("Error: printFatalError already in progress by another thread.").newline();
- log.newline();
- return false;
- }
/*
* Execute an endless loop if requested. This makes it easier to attach a debugger lazily.
@@ -232,6 +276,16 @@ public static boolean printFatalError(Log log, Pointer sp, CodePointer ip, Regis
PauseNode.pause();
}
+ /*
+ * Save the state of the initial error so that this state is consistently used, even if
+ * further errors occur while printing diagnostics.
+ */
+ if (!fatalErrorState().trySet(log, sp, ip, registerContext, frameHasCalleeSavedRegisters) && !isFatalErrorHandlingThread()) {
+ log.string("Error: printFatalError already in progress by another thread.").newline();
+ log.newline();
+ return false;
+ }
+
printFatalErrorForCurrentState();
return true;
}
@@ -242,10 +296,12 @@ private static void printFatalErrorForCurrentState() {
FatalErrorState fatalErrorState = fatalErrorState();
Log log = fatalErrorState.log;
- if (fatalErrorState.diagnosticThunkIndex > 0) {
+ if (fatalErrorState.diagnosticThunkIndex >= 0) {
// An error must have happened earlier as the code for printing diagnostics was invoked
// recursively.
log.resetIndentation().newline();
+ } else {
+ fatalErrorState.diagnosticThunkIndex = 0;
}
// Print the various sections of the diagnostics and skip all sections that were already
@@ -393,7 +449,7 @@ private static void updateInitialInvocationCount(String entry) throws IllegalArg
}
if (matches == 0) {
- throw new IllegalArgumentException("the pattern '" + entry + "' not match any diagnostic thunk.");
+ throw new IllegalArgumentException("The pattern '" + entry + "' not match any diagnostic thunk.");
}
}
@@ -412,7 +468,7 @@ private static int parseInvocationCount(String entry, int pos) {
}
private static boolean matches(String text, String pattern) {
- assert pattern.length() > 0;
+ assert pattern.length() > 0 : pattern;
return matches(text, 0, pattern, 0);
}
@@ -450,6 +506,31 @@ private static boolean matches(String text, int t, String pattern, int p) {
return patternPos == pattern.length();
}
+ /* Scan the stack until we find a valid return address. We may encounter false-positives. */
+ private static Pointer findPotentialReturnAddressPosition(Pointer originalSp) {
+ UnsignedWord stackBase = VMThreads.StackBase.get();
+ if (stackBase.equal(0)) {
+ /* We don't know the stack boundaries, so only search within 32 bytes. */
+ stackBase = originalSp.add(32);
+ }
+
+ int wordSize = ConfigurationValues.getTarget().wordSize;
+ Pointer pos = originalSp;
+ while (pos.belowThan(stackBase)) {
+ CodePointer possibleIp = pos.readWord(0);
+ if (pointsIntoNativeImageCode(possibleIp)) {
+ return pos;
+ }
+ pos = pos.add(wordSize);
+ }
+ return WordFactory.nullPointer();
+ }
+
+ @Uninterruptible(reason = "Prevent the GC from freeing the CodeInfo.")
+ private static boolean pointsIntoNativeImageCode(CodePointer possibleIp) {
+ return CodeInfoTable.lookupCodeInfo(possibleIp).isNonNull();
+ }
+
public static class FatalErrorState {
AtomicWord diagnosticThread;
volatile int diagnosticThunkIndex;
@@ -460,7 +541,7 @@ public static class FatalErrorState {
@Platforms(Platform.HOSTED_ONLY.class)
public FatalErrorState() {
diagnosticThread = new AtomicWord<>();
- diagnosticThunkIndex = 0;
+ diagnosticThunkIndex = -1;
invocationCount = 0;
log = null;
@@ -475,7 +556,7 @@ public ErrorContext getErrorContext() {
@SuppressWarnings("hiding")
public boolean trySet(Log log, Pointer sp, CodePointer ip, RegisterDumper.Context registerContext, boolean frameHasCalleeSavedRegisters) {
if (diagnosticThread.compareAndSet(WordFactory.nullPointer(), CurrentIsolate.getCurrentThread())) {
- assert diagnosticThunkIndex == 0;
+ assert diagnosticThunkIndex == -1;
assert invocationCount == 0;
this.log = log;
@@ -498,26 +579,13 @@ public void clear() {
errorContext.setRegisterContext(WordFactory.nullPointer());
errorContext.setFrameHasCalleeSavedRegisters(false);
- diagnosticThunkIndex = 0;
+ diagnosticThunkIndex = -1;
invocationCount = 0;
diagnosticThread.set(WordFactory.nullPointer());
}
}
- private static class DumpCurrentTimestamp extends DiagnosticThunk {
- @Override
- public int maxInvocationCount() {
- return 1;
- }
-
- @Override
- @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.")
- public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) {
- log.string("Current timestamp: ").unsigned(System.currentTimeMillis()).newline().newline();
- }
- }
-
private static class DumpRegisters extends DiagnosticThunk {
@Override
public int maxInvocationCount() {
@@ -543,34 +611,48 @@ public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLev
private static class DumpInstructions extends DiagnosticThunk {
@Override
public int maxInvocationCount() {
- return 3;
+ return 4;
}
@Override
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.")
public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) {
- if (invocationCount < 3) {
- printBytesBeforeAndAfterIp(log, context.getInstructionPointer(), invocationCount);
- } else if (invocationCount == 3) {
- printWord(log, context.getInstructionPointer());
- }
- }
+ CodePointer ip = context.getInstructionPointer();
+ log.string("Printing instructions (ip=").zhex(ip).string("):");
- private static void printBytesBeforeAndAfterIp(Log log, CodePointer ip, int invocationCount) {
- // print 64 or 32 instruction bytes.
- int bytesToPrint = 64 >> invocationCount;
- hexDump(log, ip, bytesToPrint, bytesToPrint);
- }
+ if (((Pointer) ip).belowThan(VirtualMemoryProvider.get().getGranularity())) {
+ /* IP points into the first page of the virtual address space. */
+ Pointer originalSp = context.getStackPointer();
+ log.string(" IP is invalid");
- private static void printWord(Log log, CodePointer ip) {
- // just print one word starting at the ip
- hexDump(log, ip, 0, ConfigurationValues.getTarget().wordSize);
+ Pointer returnAddressPos = findPotentialReturnAddressPosition(originalSp);
+ if (returnAddressPos.isNull()) {
+ log.string(", instructions cannot be printed.").newline();
+ return;
+ }
+
+ ip = returnAddressPos.readWord(0);
+ Pointer sp = returnAddressPos.add(FrameAccess.returnAddressSize());
+ log.string(", printing instructions (ip=").zhex(ip).string(") of the most likely caller (sp + ").unsigned(sp.subtract(originalSp)).string(") instead");
+ }
+
+ log.indent(true);
+ if (invocationCount < 4) {
+ /* Print 512, 128, or 32 instruction bytes. */
+ int bytesToPrint = 1024 >> (invocationCount * 2);
+ hexDump(log, ip, bytesToPrint, bytesToPrint);
+ } else if (invocationCount == 4) {
+ /* Just print one word starting at the ip. */
+ hexDump(log, ip, 0, ConfigurationValues.getTarget().wordSize);
+ }
+ log.indent(false).newline();
}
private static void hexDump(Log log, CodePointer ip, int bytesBefore, int bytesAfter) {
- log.string("Printing Instructions (ip=").zhex(ip).string("):").indent(true);
- log.hexdump(((Pointer) ip).subtract(bytesBefore), 1, bytesBefore + bytesAfter);
- log.indent(false).newline();
+ log.hexdump(((Pointer) ip).subtract(bytesBefore), 1, bytesBefore);
+ log.indent(false);
+ log.string("> ").redent(true);
+ log.hexdump(ip, 1, bytesAfter);
}
}
@@ -583,30 +665,42 @@ public int maxInvocationCount() {
@Override
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.")
public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) {
+ /*
+ * We have to be careful here and not dump too much of the stack: if there are not many
+ * frames on the stack, we segfault when going past the beginning of the stack.
+ */
Pointer sp = context.getStackPointer();
- UnsignedWord stackBase = VMThreads.StackBase.get();
- log.string("Top of stack (sp=").zhex(sp).string("):").indent(true);
-
- int bytesToPrint = computeBytesToPrint(sp, stackBase);
- log.hexdump(sp, 8, bytesToPrint / 8);
- log.indent(false).newline();
- }
+ UnsignedWord stackEnd = VMThreads.StackEnd.get(); // low
+ UnsignedWord stackBase = VMThreads.StackBase.get(); // high
+ int wordSize = ConfigurationValues.getTarget().wordSize;
- private static int computeBytesToPrint(Pointer sp, UnsignedWord stackBase) {
- if (stackBase.equal(0)) {
- /*
- * We have to be careful here and not dump too much of the stack: if there are not
- * many frames on the stack, we segfault when going past the beginning of the stack.
- */
- return 128;
+ log.string("Top of stack (sp=").zhex(sp).string("):").indent(true);
+ int bytesToPrintBelowSp = 32;
+ if (stackEnd.notEqual(0)) {
+ UnsignedWord availableBytes = sp.subtract(stackEnd);
+ if (availableBytes.belowThan(bytesToPrintBelowSp)) {
+ bytesToPrintBelowSp = NumUtil.safeToInt(availableBytes.rawValue());
+ }
}
- int bytesToPrint = 512;
- UnsignedWord availableBytes = stackBase.subtract(sp);
- if (availableBytes.belowThan(bytesToPrint)) {
- bytesToPrint = NumUtil.safeToInt(availableBytes.rawValue());
+ int bytesToPrintAboveSp = 128;
+ if (stackBase.notEqual(0)) {
+ bytesToPrintAboveSp = 512;
+ UnsignedWord availableBytes = stackBase.subtract(sp);
+ if (availableBytes.belowThan(bytesToPrintAboveSp)) {
+ bytesToPrintAboveSp = NumUtil.safeToInt(availableBytes.rawValue());
+ }
}
- return bytesToPrint;
+
+ int wordsToPrintBelowSp = bytesToPrintBelowSp / wordSize;
+ log.hexdump(sp.subtract(wordsToPrintBelowSp * wordSize), wordSize, wordsToPrintBelowSp, 32);
+ log.indent(false);
+
+ log.string("> ").redent(true);
+ log.hexdump(sp, wordSize, bytesToPrintAboveSp / wordSize, 32);
+ log.indent(false);
+
+ log.newline();
}
}
@@ -619,13 +713,11 @@ public int maxInvocationCount() {
@Override
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.")
public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) {
- if (DeoptimizationSupport.enabled()) {
- log.string("DeoptStubPointer address: ").zhex(DeoptimizationSupport.getDeoptStubPointer()).newline().newline();
- }
+ log.string("DeoptStubPointer address: ").zhex(DeoptimizationSupport.getDeoptStubPointer()).newline().newline();
}
}
- private static class DumpTopFrame extends DiagnosticThunk {
+ private static class DumpTopDeoptimizedFrame extends DiagnosticThunk {
@Override
public int maxInvocationCount() {
return 1;
@@ -634,39 +726,19 @@ public int maxInvocationCount() {
@Override
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.")
public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) {
- // We already dump all safe values first, so there is nothing we could retry if an error
- // occurs.
Pointer sp = context.getStackPointer();
CodePointer ip = context.getInstructionPointer();
if (sp.isNonNull() && ip.isNonNull()) {
- log.string("Top frame info:").indent(true);
long totalFrameSize = getTotalFrameSize(sp, ip);
DeoptimizedFrame deoptFrame = Deoptimizer.checkDeoptimized(sp);
if (deoptFrame != null) {
+ log.string("Top frame info:").indent(true);
log.string("RSP ").zhex(sp).string(" frame was deoptimized:").newline();
log.string("SourcePC ").zhex(deoptFrame.getSourcePC()).newline();
log.string("SourceTotalFrameSize ").signed(totalFrameSize).newline();
- } else if (totalFrameSize != -1) {
- log.string("TotalFrameSize in CodeInfoTable ").signed(totalFrameSize).newline();
+ log.indent(false);
}
-
- if (totalFrameSize == -1) {
- log.string("Does not look like a Java Frame. Use JavaFrameAnchors to find LastJavaSP:").newline();
- JavaFrameAnchor anchor = JavaFrameAnchors.getFrameAnchor();
- while (anchor.isNonNull() && anchor.getLastJavaSP().belowOrEqual(sp)) {
- anchor = anchor.getPreviousAnchor();
- }
-
- if (anchor.isNonNull()) {
- log.string("Found matching Anchor:").zhex(anchor).newline();
- Pointer lastSp = anchor.getLastJavaSP();
- log.string("LastJavaSP ").zhex(lastSp).newline();
- CodePointer lastIp = anchor.getLastJavaIP();
- log.string("LastJavaIP ").zhex(lastIp).newline();
- }
- }
- log.indent(false);
}
}
}
@@ -680,8 +752,8 @@ public int maxInvocationCount() {
@Override
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.")
public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) {
- boolean allowJavaHeapAccess = DiagnosticLevel.javaHeapAccessAllowed(maxDiagnosticLevel) && invocationCount < 3;
- boolean allowUnsafeOperations = DiagnosticLevel.unsafeOperationsAllowed(maxDiagnosticLevel) && invocationCount < 2;
+ boolean allowJavaHeapAccess = DiagnosticLevel.javaHeapAccessAllowed(maxDiagnosticLevel) && invocationCount < 2;
+ boolean allowUnsafeOperations = DiagnosticLevel.unsafeOperationsAllowed(maxDiagnosticLevel) && invocationCount < 3;
/*
* If we are not at a safepoint, then it is unsafe to access the thread locals of
* another thread as the IsolateThread could be freed at any time.
@@ -703,9 +775,13 @@ public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLev
if (allowJavaHeapAccess) {
Thread threadObj = PlatformThreads.fromVMThread(thread);
- log.string(" \"").string(threadObj.getName()).string("\" - ").zhex(Word.objectToUntrackedPointer(threadObj));
- if (threadObj != null && threadObj.isDaemon()) {
- log.string(", daemon");
+ if (threadObj == null) {
+ log.string(" null");
+ } else {
+ log.string(" \"").string(threadObj.getName()).string("\" - ").zhex(Word.objectToUntrackedPointer(threadObj));
+ if (threadObj.isDaemon()) {
+ log.string(", daemon");
+ }
}
}
log.string(", stack(").zhex(VMThreads.StackEnd.get(thread)).string(",").zhex(VMThreads.StackBase.get(thread)).string(")");
@@ -780,10 +856,8 @@ public int maxInvocationCount() {
@Override
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.")
public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) {
- if (RuntimeCompilation.isEnabled()) {
- boolean allowJavaHeapAccess = DiagnosticLevel.javaHeapAccessAllowed(maxDiagnosticLevel) && invocationCount < 2;
- RuntimeCodeInfoHistory.singleton().printRecentOperations(log, allowJavaHeapAccess);
- }
+ boolean allowJavaHeapAccess = DiagnosticLevel.javaHeapAccessAllowed(maxDiagnosticLevel) && invocationCount < 2;
+ RuntimeCodeInfoHistory.singleton().printRecentOperations(log, allowJavaHeapAccess);
}
}
@@ -796,11 +870,9 @@ public int maxInvocationCount() {
@Override
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.")
public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) {
- if (RuntimeCompilation.isEnabled()) {
- boolean allowJavaHeapAccess = DiagnosticLevel.javaHeapAccessAllowed(maxDiagnosticLevel) && invocationCount < 3;
- boolean allowUnsafeOperations = DiagnosticLevel.unsafeOperationsAllowed(maxDiagnosticLevel) && invocationCount < 2;
- RuntimeCodeInfoMemory.singleton().printTable(log, allowJavaHeapAccess, allowUnsafeOperations);
- }
+ boolean allowJavaHeapAccess = DiagnosticLevel.javaHeapAccessAllowed(maxDiagnosticLevel) && invocationCount < 3;
+ boolean allowUnsafeOperations = DiagnosticLevel.unsafeOperationsAllowed(maxDiagnosticLevel) && invocationCount < 2;
+ RuntimeCodeInfoMemory.singleton().printTable(log, allowJavaHeapAccess, allowUnsafeOperations);
}
}
@@ -813,9 +885,74 @@ public int maxInvocationCount() {
@Override
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.")
public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) {
- if (DeoptimizationSupport.enabled()) {
- Deoptimizer.logRecentDeoptimizationEvents(log);
+ Deoptimizer.logRecentDeoptimizationEvents(log);
+ }
+ }
+
+ static class DumpVMInfo extends DiagnosticThunk {
+ @Override
+ public int maxInvocationCount() {
+ return 1;
+ }
+
+ @Override
+ @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.")
+ public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) {
+ log.string("Build time information:").indent(true);
+
+ VM vm = ImageSingletons.lookup(VM.class);
+ log.string("Version: ").string(vm.version).string(", ").string(vm.info).newline();
+
+ Platform platform = ImageSingletons.lookup(Platform.class);
+ log.string("Platform: ").string(platform.getOS()).string("/").string(platform.getArchitecture()).newline();
+ log.string("Page size: ").unsigned(SubstrateOptions.getPageSize()).newline();
+ log.string("Container support: ").bool(Containers.Options.UseContainerSupport.getValue()).newline();
+ log.string("CPU features used for AOT compiled code: ").string(getBuildTimeCpuFeatures()).newline();
+ log.indent(false);
+ }
+
+ @Fold
+ static String getBuildTimeCpuFeatures() {
+ return String.join(", ", CPUFeatureAccess.singleton().buildtimeCPUFeatures().stream().map(Enum::toString).toList());
+ }
+ }
+
+ public static class DumpMachineInfo extends DiagnosticThunk {
+ @Override
+ public int maxInvocationCount() {
+ return 1;
+ }
+
+ @Override
+ @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.")
+ public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) {
+ log.string("Runtime information:").indent(true);
+
+ log.string("CPU cores (OS): ");
+ if (SubstrateOptions.JNI.getValue()) {
+ log.signed(Jvm.JVM_ActiveProcessorCount()).newline();
+ } else {
+ log.string("unknown").newline();
}
+
+ log.string("Memory: ");
+ if (PhysicalMemory.isInitialized()) {
+ log.rational(PhysicalMemory.getCachedSize(), 1024 * 1024, 0).string("M").newline();
+ } else {
+ log.string("unknown").newline();
+ }
+
+ log.string("Page size: ").unsigned(VirtualMemoryProvider.get().getGranularity()).newline();
+ log.string("VM uptime: ").rational(Isolates.getCurrentUptimeMillis(), TimeUtils.millisPerSecond, 3).string("s").newline();
+ log.string("Current timestamp: ").unsigned(System.currentTimeMillis()).newline();
+
+ CodeInfo info = CodeInfoTable.getImageCodeInfo();
+ Pointer codeStart = (Pointer) CodeInfoAccess.getCodeStart(info);
+ UnsignedWord codeSize = CodeInfoAccess.getCodeSize(info);
+ Pointer codeEnd = codeStart.add(codeSize).subtract(1);
+ log.string("AOT compiled code: ").zhex(codeStart).string(" - ").zhex(codeEnd).newline();
+
+ log.indent(false);
}
}
@@ -828,9 +965,12 @@ public int maxInvocationCount() {
@Override
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.")
public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) {
- log.string("Counters:").indent(true);
- Counter.logValues(log);
- log.indent(false);
+ CounterSupport counters = CounterSupport.singleton();
+ if (counters.hasCounters()) {
+ log.string("Counters:").indent(true);
+ counters.logValues(log);
+ log.indent(false);
+ }
}
}
@@ -862,7 +1002,7 @@ public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLev
Pointer sp = context.getStackPointer();
CodePointer ip = context.getInstructionPointer();
- log.string("Stacktrace for the failing thread ").zhex(CurrentIsolate.getCurrentThread()).string(":").indent(true);
+ log.string("Stacktrace for the failing thread ").zhex(CurrentIsolate.getCurrentThread()).string(" (A=AOT compiled, J=JIT compiled, D=deoptimized, i=inlined):").indent(true);
boolean success = ThreadStackPrinter.printStacktrace(sp, ip, printVisitors[invocationCount - 1].reset(), log);
if (!success && DiagnosticLevel.unsafeOperationsAllowed(maxDiagnosticLevel)) {
@@ -885,31 +1025,16 @@ public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLev
}
private static void startStackWalkInMostLikelyCaller(Log log, int invocationCount, Pointer originalSp) {
- UnsignedWord stackBase = VMThreads.StackBase.get();
- if (stackBase.equal(0)) {
- /* We don't know the stack boundaries, so only search within 32 bytes. */
- stackBase = originalSp.add(32);
+ Pointer returnAddressPos = findPotentialReturnAddressPosition(originalSp);
+ if (returnAddressPos.isNull()) {
+ return;
}
- /* Search until we find a valid return address. We may encounter false-positives. */
- int wordSize = ConfigurationValues.getTarget().wordSize;
- Pointer pos = originalSp;
- while (pos.belowThan(stackBase)) {
- CodePointer possibleIp = pos.readWord(0);
- if (pointsIntoNativeImageCode(possibleIp)) {
- Pointer sp = pos.add(wordSize);
- log.newline();
- log.string("Starting the stack walk in a possible caller:").newline();
- ThreadStackPrinter.printStacktrace(sp, possibleIp, printVisitors[invocationCount - 1].reset(), log);
- break;
- }
- pos = pos.add(wordSize);
- }
- }
-
- @Uninterruptible(reason = "Prevent the GC from freeing the CodeInfo.")
- private static boolean pointsIntoNativeImageCode(CodePointer possibleIp) {
- return CodeInfoTable.lookupCodeInfo(possibleIp).isNonNull();
+ CodePointer possibleIp = returnAddressPos.readWord(0);
+ Pointer sp = returnAddressPos.add(FrameAccess.returnAddressSize());
+ log.newline();
+ log.string("Starting the stack walk in a possible caller (sp + ").unsigned(sp.subtract(originalSp)).string("):").newline();
+ ThreadStackPrinter.printStacktrace(sp, possibleIp, printVisitors[invocationCount - 1].reset(), log);
}
}
@@ -961,7 +1086,7 @@ private static void printStackTrace(Log log, IsolateThread vmThread, int invocat
}
}
- private static class DumpAOTCompiledCodeInfo extends DiagnosticThunk {
+ private static class DumpCommandLine extends DiagnosticThunk {
@Override
public int maxInvocationCount() {
return 1;
@@ -970,13 +1095,14 @@ public int maxInvocationCount() {
@Override
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.")
public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) {
- CodeInfo info = CodeInfoTable.getImageCodeInfo();
- Pointer codeStart = (Pointer) CodeInfoAccess.getCodeStart(info);
- UnsignedWord codeSize = CodeInfoAccess.getCodeSize(info);
- Pointer codeEnd = codeStart.add(codeSize).subtract(1);
-
- log.string("AOT compiled code is mapped at ").zhex(codeStart).string(" - ").zhex(codeEnd).newline();
- log.newline();
+ String[] args = ImageSingletons.lookup(JavaMainWrapper.JavaMainSupport.class).mainArgs;
+ if (args != null) {
+ log.string("Command line: ");
+ for (String arg : args) {
+ log.string("'").string(arg).string("' ");
+ }
+ log.newline().newline();
+ }
}
}
@@ -1003,9 +1129,9 @@ public boolean printLocationInfo(Log log, UnsignedWord value) {
}
if (CodeInfoAccess.contains(imageCodeInfo, (CodePointer) value)) {
+ log.string("points into AOT compiled code ");
FrameInfoQueryResult compilationRoot = getCompilationRoot(imageCodeInfo, (CodePointer) value);
if (compilationRoot != null) {
- log.string("points into AOT compiled code ");
compilationRoot.log(log);
}
return true;
@@ -1113,18 +1239,39 @@ public static synchronized DiagnosticThunkRegistry singleton() {
@Platforms(Platform.HOSTED_ONLY.class)
DiagnosticThunkRegistry() {
- this.diagnosticThunks = new DiagnosticThunk[]{new DumpCurrentTimestamp(), new DumpRegisters(), new DumpInstructions(), new DumpTopOfCurrentThreadStack(), new DumpDeoptStubPointer(),
- new DumpTopFrame(), new DumpThreads(), new DumpCurrentThreadLocals(), new DumpCurrentVMOperation(), new DumpVMOperationHistory(), new DumpCodeCacheHistory(),
- new DumpRuntimeCodeInfoMemory(), new DumpRecentDeoptimizations(), new DumpCounters(), new DumpCurrentThreadFrameAnchors(), new DumpCurrentThreadDecodedStackTrace(),
- new DumpOtherStackTraces(), new VMLockSupport.DumpVMMutexes(), new DumpAOTCompiledCodeInfo()};
+ ArrayList thunks = new ArrayList<>();
+ thunks.add(new DumpRegisters());
+ thunks.add(new DumpInstructions());
+ thunks.add(new DumpTopOfCurrentThreadStack());
+ if (RuntimeCompilation.isEnabled()) {
+ thunks.add(new DumpTopDeoptimizedFrame());
+ }
+ thunks.add(new DumpCurrentThreadLocals());
+ thunks.add(new DumpCurrentThreadFrameAnchors());
+ thunks.add(new DumpCurrentThreadDecodedStackTrace());
+ thunks.add(new DumpThreads());
+ thunks.add(new DumpOtherStackTraces());
+ thunks.add(new DumpCurrentVMOperation());
+ thunks.add(new DumpVMOperationHistory());
+ thunks.add(new VMLockSupport.DumpVMMutexes());
+ thunks.add(new DumpVMInfo());
+ thunks.add(new DumpMachineInfo());
+ if (ImageSingletons.contains(JavaMainWrapper.JavaMainSupport.class)) {
+ thunks.add(new DumpCommandLine());
+ }
+ thunks.add(new DumpCounters());
+ if (RuntimeCompilation.isEnabled()) {
+ thunks.add(new DumpCodeCacheHistory());
+ thunks.add(new DumpRuntimeCodeInfoMemory());
+ thunks.add(new DumpDeoptStubPointer());
+ thunks.add(new DumpRecentDeoptimizations());
+ }
+ this.diagnosticThunks = thunks.toArray(new DiagnosticThunk[0]);
this.initialInvocationCount = new int[diagnosticThunks.length];
Arrays.fill(initialInvocationCount, 1);
}
- /**
- * Register a diagnostic thunk to be called after a segfault.
- */
@Platforms(Platform.HOSTED_ONLY.class)
public synchronized void register(DiagnosticThunk diagnosticThunk) {
diagnosticThunks = Arrays.copyOf(diagnosticThunks, diagnosticThunks.length + 1);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
index 0bae9f604f8a..71cbf24a3e0c 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
@@ -889,6 +889,12 @@ protected void onValueUpdate(EconomicMap, Object> values, String ol
}
};
+ @Option(help = "Specifies the number of entries that diagnostic buffers have.", type = OptionType.Debug)//
+ public static final HostedOptionKey DiagnosticBufferSize = new HostedOptionKey<>(30);
+
+ @Option(help = "Determines if implicit exceptions are fatal if they don't have a stack trace.", type = OptionType.Debug)//
+ public static final RuntimeOptionKey ImplicitExceptionWithoutStacktraceIsFatal = new RuntimeOptionKey<>(false);
+
@SuppressWarnings("unused")//
@APIOption(name = "configure-reflection-metadata")//
@Option(help = "Enable runtime instantiation of reflection objects for non-invoked methods.", type = OptionType.Expert, deprecated = true)//
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateSegfaultHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateSegfaultHandler.java
index 8fd81421c09d..e0ff50aa5ef9 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateSegfaultHandler.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateSegfaultHandler.java
@@ -26,21 +26,27 @@
import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.NO_ALLOCATION;
+import java.lang.reflect.Field;
+import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
import org.graalvm.compiler.api.replacements.Fold;
import org.graalvm.compiler.options.Option;
+import org.graalvm.compiler.word.Word;
import org.graalvm.nativeimage.CurrentIsolate;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Isolate;
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.LogHandler;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
import org.graalvm.nativeimage.c.function.CodePointer;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.word.LocationIdentity;
import org.graalvm.word.Pointer;
import org.graalvm.word.PointerBase;
+import org.graalvm.word.UnsignedWord;
import org.graalvm.word.WordFactory;
import com.oracle.svm.core.IsolateListenerSupport.IsolateListener;
@@ -49,18 +55,20 @@
import com.oracle.svm.core.c.CGlobalDataFactory;
import com.oracle.svm.core.c.function.CEntryPointActions;
import com.oracle.svm.core.c.function.CEntryPointErrors;
+import com.oracle.svm.core.config.ConfigurationValues;
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
import com.oracle.svm.core.feature.InternalFeature;
import com.oracle.svm.core.graal.nodes.WriteCurrentVMThreadNode;
import com.oracle.svm.core.graal.snippets.CEntryPointSnippets;
+import com.oracle.svm.core.heap.ReferenceAccess;
import com.oracle.svm.core.heap.RestrictHeapAccess;
import com.oracle.svm.core.jdk.RuntimeSupport;
import com.oracle.svm.core.log.Log;
import com.oracle.svm.core.option.RuntimeOptionKey;
-import com.oracle.svm.core.snippets.KnownIntrinsics;
import com.oracle.svm.core.stack.StackOverflowCheck;
import com.oracle.svm.core.thread.VMThreads;
import com.oracle.svm.core.thread.VMThreads.SafepointBehavior;
+import com.oracle.svm.util.ReflectionUtil;
@AutomaticallyRegisteredFeature
class SubstrateSegfaultHandlerFeature implements InternalFeature {
@@ -72,15 +80,31 @@ public List> getRequiredFeatures() {
@Override
public void beforeAnalysis(BeforeAnalysisAccess access) {
if (!ImageSingletons.contains(SubstrateSegfaultHandler.class)) {
- return; /* No segfault handler. */
+ return;
}
+ /* Register the marker as accessed so that we have a field with a well-known value. */
+ access.registerAsUnsafeAccessed(getStaticFieldWithWellKnownValue());
+
SingleIsolateSegfaultSetup singleIsolateSegfaultSetup = new SingleIsolateSegfaultSetup();
ImageSingletons.add(SingleIsolateSegfaultSetup.class, singleIsolateSegfaultSetup);
IsolateListenerSupport.singleton().register(singleIsolateSegfaultSetup);
RuntimeSupport.getRuntimeSupport().addStartupHook(new SubstrateSegfaultHandlerStartupHook());
}
+
+ @Override
+ public void beforeCompilation(BeforeCompilationAccess access) {
+ if (!ImageSingletons.contains(SubstrateSegfaultHandler.class)) {
+ return;
+ }
+
+ SubstrateSegfaultHandler.offsetOfStaticFieldWithWellKnownValue = access.objectFieldOffset(getStaticFieldWithWellKnownValue());
+ }
+
+ private static Field getStaticFieldWithWellKnownValue() {
+ return ReflectionUtil.lookupField(SubstrateSegfaultHandler.class, "staticFieldWithWellKnownValue");
+ }
}
final class SubstrateSegfaultHandlerStartupHook implements RuntimeSupport.Hook {
@@ -101,6 +125,16 @@ public static class Options {
static final RuntimeOptionKey InstallSegfaultHandler = new RuntimeOptionKey<>(null);
}
+ @Platforms(Platform.HOSTED_ONLY.class) //
+ static long offsetOfStaticFieldWithWellKnownValue;
+
+ private static final long MARKER_VALUE = 0x0123456789ABCDEFL;
+ private static final CGlobalData OFFSET_OF_STATIC_FIELD_WITH_WELL_KNOWN_VALUE = CGlobalDataFactory.createBytes(() -> {
+ assert ConfigurationValues.getTarget().wordSize == Long.BYTES;
+ return ByteBuffer.allocate(Long.BYTES).order(ConfigurationValues.getTarget().arch.getByteOrder()).putLong(offsetOfStaticFieldWithWellKnownValue).array();
+ });
+ @SuppressWarnings("unused") private static long staticFieldWithWellKnownValue = MARKER_VALUE;
+
private boolean installed;
@Fold
@@ -122,39 +156,97 @@ public void install() {
protected abstract void printSignalInfo(Log log, PointerBase signalInfo);
- /** Called from the platform dependent segfault handler to enter the isolate. */
+ /**
+ * Called from the platform dependent segfault handler to enter the isolate. Note that this code
+ * may trigger a new segfault, which can lead to recursion. In the worst case, the recursion is
+ * only stopped once we trigger a native stack overflow.
+ */
@Uninterruptible(reason = "Thread state not set up yet.")
@RestrictHeapAccess(access = NO_ALLOCATION, reason = "Must not allocate in segfault handler.")
protected static boolean tryEnterIsolate(RegisterDumper.Context context) {
- // Check if we have sufficient information to enter the correct isolate.
+ /* If there is only a single isolate, we can just enter that isolate. */
Isolate isolate = SingleIsolateSegfaultSetup.singleton().getIsolate();
if (isolate.rawValue() != -1) {
- // There is only a single isolate, so lets attach to that isolate.
int error = CEntryPointActions.enterAttachThreadFromCrashHandler(isolate);
return error == CEntryPointErrors.NO_ERROR;
- } else if (!SubstrateOptions.useLLVMBackend()) {
- // Try to determine the isolate via the register information. This very likely fails if
- // the crash happened in native code that was linked into Native Image.
- if (SubstrateOptions.SpawnIsolates.getValue()) {
- PointerBase heapBase = RegisterDumper.singleton().getHeapBase(context);
- CEntryPointSnippets.setHeapBase(heapBase);
- }
- if (SubstrateOptions.MultiThreaded.getValue()) {
- PointerBase threadPointer = RegisterDumper.singleton().getThreadPointer(context);
- WriteCurrentVMThreadNode.writeCurrentVMThread((IsolateThread) threadPointer);
+ }
+
+ /* The LLVM backend doesn't support the register-based approach. */
+ if (SubstrateOptions.useLLVMBackend()) {
+ return false;
+ }
+
+ /* Try to determine the isolate via the thread register. */
+ if (SubstrateOptions.MultiThreaded.getValue() && tryEnterIsolateViaThreadRegister(context)) {
+ return true;
+ }
+
+ /* Try to determine the isolate via the heap base register. */
+ return SubstrateOptions.SpawnIsolates.getValue() && tryEnterIsolateViaHeapBaseRegister(context);
+ }
+
+ @Uninterruptible(reason = "Thread state not set up yet.")
+ @NeverInline("Prevent register writes from floating")
+ private static boolean tryEnterIsolateViaThreadRegister(RegisterDumper.Context context) {
+ /*
+ * Set the thread register to null so that we don't execute this code more than once if we
+ * trigger a recursive segfault.
+ */
+ WriteCurrentVMThreadNode.writeCurrentVMThread(WordFactory.nullPointer());
+
+ IsolateThread isolateThread = (IsolateThread) RegisterDumper.singleton().getThreadPointer(context);
+ if (isolateThread.isNonNull()) {
+ Isolate isolate = VMThreads.IsolateTL.get(isolateThread);
+ if (isValid(isolate)) {
+ if (SubstrateOptions.SpawnIsolates.getValue()) {
+ CEntryPointSnippets.setHeapBase(isolate);
+ }
+
+ WriteCurrentVMThreadNode.writeCurrentVMThread(isolateThread);
+ return true;
}
+ }
+ return false;
+ }
- /*
- * The following probing is subject to implicit recursion as it may trigger a new
- * segfault. However, this is fine, as it will eventually result in native stack
- * overflow.
- */
- isolate = VMThreads.IsolateTL.get();
- return Isolates.checkIsolate(isolate) == CEntryPointErrors.NO_ERROR && (!SubstrateOptions.SpawnIsolates.getValue() || isolate.equal(KnownIntrinsics.heapBase()));
+ @Uninterruptible(reason = "Thread state not set up yet.")
+ @NeverInline("Prevent register writes from floating")
+ private static boolean tryEnterIsolateViaHeapBaseRegister(RegisterDumper.Context context) {
+ /*
+ * Set the heap base register to null so that we don't execute this code more than once if
+ * we trigger a recursive segfault.
+ */
+ CEntryPointSnippets.setHeapBase(WordFactory.nullPointer());
+
+ Isolate isolate = (Isolate) RegisterDumper.singleton().getHeapBase(context);
+ if (isValid(isolate)) {
+ int error = CEntryPointActions.enterAttachThreadFromCrashHandler(isolate);
+ return error == CEntryPointErrors.NO_ERROR;
}
return false;
}
+ @Uninterruptible(reason = "Thread state not set up yet.")
+ private static boolean isValid(Isolate isolate) {
+ if (Isolates.checkIsolate(isolate) != CEntryPointErrors.NO_ERROR) {
+ return false;
+ }
+
+ /*
+ * Read a static field in the image heap and compare its value with a well-known marker
+ * value as an extra sanity check. Note that the heap base register still contains an
+ * invalid value when we execute this code, which makes things a bit more complex.
+ */
+ if (SubstrateOptions.SpawnIsolates.getValue()) {
+ UnsignedWord staticFieldsOffsets = ReferenceAccess.singleton().getCompressedRepresentation(StaticFieldsSupport.getStaticPrimitiveFields());
+ UnsignedWord wellKnownFieldOffset = staticFieldsOffsets.shiftLeft(ReferenceAccess.singleton().getCompressionShift()).add(OFFSET_OF_STATIC_FIELD_WITH_WELL_KNOWN_VALUE.get().readWord(0));
+ Pointer wellKnownField = ((Pointer) isolate).add(wellKnownFieldOffset);
+ return wellKnownField.readLong(0) == MARKER_VALUE;
+ }
+
+ return true;
+ }
+
/** Called from the platform dependent segfault handler to print diagnostics. */
@Uninterruptible(reason = "Must be uninterruptible until we get immune to safepoints.", calleeMustBe = false)
@RestrictHeapAccess(access = NO_ALLOCATION, reason = "Must not allocate in segfault handler.")
@@ -187,7 +279,16 @@ private static void dumpInterruptibly(PointerBase signalInfo, RegisterDumper.Con
logHandler.fatalError();
}
- static class SingleIsolateSegfaultSetup implements IsolateListener {
+ protected static void printSegfaultAddressInfo(Log log, long addr) {
+ log.zhex(addr);
+ if (addr != 0) {
+ long delta = addr - CurrentIsolate.getIsolate().rawValue();
+ String sign = (delta >= 0 ? "+" : "-");
+ log.string(" (heapBase ").string(sign).string(" ").signed(Math.abs(delta)).string(")");
+ }
+ }
+
+ public static class SingleIsolateSegfaultSetup implements IsolateListener {
/**
* Stores the address of the first isolate created. This is meant to attempt to detect the
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointActions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointActions.java
index 263a370bc9fa..db7356c8ce58 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointActions.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointActions.java
@@ -65,10 +65,11 @@ private CEntryPointActions() {
* @param startedByIsolate Whether the current thread has been launched directly by the isolate
* (as opposed to being an externally started thread), which makes the isolate
* responsible for cleanups when the thread detaches.
- * @param ensureJavaThread when set to true, the method ensures that the {@link Thread} object
- * for the newly attached thread is created. If the parameter is set to false, a
- * later call to one of the {@link PlatformThreads#ensureCurrentAssigned} methods
- * early after the prologue must be used to do the initialization manually.
+ * @param ensureJavaThread when set to true, {@link PlatformThreads#ensureCurrentAssigned()} is
+ * called to ensure that the Java {@link Thread} is fully initialized. If the
+ * parameter is set to false, the initialization must be done manually (early after
+ * the prologue).
+ *
* @return 0 on success, otherwise non-zero.
*/
public static native int enterAttachThread(Isolate isolate, boolean startedByIsolate, boolean ensureJavaThread);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java
index 330962dda3b1..f8891f44c5cb 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java
@@ -309,7 +309,7 @@ public static void setEncodings(CodeInfo info, NonmovableObjectArray