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 obj } public static Log log(CodeInfo info, Log log) { - return info.isNull() ? log.string("null") : log.string(CodeInfo.class.getName()).string("@").hex(info); + return info.isNull() ? log.string("null") : log.string("CodeInfo@").hex(info); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java index a2b7ebab5822..480e17602f5d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java @@ -515,7 +515,8 @@ static CodeInfoDecoderCounters counters() { /** * This class can be used to iterate the Java-level stack trace information for a given * instruction pointer (IP). A single physical stack frame may correspond to multiple Java-level - * stack frames. + * stack frames. Iteration starts in the deepest inlined method and ends at the compilation + * root. */ public static class FrameInfoCursor { private final ReusableTypeReader frameInfoReader = new ReusableTypeReader(); @@ -550,6 +551,12 @@ public boolean advance() { return result != null; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean hasCaller() { + assert result != null; + return !state.isDone; + } + /** * Returns the information for the current frame. * diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java index bde7d510f25d..2b59e58c23c4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java @@ -470,9 +470,9 @@ public boolean verifyFrameInfo(CodeInfo info) { class CodeInfoVerifier { static void verifyMethod(SharedMethod method, CompilationResult compilation, int compilationOffset, int compilationSize, CodeInfo info) { + CodeInfoQueryResult queryResult = new CodeInfoQueryResult(); for (int relativeIP = 0; relativeIP < compilationSize; relativeIP++) { int totalIP = relativeIP + compilationOffset; - CodeInfoQueryResult queryResult = new CodeInfoQueryResult(); CodeInfoAccess.lookupCodeInfo(info, totalIP, queryResult); assert queryResult.isEntryPoint() == method.isEntryPoint(); assert queryResult.hasCalleeSavedRegisters() == method.hasCalleeSavedRegisters(); @@ -486,7 +486,6 @@ static void verifyMethod(SharedMethod method, CompilationResult compilation, int int offset = CodeInfoEncoder.getEntryOffset(infopoint); if (offset >= 0) { assert offset < compilationSize; - CodeInfoQueryResult queryResult = new CodeInfoQueryResult(); CodeInfoAccess.lookupCodeInfo(info, offset + compilationOffset, queryResult); CollectingObjectReferenceVisitor visitor = new CollectingObjectReferenceVisitor(); @@ -506,7 +505,6 @@ static void verifyMethod(SharedMethod method, CompilationResult compilation, int int offset = handler.pcOffset; assert offset >= 0 && offset < compilationSize; - CodeInfoQueryResult queryResult = new CodeInfoQueryResult(); CodeInfoAccess.lookupCodeInfo(info, offset + compilationOffset, queryResult); long actual = queryResult.getExceptionOffset(); long expected = handler.handlerPos - handler.pcOffset; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java index 4692a58d23fe..e37d6a72f454 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java @@ -329,7 +329,7 @@ public String getLocalsStr() { sb.append(", "); } sb.append("li("); - Local local = locals != null ? locals[i] : null; + Local local = locals[i]; if (local != null) { sb.append(local.getName()); sb.append("="); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoDecoder.java index 9d5703a38b98..ab54d50ca4a1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoDecoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoDecoder.java @@ -377,7 +377,7 @@ private static FrameInfoQueryResult decodeUncompressedFrameInfo(boolean isDeoptE FrameInfoQueryResult prev = null; ValueInfo[][] virtualObjects = null; - while (true) { + while (!state.isDone) { long start = readBuffer.getByteIndex(); int encodedBci = readBuffer.getSVInt(); if (encodedBci == NO_CALLER_BCI) { @@ -460,6 +460,8 @@ private static FrameInfoQueryResult decodeUncompressedFrameInfo(boolean isDeoptE state.isFirstFrame = false; } + + return result; } @Uninterruptible(reason = "Some allocators are interruptible.", calleeMustBe = false) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoHistory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoHistory.java index 5b849f06dc7e..a69e765279bb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoHistory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoHistory.java @@ -24,22 +24,25 @@ */ package com.oracle.svm.core.code; -import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.code.CodeInfoAccess.HasInstalledCode; -import com.oracle.svm.core.deopt.SubstrateInstalledCode; -import com.oracle.svm.core.thread.Safepoint; import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.function.CodePointer; +import org.graalvm.word.UnsignedWord; +import com.oracle.svm.core.Isolates; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.code.CodeInfoAccess.HasInstalledCode; +import com.oracle.svm.core.deopt.SubstrateInstalledCode; import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.log.Log; +import com.oracle.svm.core.thread.Safepoint; import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.util.RingBuffer; -import org.graalvm.word.UnsignedWord; +import com.oracle.svm.core.util.TimeUtils; public class RuntimeCodeInfoHistory { private static final RingBuffer.Consumer PRINT_WITH_JAVA_HEAP_DATA = RuntimeCodeInfoHistory::printEntryWithJavaHeapData; @@ -49,7 +52,7 @@ public class RuntimeCodeInfoHistory { @Platforms(Platform.HOSTED_ONLY.class) RuntimeCodeInfoHistory() { - recentOperations = new RingBuffer<>(20, CodeCacheLogEntry::new); + recentOperations = new RingBuffer<>(SubstrateOptions.DiagnosticBufferSize.getValue(), CodeCacheLogEntry::new); } @Fold @@ -98,7 +101,7 @@ private static void traceCodeCache(String kind, CodeInfo info, boolean allowJava } public void printRecentOperations(Log log, boolean allowJavaHeapAccess) { - log.string("The ").signed(recentOperations.size()).string(" most recent RuntimeCodeInfo operations (oldest first): ").indent(true); + log.string("The ").signed(recentOperations.size()).string(" most recent RuntimeCodeInfo operations: ").indent(true); recentOperations.foreach(log, allowJavaHeapAccess ? PRINT_WITH_JAVA_HEAP_DATA : PRINT_WITHOUT_JAVA_HEAP_DATA); log.indent(false); } @@ -159,7 +162,8 @@ public void setValues(String kind, CodeInfo codeInfo, int codeInfoState, String public void print(Log log, boolean allowJavaHeapAccess) { if (kind != null) { - log.unsigned(timestamp).string(" - ").string(kind).spaces(1); + long uptime = timestamp - Isolates.getCurrentStartTimeMillis(); + log.rational(uptime, TimeUtils.millisPerSecond, 3).string("s - ").string(kind).spaces(1); String name = allowJavaHeapAccess ? codeName : null; CodeInfoAccess.printCodeInfo(log, codeInfo, codeInfoState, name, codeStart, codeEnd, hasInstalledCode, installedCodeAddress, installedCodeEntryPoint); log.string(", safepointId: ").unsigned(safepointId).newline(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/Deoptimizer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/Deoptimizer.java index c478fb1c7ffd..c3346a4450ce 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/Deoptimizer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/Deoptimizer.java @@ -48,6 +48,7 @@ import org.graalvm.word.WordFactory; import com.oracle.svm.core.FrameAccess; +import com.oracle.svm.core.Isolates; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.ReservedRegisters; import com.oracle.svm.core.SubstrateOptions; @@ -83,6 +84,7 @@ import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.util.RingBuffer; +import com.oracle.svm.core.util.TimeUtils; import com.oracle.svm.core.util.VMError; import jdk.internal.misc.Unsafe; @@ -149,7 +151,7 @@ */ public final class Deoptimizer { private static final int MAX_DEOPTIMIZATION_EVENT_PRINT_LENGTH = 1000; - private static final RingBuffer recentDeoptimizationEvents = new RingBuffer<>(); + private static final RingBuffer recentDeoptimizationEvents = new RingBuffer<>(SubstrateOptions.DiagnosticBufferSize.getValue()); private static final int actionShift = 0; private static final int actionBits = Integer.SIZE - Integer.numberOfLeadingZeros(DeoptimizationAction.values().length); @@ -1133,7 +1135,7 @@ private static JavaConstant readConstant(Pointer addr, SignedWord offset, JavaKi } private static void printDeoptimizedFrame(Log log, Pointer sp, DeoptimizedFrame deoptimizedFrame, FrameInfoQueryResult sourceFrameInfo, boolean printOnlyTopFrames) { - log.string("[Deoptimization of frame (timestamp ").unsigned(System.currentTimeMillis()).string(")").newline(); + log.string("[Deoptimization of frame (").rational(Isolates.getCurrentUptimeMillis(), TimeUtils.millisPerSecond, 3).string("s)").newline(); SubstrateInstalledCode installedCode = deoptimizedFrame.getSourceInstalledCode(); if (installedCode != null) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SubstrateBasicLoweringProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SubstrateBasicLoweringProvider.java index 7ab5ed7f986f..2dcb09313469 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SubstrateBasicLoweringProvider.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SubstrateBasicLoweringProvider.java @@ -228,7 +228,7 @@ protected ValueNode createReadHub(StructuredGraph graph, ValueNode object, Lower // get rid of the reserved header bits and extract the actual pointer to the hub assert CodeUtil.isPowerOf2(reservedBitsMask + 1) : "only the lowest bits may be set"; int numReservedBits = CodeUtil.log2(reservedBitsMask + 1); - int compressionShift = ReferenceAccess.singleton().getCompressEncoding().getShift(); + int compressionShift = ReferenceAccess.singleton().getCompressionShift(); int numAlignmentBits = CodeUtil.log2(objectLayout.getAlignment()); assert compressionShift <= numAlignmentBits : "compression discards bits"; if (numReservedBits == numAlignmentBits && compressionShift == 0) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java index af7327cec9f5..d7232a5af9ea 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java @@ -55,7 +55,6 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Isolate; import org.graalvm.nativeimage.IsolateThread; -import org.graalvm.nativeimage.LogHandler; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CLongPointer; @@ -89,7 +88,6 @@ import com.oracle.svm.core.graal.nodes.CEntryPointLeaveNode; import com.oracle.svm.core.graal.nodes.CEntryPointUtilityNode; import com.oracle.svm.core.graal.nodes.WriteCurrentVMThreadNode; -import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.heap.PhysicalMemory; import com.oracle.svm.core.heap.ReferenceHandler; import com.oracle.svm.core.heap.ReferenceHandlerThread; @@ -106,6 +104,7 @@ import com.oracle.svm.core.stack.StackOverflowCheck; import com.oracle.svm.core.thread.PlatformThreads; import com.oracle.svm.core.thread.Safepoint; +import com.oracle.svm.core.thread.ThreadListenerSupport; import com.oracle.svm.core.thread.VMOperationControl; import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.thread.VMThreads.SafepointBehavior; @@ -193,7 +192,6 @@ public static int createIsolateSnippet(CEntryPointCreateIsolateParameters parame writeCurrentVMThread(WordFactory.nullPointer()); } int result = runtimeCall(CREATE_ISOLATE, parameters, vmThreadSize); - if (result != CEntryPointErrors.NO_ERROR) { return result; } @@ -214,8 +212,7 @@ private static int createIsolate(CEntryPointCreateIsolateParameters parameters, UnsignedWord runtimePageSize = VirtualMemoryProvider.get().getGranularity(); UnsignedWord imagePageSize = WordFactory.unsigned(SubstrateOptions.getPageSize()); - boolean validPageSize = runtimePageSize.equal(imagePageSize) || - (Heap.getHeap().allowPageSizeMismatch() && UnsignedUtils.isAMultiple(imagePageSize, runtimePageSize)); + boolean validPageSize = UnsignedUtils.isAMultiple(imagePageSize, runtimePageSize); if (!validPageSize) { return CEntryPointErrors.PAGE_SIZE_CHECK_FAILED; } @@ -250,8 +247,7 @@ private static int createIsolate0(CLongPointer parsedArgs, WordPointer isolate, if (error != CEntryPointErrors.NO_ERROR) { return error; } - - PlatformThreads.singleton().initializeIsolate(); + PlatformThreads.singleton().assignMainThread(); return CEntryPointErrors.NO_ERROR; } @@ -294,6 +290,16 @@ private static int initializeIsolateInterruptibly(CEntryPointCreateIsolateParame } private static int initializeIsolateInterruptibly0(CEntryPointCreateIsolateParameters parameters) { + try { + return initializeIsolateInterruptibly1(parameters); + } catch (Throwable t) { + Log.log().string("Uncaught exception while initializing isolate: ").exception(t).newline(); + return CEntryPointErrors.ISOLATE_INITIALIZATION_FAILED; + } + } + + @NeverInline("GR-24649") + private static int initializeIsolateInterruptibly1(CEntryPointCreateIsolateParameters parameters) { /* * The VM operation thread must be started early as no VM operations can be scheduled before * this thread is fully started. The isolate teardown may also use VM operations. @@ -306,6 +312,7 @@ private static int initializeIsolateInterruptibly0(CEntryPointCreateIsolateParam boolean firstIsolate = Unsafe.getUnsafe().compareAndSetInt(null, initStateAddr, FirstIsolateInitStates.UNINITIALIZED, FirstIsolateInitStates.IN_PROGRESS); Isolates.setCurrentIsFirstIsolate(firstIsolate); + Isolates.assignCurrentStartTime(); if (!firstIsolate) { int state = Unsafe.getUnsafe().getInt(initStateAddr); @@ -377,10 +384,20 @@ private static int initializeIsolateInterruptibly0(CEntryPointCreateIsolateParam assert !isolateInitialized; isolateInitialized = true; + /* Run isolate initialization hooks. */ try { RuntimeSupport.executeInitializationHooks(); } catch (Throwable t) { - System.err.println("Uncaught exception while running initialization hooks:"); + System.err.println("Uncaught exception while running isolate initialization hooks:"); + t.printStackTrace(); + return CEntryPointErrors.ISOLATE_INITIALIZATION_FAILED; + } + + /* The isolate is now initialized, so we can finally finish initializing the main thread. */ + try { + ThreadListenerSupport.get().beforeThreadRun(); + } catch (Throwable t) { + System.err.println("Uncaught exception in beforeThreadRun():"); t.printStackTrace(); return CEntryPointErrors.ISOLATE_INITIALIZATION_FAILED; } @@ -403,8 +420,9 @@ public static int attachThreadSnippet(Isolate isolate, boolean startedByIsolate, if (MultiThreaded.getValue() && !inCrashHandler) { Safepoint.transitionNativeToJava(false); } + if (ensureJavaThread) { - runtimeCallEnsureJavaThread(ENSURE_JAVA_THREAD); + return runtimeCallEnsureJavaThread(ENSURE_JAVA_THREAD); } return CEntryPointErrors.NO_ERROR; } @@ -467,11 +485,22 @@ private static int attachUnattachedThread(Isolate isolate, boolean startedByIsol } @NodeIntrinsic(value = ForeignCallNode.class) - public static native void runtimeCallEnsureJavaThread(@ConstantNodeParameter ForeignCallDescriptor descriptor); + public static native int runtimeCallEnsureJavaThread(@ConstantNodeParameter ForeignCallDescriptor descriptor); + @Uninterruptible(reason = "Prevent stack overflow errors; thread is no longer attached in error case.", calleeMustBe = false) @SubstrateForeignCallTarget(stubCallingConvention = false) - private static void ensureJavaThread() { - PlatformThreads.ensureCurrentAssigned(); + private static int ensureJavaThread() { + try { + PlatformThreads.ensureCurrentAssigned(); + return CEntryPointErrors.NO_ERROR; + } catch (Throwable e) { + int result = CEntryPointErrors.UNCAUGHT_EXCEPTION; + if (e instanceof OutOfMemoryError) { + result = CEntryPointErrors.ALLOCATION_FAILED; + } + CEntryPointActions.leaveDetachThread(); + return result; + } } @Snippet(allowMissingProbabilities = true) @@ -481,11 +510,6 @@ public static int detachThreadSnippet() { IsolateThread thread = CurrentIsolate.getCurrentThread(); result = runtimeCall(DETACH_THREAD_MT, thread); } - /* - * Note that we do not reset the fixed registers used for the thread and isolate to null: - * Since these values are not copied to different registers when they are used, we need to - * keep the registers intact until the last possible point where we are in Java code. - */ return result; } @@ -649,13 +673,14 @@ private static int reportException(Throwable exception) { private static void reportExceptionInterruptibly(Throwable exception) { logException(exception); - ImageSingletons.lookup(LogHandler.class).fatalError(); + VMError.shouldNotReachHere("Unhandled exception"); } @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Must not allocate in fatal error handling.") private static void logException(Throwable exception) { try { - Log.log().exception(exception); + Log.log().string("Unhandled exception: "); + Log.log().exception(exception).newline().newline(); } catch (Throwable ex) { /* Logging failed, so there is nothing we can do anymore to log. */ } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Heap.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Heap.java index 42e28322d963..4902b9c13dd5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Heap.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Heap.java @@ -37,7 +37,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.PredefinedClassesSupport; @@ -168,14 +167,6 @@ public List> getLoadedClasses() { @Fold public abstract int getImageHeapNullRegionSize(); - /** - * Returns whether the runtime page size doesn't have to match the page size set at image - * creation ({@link SubstrateOptions#getPageSize()}). If there is a mismatch, then the page size - * set at image creation must be a multiple of the runtime page size. - */ - @Fold - public abstract boolean allowPageSizeMismatch(); - /** * Returns true if the given object is located in the image heap. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ObjectHeader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ObjectHeader.java index 5e83ff7a0932..f6b0edb0da02 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ObjectHeader.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ObjectHeader.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.core.heap; -import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -97,9 +96,4 @@ public boolean pointsToObjectHeader(Pointer ptr) { @Uninterruptible(reason = "Prevent a GC interfering with the object's identity hash state.", callerMustBe = true) public abstract void setIdentityHashFromAddress(Pointer ptr, Word currentHeader); - - @Fold - protected static int getCompressionShift() { - return ReferenceAccess.singleton().getCompressEncoding().getShift(); - } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/OutOfMemoryUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/OutOfMemoryUtil.java index 86fb9c99d2d3..579c8e015d8e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/OutOfMemoryUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/OutOfMemoryUtil.java @@ -30,8 +30,13 @@ import com.oracle.svm.core.log.Log; import com.oracle.svm.core.util.VMError; +/** + * This class must be used for {@link OutOfMemoryError}s that are thrown because the VM is out of + * Java heap memory. Other {@link OutOfMemoryError}s (e.g., when we run out of native memory) can be + * thrown directly. + */ public class OutOfMemoryUtil { - private static final OutOfMemoryError OUT_OF_MEMORY_ERROR = new OutOfMemoryError("Garbage-collected heap size exceeded."); + private static final OutOfMemoryError OUT_OF_MEMORY_ERROR = new OutOfMemoryError("Garbage-collected heap size exceeded. Consider increasing the maximum Java heap size, for example with '-Xmx'."); @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Can't allocate when out of memory.") public static OutOfMemoryError heapSizeExceeded() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/PhysicalMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/PhysicalMemory.java index d9516fe2b2d3..02849154089b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/PhysicalMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/PhysicalMemory.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.heap; +import com.oracle.svm.core.Uninterruptible; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; @@ -58,10 +59,17 @@ default boolean hasSize() { private static final UnsignedWord UNSET_SENTINEL = UnsignedUtils.MAX_VALUE; private static UnsignedWord cachedSize = UNSET_SENTINEL; + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean isInitialized() { return cachedSize != UNSET_SENTINEL; } + @Uninterruptible(reason = "May only be called during early startup.") + public static void setSize(UnsignedWord value) { + VMError.guarantee(!isInitialized(), "PhysicalMemorySize must not be initialized yet."); + cachedSize = value; + } + /** * Returns the size of physical memory in bytes, querying it from the OS if it has not been * initialized yet. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceAccess.java index b2a174de1492..730ea54795d2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceAccess.java @@ -118,6 +118,11 @@ static ReferenceAccess singleton() { */ CompressEncoding getCompressEncoding(); + /** + * Returns a compile-time constant for {@link CompressEncoding#getShift()}. + */ + int getCompressionShift(); + /** * Returns the size of the address space, based on the reference size. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceAccessImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceAccessImpl.java index c3f22e0e6bf2..7709a96f4ce1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceAccessImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceAccessImpl.java @@ -117,4 +117,10 @@ public UnsignedWord getAddressSpaceSize() { // Assume that 48 bit is the maximum address space that can be used. return WordFactory.unsigned((1L << 48) - 1); } + + @Fold + @Override + public int getCompressionShift() { + return getCompressEncoding().getShift(); + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index e015eea7ea4e..856d12a7aa0f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -190,9 +190,9 @@ public final class DynamicHub implements AnnotatedElement, java.lang.reflect.Typ * by an instance of this class. If 0, then instances of this class are locked using a side * table. */ - private short monitorOffset; + private char monitorOffset; - private short optionalIdentityHashOffset; + private char optionalIdentityHashOffset; /** * Bit-set for various boolean flags, to reduce size of instances. It is important that this @@ -433,13 +433,17 @@ public void setData(int layoutEncoding, int typeID, int monitorOffset, int optio ObjectLayout ol = ConfigurationValues.getObjectLayout(); assert ol.hasFixedIdentityHashField() ? (optionalIdentityHashOffset == ol.getFixedIdentityHashOffset()) : (optionalIdentityHashOffset > 0); } else { - assert optionalIdentityHashOffset == -1; + assert optionalIdentityHashOffset == 0; } + VMError.guarantee(monitorOffset == (char) monitorOffset, "Class %s has an invalid monitor field offset. Most likely, its objects are larger than supported.", name); + VMError.guarantee(optionalIdentityHashOffset == (char) optionalIdentityHashOffset, + "Class %s has an invalid identity hash code field offset. Most likely, its objects are larger than supported.", name); this.layoutEncoding = layoutEncoding; this.typeID = typeID; - this.monitorOffset = NumUtil.safeToShort(monitorOffset); - this.optionalIdentityHashOffset = NumUtil.safeToShort(optionalIdentityHashOffset); + this.monitorOffset = (char) monitorOffset; + this.optionalIdentityHashOffset = (char) optionalIdentityHashOffset; + this.typeCheckStart = typeCheckStart; this.typeCheckRange = typeCheckRange; this.typeCheckSlot = typeCheckSlot; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKUtils.java index c584daf3c838..ad5ef7f39dc6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKUtils.java @@ -37,6 +37,16 @@ public static String getRawMessage(Throwable ex) { return SubstrateUtil.cast(ex, Target_java_lang_Throwable.class).detailMessage; } + /** + * Returns the raw cause stored in {@link Throwable} and returned by default from + * {@link Throwable#getCause}. This method ignores possible overrides of + * {@link Throwable#getCause} and is therefore guaranteed to be allocation free. + */ + public static Throwable getRawCause(Throwable ex) { + Throwable cause = SubstrateUtil.cast(ex, Target_java_lang_Throwable.class).cause; + return cause == ex ? null : cause; + } + public static StackTraceElement[] getRawStackTrace(Throwable ex) { return SubstrateUtil.cast(ex, Target_java_lang_Throwable.class).stackTrace; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java index be25cdbe90c8..e2046f282f2e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java @@ -295,6 +295,9 @@ final class Target_java_lang_Throwable { @Alias @RecomputeFieldValue(kind = Reset)// private Object backtrace; + @Alias// + Throwable cause; + @Alias @RecomputeFieldValue(kind = Reset)// StackTraceElement[] stackTrace; @@ -731,6 +734,19 @@ static void disable() { @TargetClass(java.lang.NullPointerException.class) final class Target_java_lang_NullPointerException { + /** + * {@link NullPointerException} overrides {@link Throwable#fillInStackTrace()} with a + * {@code synchronized} method which is not permitted in a {@code VMOperation}. + */ + @Substitute + @TargetElement(onlyWith = JDK17OrLater.class) + @Platforms(InternalPlatform.NATIVE_ONLY.class) + Target_java_lang_Throwable fillInStackTrace() { + Target_java_lang_Throwable t = SubstrateUtil.cast(this, Target_java_lang_Throwable.class); + t.stackTrace = JavaThreads.getStackTrace(true, Thread.currentThread()); + return t; + } + @Substitute @TargetElement(onlyWith = JDK17OrLater.class) @SuppressWarnings("static-method") diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RuntimeSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RuntimeSupport.java index 10a81b3ffc48..75ad7691793f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RuntimeSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RuntimeSupport.java @@ -126,7 +126,7 @@ public static void executeInitializationHooks() { /** * Adds a hook which will execute during isolate tear-down. Note it is possible for the - * {@link #tearDownHooks} to called without the {@link #initializationHooks} executing first. + * {@link #tearDownHooks} to be called without the {@link #initializationHooks} executing first. */ public void addTearDownHook(Hook tearDownHook) { addHook(tearDownHooks, tearDownHook); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java index 3302cc6017db..d949b1f524a1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java @@ -49,7 +49,6 @@ import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.thread.LoomSupport; import com.oracle.svm.core.thread.PlatformThreads; -import com.oracle.svm.core.thread.Target_java_lang_Thread; import com.oracle.svm.core.thread.Target_jdk_internal_vm_Continuation; import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.thread.VirtualThreads; @@ -86,6 +85,9 @@ public static StackTraceElement[] getStackTrace(boolean filterExceptions, Pointe @NeverInline("Potentially starting a stack walk in the caller frame") public static StackTraceElement[] getStackTraceAtSafepoint(Thread thread) { assert VMOperation.isInProgressAtSafepoint(); + if (thread == null) { + return NO_ELEMENTS; + } if (VirtualThreads.isSupported()) { // NOTE: also for platform threads! return VirtualThreads.singleton().getVirtualOrPlatformThreadStackTraceAtSafepoint(thread, readCallerStackPointer()); } @@ -94,6 +96,9 @@ public static StackTraceElement[] getStackTraceAtSafepoint(Thread thread) { public static StackTraceElement[] getThreadStackTraceAtSafepoint(IsolateThread isolateThread, Pointer endSP) { assert VMOperation.isInProgressAtSafepoint(); + if (isolateThread.isNull()) { // recently launched thread + return NO_ELEMENTS; + } BuildStackTraceVisitor visitor = new BuildStackTraceVisitor(false, SubstrateOptions.MaxJavaStackTraceDepth.getValue()); JavaStackWalker.walkThread(isolateThread, endSP, visitor, null); return visitor.trace.toArray(NO_ELEMENTS); @@ -213,6 +218,10 @@ public static ClassLoader latestUserDefinedClassLoader(Pointer startSP) { } public static StackTraceElement[] asyncGetStackTrace(Thread thread) { + if (thread == null || !thread.isAlive()) { + /* Avoid triggering a safepoint operation below if the thread is not even alive. */ + return NO_ELEMENTS; + } GetStackTraceOperation vmOp = new GetStackTraceOperation(thread); vmOp.enqueue(); return vmOp.result; @@ -229,11 +238,7 @@ private static class GetStackTraceOperation extends JavaVMOperation { @Override protected void operate() { - if (thread.isAlive()) { - result = getStackTraceAtSafepoint(thread); - } else { - result = Target_java_lang_Thread.EMPTY_STACK_TRACE; - } + result = getStackTraceAtSafepoint(thread); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VMErrorSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VMErrorSubstitutions.java index 6cabfd232d07..5a5e6c2e91a0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VMErrorSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VMErrorSubstitutions.java @@ -135,9 +135,8 @@ private static void doShutdown(CodePointer callerIP, String msg, Throwable ex) { } if (ex != null) { log.string(": ").exception(ex); - } else { - log.newline(); } + log.newline(); SubstrateDiagnostics.printFatalError(log, KnownIntrinsics.readCallerStackPointer(), KnownIntrinsics.readReturnAddress()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/JfrRecurringCallbackExecutionSampler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/JfrRecurringCallbackExecutionSampler.java index e070684ef53a..668fc9958493 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/JfrRecurringCallbackExecutionSampler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/JfrRecurringCallbackExecutionSampler.java @@ -27,10 +27,9 @@ import static com.oracle.svm.core.snippets.KnownIntrinsics.readCallerStackPointer; import static com.oracle.svm.core.snippets.KnownIntrinsics.readReturnAddress; -import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; -import com.oracle.svm.core.feature.InternalFeature; -import com.oracle.svm.core.jfr.JfrFeature; -import com.oracle.svm.core.thread.ThreadListenerSupport; +import java.util.Collections; +import java.util.List; + import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.IsolateThread; @@ -43,17 +42,18 @@ import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.jfr.JfrFeature; import com.oracle.svm.core.jfr.SubstrateJVM; import com.oracle.svm.core.thread.ThreadListener; +import com.oracle.svm.core.thread.ThreadListenerSupport; import com.oracle.svm.core.thread.ThreadingSupportImpl; import com.oracle.svm.core.thread.ThreadingSupportImpl.RecurringCallbackTimer; import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.util.TimeUtils; -import java.util.Collections; -import java.util.List; - public class JfrRecurringCallbackExecutionSampler extends AbstractJfrExecutionSampler implements ThreadListener { private static final ExecutionSampleCallback CALLBACK = new ExecutionSampleCallback(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMLockSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMLockSupport.java index 9a640c058e81..bbafc2c39575 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMLockSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMLockSupport.java @@ -68,7 +68,7 @@ public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLev } if (support == null || support.getMutexes() == null) { - log.string("No mutex information is available."); + log.string("No mutex information is available.").newline(); } else { VMMutex[] mutexes = support.getMutexes(); for (int i = 0; i < mutexes.length; i++) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/Log.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/Log.java index 3fb00f676b84..400365dbd85d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/Log.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/Log.java @@ -36,6 +36,7 @@ import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.word.PointerBase; +import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; /** @@ -139,6 +140,12 @@ protected Log() { */ public abstract Log string(String str, int fill, int align); + /** + * Prints the string characters, up to the given maximum length. Does not do any platform- or + * charset-depending conversions. + */ + public abstract Log string(String value, int maxLen); + /** * Prints all characters in the array, without any platform- or charset-depending conversions. */ @@ -202,13 +209,18 @@ protected Log() { */ public abstract Log signed(long value); + /** + * Prints the value, treated as a signed value, filling spaces before or after. + */ + public abstract Log signed(long value, int fill, int align); + /** * Prints the value, treated as an unsigned value, in decimal format. */ public abstract Log unsigned(WordBase value); /** - * Prints the value, treated as an unsigned value, filing spaces before or after. + * Prints the value, treated as an unsigned value, filling spaces before or after. */ public abstract Log unsigned(WordBase value, int fill, int align); @@ -223,12 +235,14 @@ protected Log() { public abstract Log unsigned(long value); /** - * Prints the value, treated as an unsigned value, filing spaces before or after. + * Prints the value, treated as an unsigned value, filling spaces before or after. */ public abstract Log unsigned(long value, int fill, int align); public abstract Log rational(long numerator, long denominator, long decimals); + public abstract Log rational(UnsignedWord numerator, long denominator, long decimals); + /** * Prints the value, treated as an unsigned value, in hexadecimal format. */ @@ -283,6 +297,16 @@ protected Log() { */ public abstract Log hexdump(PointerBase from, int wordSize, int numWords); + /** + * Prints a hexdump. + * + * @param from pointer to memory where dumping should start from + * @param wordSize size in bytes that a single word should have + * @param numWords number of words to dump + * @param bytesPerLine number of bytes that should be printed on one line + */ + public abstract Log hexdump(PointerBase from, int wordSize, int numWords, int bytesPerLine); + /** * Change current amount of indentation. Indentation determines the amount of spaces emitted * after each newline. @@ -393,6 +417,11 @@ public Log string(String str, int fill, int align) { return this; } + @Override + public Log string(String value, int maxLen) { + return this; + } + @Override public Log string(char[] value) { return this; @@ -443,6 +472,11 @@ public Log signed(long value) { return this; } + @Override + public Log signed(long value, int fill, int align) { + return this; + } + @Override public Log unsigned(WordBase value) { return this; @@ -473,6 +507,11 @@ public Log rational(long numerator, long denominator, long decimals) { return this; } + @Override + public Log rational(UnsignedWord numerator, long denominator, long decimals) { + return this; + } + @Override public Log hex(WordBase value) { return this; @@ -543,6 +582,11 @@ public Log hexdump(PointerBase from, int wordSize, int numWords) { return this; } + @Override + public Log hexdump(PointerBase from, int wordSize, int numWords, int bytesPerLine) { + return this; + } + @Override public Log exception(Throwable t) { return this; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/RealLog.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/RealLog.java index ba19178f8bdf..da6edc867851 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/RealLog.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/RealLog.java @@ -87,6 +87,14 @@ public Log string(String str, int fill, int align) { return this; } + @Override + @NeverInline("Logging is always slow-path code") + @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate when logging.") + public Log string(String value, int maxLen) { + rawString(value, maxLen); + return this; + } + private static final char[] NULL_CHARS = "null".toCharArray(); @Override @@ -291,6 +299,12 @@ public Log signed(long value) { return this; } + @Override + public Log signed(long value, int fill, int align) { + number(value, 10, true, fill, align); + return this; + } + @Override @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate when logging.") public Log unsigned(WordBase value) { @@ -373,6 +387,12 @@ public Log rational(long numerator, long denominator, long decimals) { return this; } + @Override + @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate when logging.") + public Log rational(UnsignedWord numerator, long denominator, long decimals) { + return rational(numerator.rawValue(), denominator, decimals); + } + @Override @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate when logging.") public Log hex(WordBase value) { @@ -560,13 +580,19 @@ public Log zhex(byte value) { } @Override - @NeverInline("Logging is always slow-path code") @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate when logging.") public Log hexdump(PointerBase from, int wordSize, int numWords) { + return hexdump(from, wordSize, numWords, 16); + } + + @Override + @NeverInline("Logging is always slow-path code") + @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate when logging.") + public Log hexdump(PointerBase from, int wordSize, int numWords, int bytesPerLine) { Pointer base = WordFactory.pointer(from.rawValue()); int sanitizedWordsize = wordSize > 0 ? Integer.highestOneBit(Math.min(wordSize, 8)) : 2; for (int offset = 0; offset < sanitizedWordsize * numWords; offset += sanitizedWordsize) { - if (offset % 16 == 0) { + if (offset % bytesPerLine == 0) { zhex(base.add(offset)); string(":"); } @@ -585,7 +611,7 @@ public Log hexdump(PointerBase from, int wordSize, int numWords) { zhex(base.readLong(offset)); break; } - if ((offset + sanitizedWordsize) % 16 == 0 && (offset + sanitizedWordsize) < sanitizedWordsize * numWords) { + if ((offset + sanitizedWordsize) % bytesPerLine == 0 && (offset + sanitizedWordsize) < sanitizedWordsize * numWords) { newline(); } } @@ -608,31 +634,41 @@ public Log exception(Throwable t, int maxFrames) { return this; } - /* - * We do not want to call getMessage(), since it can be overridden by subclasses of - * Throwable. So we access the raw detailMessage directly from the field in Throwable. That - * is better than printing nothing. - */ - String detailMessage = JDKUtils.getRawMessage(t); - StackTraceElement[] stackTrace = JDKUtils.getRawStackTrace(t); - - string(t.getClass().getName()).string(": ").string(detailMessage); - if (stackTrace != null) { - int i; - for (i = 0; i < stackTrace.length && i < maxFrames; i++) { - StackTraceElement element = stackTrace[i]; - if (element != null) { - newline(); - string(" at ").string(element.getClassName()).string(".").string(element.getMethodName()); - string("(").string(element.getFileName()).string(":").signed(element.getLineNumber()).string(")"); - } + Throwable cur = t; + int maxCauses = 25; + for (int i = 0; i < maxCauses && cur != null; i++) { + if (i > 0) { + newline().string("Caused by: "); } - int remaining = stackTrace.length - i; - if (remaining > 0) { - newline().string(" ... ").unsigned(remaining).string(" more"); + + /* + * We do not want to call getMessage(), since it can be overridden by subclasses of + * Throwable. So we access the raw detailMessage directly from the field in Throwable. + * That is better than printing nothing. + */ + String detailMessage = JDKUtils.getRawMessage(cur); + StackTraceElement[] stackTrace = JDKUtils.getRawStackTrace(cur); + + string(cur.getClass().getName()).string(": ").string(detailMessage); + if (stackTrace != null) { + int j; + for (j = 0; j < stackTrace.length && j < maxFrames; j++) { + StackTraceElement element = stackTrace[j]; + if (element != null) { + newline(); + string(" at ").string(element.getClassName()).string(".").string(element.getMethodName()); + string("(").string(element.getFileName()).string(":").signed(element.getLineNumber()).string(")"); + } + } + int remaining = stackTrace.length - j; + if (remaining > 0) { + newline().string(" ... ").unsigned(remaining).string(" more"); + } } + + cur = JDKUtils.getRawCause(cur); } - newline(); + return this; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java index 9bbd4a564846..e70d66a9ceb7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java @@ -43,7 +43,7 @@ /** * {@link JavaMonitor} is based on the code of {@link java.util.concurrent.locks.ReentrantLock} as - * of JDK 19 (git commit hash: f640fc5a1eb876a657d0de011dcd9b9a42b88eec, JDK tag: jdk-19+30). + * of JDK 21+26. * * Only the relevant methods from the JDK sources have been kept. Some additional Native * Image-specific functionality has been added. @@ -86,7 +86,7 @@ protected JavaMonitorConditionObject getOrCreateCondition(boolean createIfNotExi return existingCondition; } JavaMonitorConditionObject newCondition = new JavaMonitorConditionObject(); - if (!U.compareAndSetObject(this, CONDITION_FIELD_OFFSET, null, newCondition)) { + if (!U.compareAndSetReference(this, CONDITION_FIELD_OFFSET, null, newCondition)) { newCondition = condition; } return newCondition; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java index e21372597fe8..db217c7de5ec 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java @@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; +import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jfr.JfrTicks; import com.oracle.svm.core.jfr.SubstrateJVM; @@ -39,10 +40,9 @@ /** * {@link JavaMonitorQueuedSynchronizer} is based on the code of - * {@link java.util.concurrent.locks.AbstractQueuedLongSynchronizer} as of JDK 19 (git commit hash: - * f640fc5a1eb876a657d0de011dcd9b9a42b88eec, JDK tag: jdk-19+30). This class could be merged with - * {@link JavaMonitor} but we keep it separate because that way diffing against the JDK sources is - * easier. + * {@link java.util.concurrent.locks.AbstractQueuedLongSynchronizer} as of JDK 21+26. This class + * could be merged with {@link JavaMonitor} but we keep it separate because that way diffing against + * the JDK sources is easier. * * Only the relevant methods from the JDK sources have been kept. Some additional Native * Image-specific functionality has been added. @@ -75,12 +75,12 @@ abstract static class Node { // see AbstractQueuedLongSynchronizer.Node.casPrev(Node, Node) final boolean casPrev(Node c, Node v) { - return weakCompareAndSetReference(this, PREV, c, v); + return U.weakCompareAndSetReference(this, PREV, c, v); } // see AbstractQueuedLongSynchronizer.Node.casNext(Node, Node) final boolean casNext(Node c, Node v) { - return weakCompareAndSetReference(this, NEXT, c, v); + return U.weakCompareAndSetReference(this, NEXT, c, v); } // see AbstractQueuedLongSynchronizer.Node.getAndUnsetStatus(int) @@ -90,7 +90,7 @@ final int getAndUnsetStatus(int v) { // see AbstractQueuedLongSynchronizer.Node.setPrevRelaxed(Node) final void setPrevRelaxed(Node p) { - putReference(this, PREV, p); + U.putReference(this, PREV, p); } // see AbstractQueuedLongSynchronizer.Node.setStatusRelaxed(int) @@ -169,33 +169,52 @@ protected final boolean compareAndSetState(long expect, long update) { // see AbstractQueuedLongSynchronizer.casTail(Node, Node) private boolean casTail(Node c, Node v) { - return compareAndSetReference(this, TAIL, c, v); + return U.compareAndSetReference(this, TAIL, c, v); } // see AbstractQueuedLongSynchronizer.tryInitializeHead() - private void tryInitializeHead() { - Node h = new ExclusiveNode(); - if (compareAndSetReference(this, HEAD, null, h)) { - tail = h; + private Node tryInitializeHead() { + for (Node h = null, t;;) { + if ((t = tail) != null) { + return t; + } else if (head != null) { + Thread.onSpinWait(); + } else { + if (h == null) { + try { + h = allocateExclusiveNode(); + } catch (OutOfMemoryError oome) { + return null; + } + } + if (U.compareAndSetReference(this, HEAD, null, h)) { + return tail = h; + } + } } } // see AbstractQueuedLongSynchronizer.enqueue(Node) - final void enqueue(Node node) { + final void enqueue(ConditionNode node) { if (node != null) { - for (;;) { - Node t = tail; - node.setPrevRelaxed(t); // avoid unnecessary fence - if (t == null) { // initialize - tryInitializeHead(); - } else if (casTail(t, node)) { + boolean unpark = false; + for (Node t;;) { + if ((t = tail) == null && (t = tryInitializeHead()) == null) { + unpark = true; // wake up to spin on OOME + break; + } + node.setPrevRelaxed(t); // avoid unnecessary fence + if (casTail(t, node)) { t.next = node; - if (t.status < 0) { // wake up to clean link - LockSupport.unpark(node.waiter); + if (t.status < 0) { // wake up to clean link + unpark = true; } break; } } + if (unpark) { + LockSupport.unpark(node.waiter); + } } } @@ -298,15 +317,21 @@ final int acquire(Node node, long arg) { return 1; } } - if (node == null) { // allocate; retry before enqueue - node = new ExclusiveNode(); + Node t; + if ((t = tail) == null) { // initialize queue + if (tryInitializeHead() == null) { + return acquireOnOOME(arg); + } + } else if (node == null) { // allocate; retry before enqueue + try { + node = allocateExclusiveNode(); + } catch (OutOfMemoryError oome) { + return acquireOnOOME(arg); + } } else if (pred == null) { // try to enqueue node.waiter = current; - Node t = tail; node.setPrevRelaxed(t); // avoid unnecessary fence - if (t == null) { - tryInitializeHead(); - } else if (!casTail(t, node)) { + if (!casTail(t, node)) { node.setPrevRelaxed(null); // back out } else { t.next = node; @@ -324,6 +349,19 @@ final int acquire(Node node, long arg) { } } + // see AbstractQueuedLongSynchronizer.acquireOnOOME(boolean, long) + private int acquireOnOOME(long arg) { + for (long nanos = 1L;;) { + if (tryAcquire(arg)) { + return 1; + } + U.park(false, nanos); // must use Unsafe park to sleep + if (nanos < 1L << 30) { // max about 1 second + nanos <<= 1; + } + } + } + // see AbstractQueuedLongSynchronizer.cleanQueue() private void cleanQueue() { for (;;) { // restart point @@ -358,6 +396,11 @@ private void cleanQueue() { } } + @NeverInline("Can be removed once GR-51172 is resolved") + private static ExclusiveNode allocateExclusiveNode() { + return new ExclusiveNode(); + } + // see AbstractQueuedLongSynchronizer.cancelAcquire(Node, boolean, boolean) private int cancelAcquire(Node node) { if (node != null) { @@ -400,6 +443,8 @@ public final class JavaMonitorConditionObject { private transient ConditionNode firstWaiter; private transient ConditionNode lastWaiter; + static final long OOME_COND_WAIT_DELAY = 10L * 1000L * 1000L; // 10 ms + // see AbstractQueuedLongSynchronizer.ConditionObject.doSignal(ConditionNode, boolean) @SuppressWarnings("all") private void doSignal(ConditionNode first, boolean all) { @@ -424,8 +469,7 @@ public void signal() { ConditionNode first = firstWaiter; if (!isHeldExclusively()) { throw new IllegalMonitorStateException(); - } - if (first != null) { + } else if (first != null) { doSignal(first, false); } } @@ -435,8 +479,7 @@ public void signalAll() { ConditionNode first = firstWaiter; if (!isHeldExclusively()) { throw new IllegalMonitorStateException(); - } - if (first != null) { + } else if (first != null) { doSignal(first, true); } } @@ -495,6 +538,29 @@ private void unlinkCancelledWaiters(ConditionNode node) { } } + // see AbstractQueuedLongSynchronizer.ConditionObject.newConditionNode() + private ConditionNode newConditionNode() { + long savedState; + if (tryInitializeHead() != null) { + try { + return allocateConditionNode(); + } catch (OutOfMemoryError oome) { + } + } + // fall through if encountered OutOfMemoryError + if (!isHeldExclusively() || !release(savedState = getState())) { + throw new IllegalMonitorStateException(); + } + U.park(false, OOME_COND_WAIT_DELAY); + acquireOnOOME(savedState); + return null; + } + + @NeverInline("Can be removed once GR-51172 is resolved") + private static ConditionNode allocateConditionNode() { + return new ConditionNode(); + } + // see AbstractQueuedLongSynchronizer.ConditionObject.await() @SuppressWarnings("all") public void await(Object obj) throws InterruptedException { @@ -503,7 +569,10 @@ public void await(Object obj) throws InterruptedException { JavaMonitorWaitEvent.emit(startTicks, obj, 0, 0L, false); throw new InterruptedException(); } - ConditionNode node = new ConditionNode(); + ConditionNode node = newConditionNode(); + if (node == null) { + return; + } long savedAcquisitions = enableWait(node); boolean interrupted = false; boolean cancelled = false; @@ -540,7 +609,10 @@ public boolean await(Object obj, long time, TimeUnit unit) throws InterruptedExc JavaMonitorWaitEvent.emit(startTicks, obj, 0, 0L, false); throw new InterruptedException(); } - ConditionNode node = new ConditionNode(); + ConditionNode node = newConditionNode(); + if (node == null) { + return false; + } long savedAcquisitions = enableWait(node); long nanos = (nanosTimeout < 0L) ? 0L : nanosTimeout; long deadline = System.nanoTime() + nanos; @@ -571,21 +643,6 @@ public boolean await(Object obj, long time, TimeUnit unit) throws InterruptedExc } } - @SuppressWarnings("deprecation") - static boolean compareAndSetReference(Object object, long offset, Node expected, Node newValue) { - return U.compareAndSetObject(object, offset, expected, newValue); - } - - @SuppressWarnings("deprecation") - static boolean weakCompareAndSetReference(Object object, long offset, Node expected, Node newValue) { - return U.weakCompareAndSetObject(object, offset, expected, newValue); - } - - @SuppressWarnings("deprecation") - static void putReference(Object object, long offset, Node p) { - U.putObject(object, offset, p); - } - // Unsafe private static final Unsafe U = Unsafe.getUnsafe(); static final long STATE = U.objectFieldOffset(JavaMonitorQueuedSynchronizer.class, "state"); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MultiThreadedMonitorSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MultiThreadedMonitorSupport.java index 3d551fcffc6a..339fe9f9687b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MultiThreadedMonitorSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MultiThreadedMonitorSupport.java @@ -26,9 +26,8 @@ import java.lang.ref.ReferenceQueue; import java.util.Collections; -import java.util.HashSet; +import java.util.HashMap; import java.util.Map; -import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.LockSupport; @@ -105,10 +104,11 @@ public class MultiThreadedMonitorSupport extends MonitorSupport { * Types that are used to implement the secondary storage for monitor slots cannot themselves * use the additionalMonitors map. That could result in recursive manipulation of the * additionalMonitors map which could lead to table corruptions and double insertion of a - * monitor for the same object. Therefore these types will always get a monitor slot. + * monitor for the same object. Therefore, these types will always get a monitor slot. The + * boolean value specifies if the monitor slot is also needed for subtypes. */ @Platforms(Platform.HOSTED_ONLY.class)// - public static final Set> FORCE_MONITOR_SLOT_TYPES; + public static final Map, Boolean> FORCE_MONITOR_SLOT_TYPES; static { try { @@ -117,16 +117,16 @@ public class MultiThreadedMonitorSupport extends MonitorSupport { * com.oracle.svm.core.monitor.MultiThreadedMonitorSupport#additionalMonitors map uses * java.lang.ref.ReferenceQueue internally. */ - HashSet> monitorTypes = new HashSet<>(); + HashMap, Boolean> monitorTypes = new HashMap<>(); if (JavaVersionUtil.JAVA_SPEC <= 17) { /* * Until JDK 17, the ReferenceQueue uses the inner static class Lock for all its * locking needs. */ - monitorTypes.add(Class.forName("java.lang.ref.ReferenceQueue$Lock")); + monitorTypes.put(Class.forName("java.lang.ref.ReferenceQueue$Lock"), false); } /* The WeakIdentityHashMap also synchronizes on its internal ReferenceQueue field. */ - monitorTypes.add(java.lang.ref.ReferenceQueue.class); + monitorTypes.put(java.lang.ref.ReferenceQueue.class, false); /* * Whenever the monitor allocation in @@ -135,7 +135,7 @@ public class MultiThreadedMonitorSupport extends MonitorSupport { * LinuxPhysicalMemory$PhysicalMemorySupportImpl.sizeFromCGroup() is called which * triggers file IO using the synchronized java.io.FileDescriptor.attach(). */ - monitorTypes.add(java.io.FileDescriptor.class); + monitorTypes.put(java.io.FileDescriptor.class, false); /* * LinuxPhysicalMemory$PhysicalMemorySupportImpl.sizeFromCGroup() also calls @@ -146,7 +146,7 @@ public class MultiThreadedMonitorSupport extends MonitorSupport { * This should also take care of the synchronization in * ReferenceInternals.processPendingReferences(). */ - monitorTypes.add(java.lang.Object.class); + monitorTypes.put(java.lang.Object.class, false); /* * The map access in MultiThreadedMonitorSupport.getOrCreateMonitorFromMap() calls @@ -155,7 +155,7 @@ public class MultiThreadedMonitorSupport extends MonitorSupport { * SplittableRandomAccessors.initialize() which synchronizes on an instance of * SplittableRandomAccessors. */ - monitorTypes.add(Class.forName("com.oracle.svm.core.jdk.SplittableRandomAccessors")); + monitorTypes.put(Class.forName("com.oracle.svm.core.jdk.SplittableRandomAccessors"), false); if (JavaVersionUtil.JAVA_SPEC >= 11) { /* @@ -164,16 +164,24 @@ public class MultiThreadedMonitorSupport extends MonitorSupport { * slow-path-new-instance allocation which in turn can trigger a GC which processes * all the pending cleaners. */ - monitorTypes.add(Class.forName("jdk.internal.ref.PhantomCleanable")); + monitorTypes.put(Class.forName("jdk.internal.ref.PhantomCleanable"), false); } /* * Use as the delegate for locking on {@link Class} (i.e. {@link DynamicHub}) since the * hub itself must be immutable. */ - monitorTypes.add(DynamicHubCompanion.class); + monitorTypes.put(DynamicHubCompanion.class, false); - FORCE_MONITOR_SLOT_TYPES = Collections.unmodifiableSet(monitorTypes); + /* + * When a thread exits, it locks its own thread mutex and changes its state to + * TERMINATED. Without an explict monitor slot, the thread could get parked when + * unlocking its own mutex (because we need to lock the shared monitor map). If the + * thread gets blocked during unlocking, its thread state would change unexpectedly. + */ + monitorTypes.put(Thread.class, true); + + FORCE_MONITOR_SLOT_TYPES = Collections.unmodifiableMap(monitorTypes); } catch (ClassNotFoundException e) { throw VMError.shouldNotReachHere("Error building the list of types that always need a monitor slot.", e); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ExceptionUnwind.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ExceptionUnwind.java index 09012f39b6a7..9bc5da9f85d0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ExceptionUnwind.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ExceptionUnwind.java @@ -26,7 +26,6 @@ import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.LogHandler; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.nativeimage.hosted.Feature; @@ -55,6 +54,7 @@ import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; import com.oracle.svm.core.threadlocal.FastThreadLocalObject; +import com.oracle.svm.core.util.VMError; public abstract class ExceptionUnwind { @@ -143,8 +143,8 @@ private static void unwindExceptionInterruptible(Throwable exception, Pointer ca */ private static void reportRecursiveUnwind(Throwable exception) { Log.log().string("Fatal error: recursion in exception handling: ").string(exception.getClass().getName()); - Log.log().string(" thrown while unwinding ").string(currentException.get().getClass().getName()).newline(); - ImageSingletons.lookup(LogHandler.class).fatalError(); + Log.log().string(" thrown while unwinding ").string(currentException.get().getClass().getName()).newline().newline(); + VMError.shouldNotReachHere("Recursion in exception handling"); } /** @@ -157,8 +157,8 @@ private static void reportRecursiveUnwind(Throwable exception) { */ private static void reportFatalUnwind(Throwable exception) { Log.log().string("Fatal error: exception unwind while thread is not in Java state: "); - Log.log().exception(exception); - ImageSingletons.lookup(LogHandler.class).fatalError(); + Log.log().exception(exception).newline().newline(); + VMError.shouldNotReachHere("Exception unwind while thread is not in Java state"); } /** @@ -169,8 +169,8 @@ private static void reportFatalUnwind(Throwable exception) { */ private static void reportUnhandledException(Throwable exception) { Log.log().string("Fatal error: unhandled exception in isolate ").hex(CurrentIsolate.getIsolate()).string(": "); - Log.log().exception(exception); - ImageSingletons.lookup(LogHandler.class).fatalError(); + Log.log().exception(exception).newline().newline(); + VMError.shouldNotReachHere("Unhandled exception"); } /** Hook to allow a {@link Feature} to install custom exception unwind code. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ImplicitExceptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ImplicitExceptions.java index ea7e4814bd91..ca8f86796837 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ImplicitExceptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ImplicitExceptions.java @@ -27,6 +27,7 @@ import java.lang.reflect.GenericSignatureFormatError; import com.oracle.svm.core.SubstrateDiagnostics; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.code.FactoryMethodMarker; import com.oracle.svm.core.heap.RestrictHeapAccess; import com.oracle.svm.core.jdk.InternalVMMethod; @@ -157,8 +158,10 @@ public static void deactivateImplicitExceptionsAreFatal() { implicitExceptionsAreFatal.set(implicitExceptionsAreFatal.get() - 1); } - private static void vmErrorIfImplicitExceptionsAreFatal() { - if ((implicitExceptionsAreFatal.get() > 0 || ExceptionUnwind.exceptionsAreFatal()) && !SubstrateDiagnostics.isFatalErrorHandlingThread()) { + private static void vmErrorIfImplicitExceptionsAreFatal(boolean cachedException) { + if (cachedException && SubstrateOptions.ImplicitExceptionWithoutStacktraceIsFatal.getValue()) { + throw VMError.shouldNotReachHere("AssertionError without stack trace."); + } else if ((implicitExceptionsAreFatal.get() > 0 || ExceptionUnwind.exceptionsAreFatal()) && !SubstrateDiagnostics.isFatalErrorHandlingThread()) { throw VMError.shouldNotReachHere("Implicit exception thrown in code where such exceptions are fatal errors"); } } @@ -166,21 +169,21 @@ private static void vmErrorIfImplicitExceptionsAreFatal() { /** Foreign call: {@link #CREATE_NULL_POINTER_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static NullPointerException createNullPointerException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new NullPointerException(); } /** Foreign call: {@link #CREATE_OUT_OF_BOUNDS_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArrayIndexOutOfBoundsException createIntrinsicOutOfBoundsException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new ArrayIndexOutOfBoundsException(); } /** Foreign call: {@link #CREATE_OUT_OF_BOUNDS_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArrayIndexOutOfBoundsException createOutOfBoundsException(int index, int length) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); /* * JDK 11 added the length to the error message, we can do that for all Java versions to be * consistent. @@ -192,7 +195,7 @@ private static ArrayIndexOutOfBoundsException createOutOfBoundsException(int ind @SubstrateForeignCallTarget(stubCallingConvention = true) private static ClassCastException createClassCastException(Object object, Object expectedClass) { assert object != null : "null can be cast to any type, so it cannot show up as a source of a ClassCastException"; - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); String expectedClassName; if (expectedClass instanceof Class) { expectedClassName = ((Class) expectedClass).getTypeName(); @@ -206,84 +209,84 @@ private static ClassCastException createClassCastException(Object object, Object @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArrayStoreException createArrayStoreException(Object value) { assert value != null : "null can be stored into any array, so it cannot show up as a source of an ArrayStoreException"; - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new ArrayStoreException(value.getClass().getTypeName()); } /** Foreign call: {@link #CREATE_INCOMPATIBLE_CLASS_CHANGE_ERROR}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static IncompatibleClassChangeError createIncompatibleClassChangeError() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new IncompatibleClassChangeError(); } /** Foreign call: {@link #CREATE_ILLEGAL_ARGUMENT_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static IllegalArgumentException createIllegalArgumentException(String message) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new IllegalArgumentException(message); } /** Foreign call: {@link #CREATE_NEGATIVE_ARRAY_SIZE_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static NegativeArraySizeException createNegativeArraySizeException(int length) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new NegativeArraySizeException(String.valueOf(length)); } /** Foreign call: {@link #CREATE_DIVISION_BY_ZERO_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArithmeticException createDivisionByZeroException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new ArithmeticException("/ by zero"); } /** Foreign call: {@link #CREATE_INTEGER_OVERFLOW_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArithmeticException createIntegerOverflowException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new ArithmeticException("integer overflow"); } /** Foreign call: {@link #CREATE_LONG_OVERFLOW_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArithmeticException createLongOverflowException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new ArithmeticException("long overflow"); } /** Foreign call: {@link #CREATE_ASSERTION_ERROR_NULLARY}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static AssertionError createAssertionErrorNullary() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new AssertionError(); } /** Foreign call: {@link #CREATE_ASSERTION_ERROR_OBJECT}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static AssertionError createAssertionErrorObject(Object detailMessage) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new AssertionError(detailMessage); } /** Foreign call: {@link #THROW_NEW_NULL_POINTER_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewNullPointerException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new NullPointerException(); } /** Foreign call: {@link #THROW_NEW_INTRINSIC_OUT_OF_BOUNDS_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewIntrinsicOutOfBoundsException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArrayIndexOutOfBoundsException(); } /** Foreign call: {@link #THROW_NEW_OUT_OF_BOUNDS_EXCEPTION_WITH_ARGS}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewOutOfBoundsExceptionWithArgs(int index, int length) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); /* * JDK 11 added the length to the error message, we can do that for all Java versions to be * consistent. @@ -294,7 +297,7 @@ private static void throwNewOutOfBoundsExceptionWithArgs(int index, int length) /** Foreign call: {@link #THROW_NEW_CLASS_CAST_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewClassCastException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ClassCastException(); } @@ -302,7 +305,7 @@ private static void throwNewClassCastException() { @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewClassCastExceptionWithArgs(Object object, Object expectedClass) { assert object != null : "null can be cast to any type, so it cannot show up as a source of a ClassCastException"; - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); String expectedClassName; if (expectedClass instanceof Class) { expectedClassName = ((Class) expectedClass).getTypeName(); @@ -315,7 +318,7 @@ private static void throwNewClassCastExceptionWithArgs(Object object, Object exp /** Foreign call: {@link #THROW_NEW_ARRAY_STORE_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewArrayStoreException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArrayStoreException(); } @@ -323,70 +326,70 @@ private static void throwNewArrayStoreException() { @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewArrayStoreExceptionWithArgs(Object value) { assert value != null : "null can be stored into any array, so it cannot show up as a source of an ArrayStoreException"; - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArrayStoreException(value.getClass().getTypeName()); } /** Foreign call: {@link #THROW_NEW_INCOMPATIBLE_CLASS_CHANGE_ERROR}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewIncompatibleClassChangeError() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new IncompatibleClassChangeError(); } /** Foreign call: {@link #THROW_NEW_ILLEGAL_ARGUMENT_EXCEPTION_WITH_ARGS}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewIllegalArgumentExceptionWithArgs(String message) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new IllegalArgumentException(message); } /** Foreign call: {@link #THROW_NEW_NEGATIVE_ARRAY_SIZE_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewNegativeArraySizeException(int length) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new NegativeArraySizeException(String.valueOf(length)); } /** Foreign call: {@link #THROW_NEW_ARITHMETIC_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewArithmeticException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArithmeticException(); } /** Foreign call: {@link #THROW_NEW_DIVISION_BY_ZERO_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewDivisionByZeroException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArithmeticException("/ by zero"); } /** Foreign call: {@link #THROW_NEW_INTEGER_OVERFLOW_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewIntegerOverflowException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArithmeticException("integer overflow"); } /** Foreign call: {@link #THROW_NEW_LONG_OVERFLOW_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewLongOverflowException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArithmeticException("long overflow"); } /** Foreign call: {@link #THROW_NEW_ASSERTION_ERROR_NULLARY}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewAssertionErrorNullary() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new AssertionError(); } /** Foreign call: {@link #THROW_NEW_ASSERTION_ERROR_OBJECT}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewAssertionErrorObject(Object detailMessage) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new AssertionError(detailMessage); } @@ -394,7 +397,7 @@ private static void throwNewAssertionErrorObject(Object detailMessage) { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static NullPointerException getCachedNullPointerException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_NULL_POINTER_EXCEPTION; } @@ -402,7 +405,7 @@ private static NullPointerException getCachedNullPointerException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArrayIndexOutOfBoundsException getCachedOutOfBoundsException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_OUT_OF_BOUNDS_EXCEPTION; } @@ -410,7 +413,7 @@ private static ArrayIndexOutOfBoundsException getCachedOutOfBoundsException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static ClassCastException getCachedClassCastException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_CLASS_CAST_EXCEPTION; } @@ -418,7 +421,7 @@ private static ClassCastException getCachedClassCastException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArrayStoreException getCachedArrayStoreException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_ARRAY_STORE_EXCEPTION; } @@ -426,7 +429,7 @@ private static ArrayStoreException getCachedArrayStoreException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static IncompatibleClassChangeError getCachedIncompatibleClassChangeError() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_INCOMPATIBLE_CLASS_CHANGE_ERROR; } @@ -434,7 +437,7 @@ private static IncompatibleClassChangeError getCachedIncompatibleClassChangeErro @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static IllegalArgumentException getCachedIllegalArgumentException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_ILLEGAL_ARGUMENT_EXCEPTION; } @@ -442,7 +445,7 @@ private static IllegalArgumentException getCachedIllegalArgumentException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static NegativeArraySizeException getCachedNegativeArraySizeException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_NEGATIVE_ARRAY_SIZE_EXCEPTION; } @@ -450,7 +453,7 @@ private static NegativeArraySizeException getCachedNegativeArraySizeException() @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArithmeticException getCachedArithmeticException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_ARITHMETIC_EXCEPTION; } @@ -458,7 +461,7 @@ private static ArithmeticException getCachedArithmeticException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static AssertionError getCachedAssertionError() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_ASSERTION_ERROR; } @@ -466,7 +469,7 @@ private static AssertionError getCachedAssertionError() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedNullPointerException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_NULL_POINTER_EXCEPTION; } @@ -474,7 +477,7 @@ private static void throwCachedNullPointerException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedOutOfBoundsException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_OUT_OF_BOUNDS_EXCEPTION; } @@ -482,7 +485,7 @@ private static void throwCachedOutOfBoundsException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedClassCastException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_CLASS_CAST_EXCEPTION; } @@ -490,7 +493,7 @@ private static void throwCachedClassCastException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedArrayStoreException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_ARRAY_STORE_EXCEPTION; } @@ -498,7 +501,7 @@ private static void throwCachedArrayStoreException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedIncompatibleClassChangeError() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_INCOMPATIBLE_CLASS_CHANGE_ERROR; } @@ -506,7 +509,7 @@ private static void throwCachedIncompatibleClassChangeError() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedIllegalArgumentException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_ILLEGAL_ARGUMENT_EXCEPTION; } @@ -514,7 +517,7 @@ private static void throwCachedIllegalArgumentException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedNegativeArraySizeException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_NEGATIVE_ARRAY_SIZE_EXCEPTION; } @@ -522,7 +525,7 @@ private static void throwCachedNegativeArraySizeException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedArithmeticException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_ARITHMETIC_EXCEPTION; } @@ -530,7 +533,7 @@ private static void throwCachedArithmeticException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedAssertionError() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_ASSERTION_ERROR; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaStackWalker.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaStackWalker.java index 10bd6811884c..d61c918290a0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaStackWalker.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaStackWalker.java @@ -298,6 +298,7 @@ public static boolean walkThread(IsolateThread thread, ParameterizedStackFrameVi @Uninterruptible(reason = "Prevent deoptimization of stack frames while in this method.") public static boolean walkThread(IsolateThread thread, Pointer endSP, ParameterizedStackFrameVisitor visitor, Object data) { + assert thread.isNonNull(); JavaStackWalk walk = StackValue.get(JavaStackWalk.class); if (initWalk(walk, thread)) { walk.setEndSP(endSP); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java index 3e434fb3949d..447da531ab1b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java @@ -27,6 +27,7 @@ import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.Pointer; +import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.Uninterruptible; @@ -53,7 +54,7 @@ public StackFramePrintVisitor() { @Override protected void logFrame(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptFrame) { if (deoptFrame != null) { - logVirtualFrames(log, sp, ip, deoptFrame); + logVirtualFrames(log, sp, ip, codeInfo, deoptFrame); return; } @@ -68,17 +69,21 @@ protected void logFrame(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo, if (!isFirst) { log.newline(); } - logFrameRaw(log, sp, ip); - FrameInfoQueryResult frame = frameInfoCursor.get(); - logFrameInfo(log, frame, CodeInfoAccess.getName(codeInfo)); + boolean compilationRoot = !frameInfoCursor.hasCaller(); + printFrameIdentifier(log, codeInfo, null, compilationRoot); + logFrameRaw(log, sp, ip, codeInfo); + + String codeInfoName = DeoptimizationSupport.enabled() ? CodeInfoAccess.getName(codeInfo) : null; + logFrameInfo(log, frameInfoCursor.get(), codeInfoName); isFirst = false; printedFrames++; } if (isFirst) { - /* The code above failed, so print less detailed information. */ - super.logFrame(log, sp, ip, codeInfo, deoptFrame); + /* We don't have any metadata, so print less detailed information. */ + super.logFrame(log, sp, ip, codeInfo, null); + log.string("missing metadata"); } } } @@ -111,7 +116,7 @@ protected final boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo @Override protected final boolean unknownFrame(Pointer sp, CodePointer ip, DeoptimizedFrame deoptimizedFrame, Object data) { Log log = (Log) data; - logFrameRaw(log, sp, ip); + logFrameRaw(log, sp, ip, WordFactory.nullPointer()); if (DeoptimizationSupport.enabled()) { log.string(" deoptFrame=").object(deoptimizedFrame); } @@ -122,14 +127,17 @@ protected final boolean unknownFrame(Pointer sp, CodePointer ip, DeoptimizedFram @SuppressWarnings("unused") protected void logFrame(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptFrame) { - logFrameRaw(log, sp, ip); - log.string(" FrameSize ").signed(CodeInfoAccess.lookupTotalFrameSize(codeInfo, CodeInfoAccess.relativeIP(codeInfo, ip))); + logFrameRaw(log, sp, ip, codeInfo); printedFrames++; } - protected static void logFrameRaw(Log log, Pointer sp, CodePointer ip) { + protected static void logFrameRaw(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo) { log.string("SP ").zhex(sp); log.string(" IP ").zhex(ip); + if (codeInfo.isNonNull()) { + long frameSize = CodeInfoAccess.lookupTotalFrameSize(codeInfo, CodeInfoAccess.relativeIP(codeInfo, ip)); + log.string(" size=").signed(frameSize, 4, Log.LEFT_ALIGN); + } } } @@ -140,22 +148,24 @@ public Stage1StackFramePrintVisitor() { @Override protected void logFrame(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptFrame) { if (deoptFrame != null) { - logVirtualFrames(log, sp, ip, deoptFrame); + logVirtualFrames(log, sp, ip, codeInfo, deoptFrame); } else { logStackFrame(log, sp, ip, codeInfo); } } - protected void logVirtualFrames(Log log, Pointer sp, CodePointer ip, DeoptimizedFrame deoptFrame) { + protected void logVirtualFrames(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptFrame) { for (DeoptimizedFrame.VirtualFrame frame = deoptFrame.getTopFrame(); frame != null; frame = frame.getCaller()) { if (printedFrames >= MAX_STACK_FRAMES_PER_THREAD_TO_PRINT) { log.string("... (truncated)").newline(); break; } - logFrameRaw(log, sp, ip); + boolean compilationRoot = frame.getCaller() == null; + printFrameIdentifier(log, WordFactory.nullPointer(), deoptFrame, compilationRoot); + logFrameRaw(log, sp, ip, codeInfo); logFrameInfo(log, frame.getFrameInfo(), ImageCodeInfo.CODE_INFO_NAME + ", deopt"); - if (frame.getCaller() != null) { + if (!compilationRoot) { log.newline(); } printedFrames++; @@ -163,10 +173,12 @@ protected void logVirtualFrames(Log log, Pointer sp, CodePointer ip, Deoptimized } private void logStackFrame(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo) { - logFrameRaw(log, sp, ip); + printFrameIdentifier(log, codeInfo, null, true); + logFrameRaw(log, sp, ip, codeInfo); log.spaces(2); - CodeInfoAccess.log(codeInfo, log); - log.string(" name = ").string(CodeInfoAccess.getName(codeInfo)); + if (DeoptimizationSupport.enabled()) { + log.string("[").string(CodeInfoAccess.getName(codeInfo)).string("] "); + } printedFrames++; } @@ -177,9 +189,28 @@ protected static void logFrameInfo(Log log, FrameInfoQueryResult frameInfo, Stri } frameInfo.log(log); } + + protected static void printFrameIdentifier(Log log, CodeInfo codeInfo, DeoptimizedFrame deoptFrame, boolean isCompilationRoot) { + char ch = getFrameIdentifier(codeInfo, deoptFrame, isCompilationRoot); + log.character(ch).spaces(2); + } + + private static char getFrameIdentifier(CodeInfo codeInfo, DeoptimizedFrame deoptFrame, boolean isCompilationRoot) { + if (deoptFrame != null) { + return 'D'; + } else if (!isCompilationRoot) { + return 'i'; + } else if (codeInfo == CodeInfoTable.getImageCodeInfo()) { + return 'A'; + } else { + return 'J'; + } + } } - /** Walk the stack printing each frame. */ + /** + * Walk the stack printing each frame. + */ @NeverInline("debugger breakpoint") @Uninterruptible(reason = "Called from uninterruptible code.") public static void printBacktrace() { @@ -204,7 +235,7 @@ public static boolean printStacktrace(Pointer startSP, CodePointer startIP, Stag @Uninterruptible(reason = "CodeInfo in JavaStackWalk is currently null, so printing to log is safe right now.", calleeMustBe = false) private static void logFrameAnchor(Log log, Pointer startSP, CodePointer startIP) { - Stage0StackFramePrintVisitor.logFrameRaw(log, startSP, startIP); + Stage0StackFramePrintVisitor.logFrameRaw(log, startSP, startIP, WordFactory.nullPointer()); log.string(" IP is not within Java code. Trying frame anchor of last Java frame instead.").newline(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java index 53dacdd1b157..44fbffc362bf 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java @@ -46,6 +46,7 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.log.Log; import com.oracle.svm.core.jdk.JDK19OrLater; import com.oracle.svm.core.jfr.events.ThreadSleepEventJDK17; import com.oracle.svm.core.snippets.KnownIntrinsics; @@ -245,26 +246,27 @@ public static StackTraceElement[] getStackTrace(boolean filterExceptions, Thread return PlatformThreads.getStackTrace(filterExceptions, thread, callerSP); } - /** If there is an uncaught exception handler, call it. */ public static void dispatchUncaughtException(Thread thread, Throwable throwable) { - /* Get the uncaught exception handler for the Thread, or the default one. */ - UncaughtExceptionHandler handler = thread.getUncaughtExceptionHandler(); - if (handler == null) { - handler = Thread.getDefaultUncaughtExceptionHandler(); - } - if (handler != null) { - try { + try { + /* Get the uncaught exception handler for the Thread, or the default one. */ + UncaughtExceptionHandler handler = thread.getUncaughtExceptionHandler(); + if (handler == null) { + handler = Thread.getDefaultUncaughtExceptionHandler(); + } + + if (handler != null) { handler.uncaughtException(thread, throwable); - } catch (Throwable t) { + } else { /* - * The JavaDoc for {@code Thread.UncaughtExceptionHandler.uncaughtException} says - * the VM ignores any exceptions thrown. + * If no uncaught exception handler is present, then just report the Throwable in + * the same way as it is done by ThreadGroup.uncaughtException(). */ + System.err.print("Exception in thread \"" + thread.getName() + "\" "); + throwable.printStackTrace(System.err); } - } else { - /* If no uncaught exception handler is present, then just report the throwable. */ - System.err.print("Exception in thread \"" + Thread.currentThread().getName() + "\" "); - throwable.printStackTrace(); + } catch (Throwable e) { + /* See JavaThread::exit() in HotSpot. */ + Log.log().newline().string("Exception: ").string(e.getClass().getName()).string(" thrown from the UncaughtExceptionHandler in thread \"").string(thread.getName()).string("\"").newline(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/LoomVirtualThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/LoomVirtualThreads.java index 6462334e5b43..bc1ecf0ba15d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/LoomVirtualThreads.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/LoomVirtualThreads.java @@ -153,7 +153,7 @@ public StackTraceElement[] getVirtualOrPlatformThreadStackTrace(boolean filterEx if (!isVirtual(thread)) { return getPlatformThreadStackTrace(filterExceptions, thread, callerSP); } - if (thread == Thread.currentThread()) { + if (thread != null && thread == Thread.currentThread()) { return getVirtualThreadStackTrace(filterExceptions, thread, callerSP); } assert !filterExceptions : "exception stack traces can be taken only for the current thread"; @@ -217,7 +217,7 @@ private static StackTraceElement[] asyncMountedGetStackTrace(Target_java_lang_Vi } private static StackTraceElement[] getPlatformThreadStackTrace(boolean filterExceptions, Thread thread, Pointer callerSP) { - if (thread == PlatformThreads.currentThread.get()) { + if (thread != null && thread == PlatformThreads.currentThread.get()) { Pointer startSP = getCarrierSPOrElse(thread, callerSP); return StackTraceUtils.getStackTrace(filterExceptions, startSP, WordFactory.nullPointer()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java index fd91ae3cfa1f..98ac68168513 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java @@ -25,6 +25,7 @@ package com.oracle.svm.core.thread; import static com.oracle.svm.core.SubstrateOptions.MultiThreaded; +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; import static com.oracle.svm.core.thread.JavaThreads.fromTarget; import static com.oracle.svm.core.thread.JavaThreads.isCurrentThreadVirtual; import static com.oracle.svm.core.thread.JavaThreads.isVirtual; @@ -62,12 +63,15 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.UnmanagedMemory; import org.graalvm.nativeimage.c.function.CEntryPoint; +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.type.CCharPointer; import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; +import org.graalvm.word.WordBase; import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; @@ -78,6 +82,12 @@ import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; +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; import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.heap.ReferenceHandler; import com.oracle.svm.core.heap.ReferenceHandlerThread; @@ -91,6 +101,7 @@ import com.oracle.svm.core.nodes.CFunctionEpilogueNode; import com.oracle.svm.core.nodes.CFunctionPrologueNode; import com.oracle.svm.core.stack.StackOverflowCheck; +import com.oracle.svm.core.thread.VMThreads.OSThreadHandle; import com.oracle.svm.core.thread.VMThreads.StatusSupport; import com.oracle.svm.core.threadlocal.FastThreadLocal; import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; @@ -121,9 +132,14 @@ public static PlatformThreads singleton() { return ImageSingletons.lookup(PlatformThreads.class); } + protected static final CEntryPointLiteral threadStartRoutine = CEntryPointLiteral.create(PlatformThreads.class, "threadStartRoutine", ThreadStartData.class); + /** The platform {@link java.lang.Thread} for the {@link IsolateThread}. */ static final FastThreadLocalObject currentThread = FastThreadLocalFactory.createObject(Thread.class, "PlatformThreads.currentThread").setMaxOffset(FastThreadLocal.BYTE_OFFSET); + /** The number of running non-daemon threads. */ + private static final UninterruptibleUtils.AtomicInteger nonDaemonThreads = new UninterruptibleUtils.AtomicInteger(0); + /** * The {@linkplain JavaThreads#getThreadId thread id} of the {@link Thread#currentThread()}, * which can be a {@linkplain Target_java_lang_Thread#vthread virtual thread} or the @@ -141,12 +157,6 @@ public static PlatformThreads singleton() { */ static final FastThreadLocalObject lockHelper = FastThreadLocalFactory.createObject(Object.class, "PlatformThreads.lockHelper").setMaxOffset(FastThreadLocal.BYTE_OFFSET); - /** - * The number of running non-daemon threads. The initial value accounts for the main thread, - * which is implicitly running when the isolate is created. - */ - private static final UninterruptibleUtils.AtomicInteger nonDaemonThreads = new UninterruptibleUtils.AtomicInteger(1); - /** * Tracks the number of threads that have been started, but are not yet executing Java code. For * a small window of time, threads are still accounted for in this count while they are already @@ -303,10 +313,12 @@ public static IsolateThread getIsolateThreadUnsafe(Thread t) { * Returns the isolate thread associated with a Java thread. The caller must own the * {@linkplain VMThreads#THREAD_MUTEX threads mutex} and release it only after it has finished * using the returned {@link IsolateThread} pointer. + * + * This method can return {@code NULL} if the thread is not alive or if it has been recently + * started but has not completed initialization yet. */ public static IsolateThread getIsolateThread(Thread t) { VMThreads.guaranteeOwnsThreadMutex("Threads mutex must be locked before accessing/iterating the thread list."); - VMError.guarantee(t.isAlive(), "Only running java.lang.Thread objects have a IsolateThread"); return getIsolateThreadUnsafe(t); } @@ -449,7 +461,11 @@ public static boolean ensureCurrentAssigned() { */ public static boolean ensureCurrentAssigned(String name, ThreadGroup group, boolean asDaemon) { if (currentThread.get() == null) { - assignCurrent(fromTarget(new Target_java_lang_Thread(name, group, asDaemon)), true); + boolean wasStartedByCurrentIsolated = VMThreads.wasStartedByCurrentIsolate(CurrentIsolate.getCurrentThread()); + Thread thread = fromTarget(new Target_java_lang_Thread(name, group, asDaemon)); + assignCurrent(thread, wasStartedByCurrentIsolated); + assignThreadToThreadGroup(thread, wasStartedByCurrentIsolated); + ThreadListenerSupport.get().beforeThreadRun(); return true; } return false; @@ -457,32 +473,28 @@ public static boolean ensureCurrentAssigned(String name, ThreadGroup group, bool /** * Assign a {@link Thread} object to the current thread, which must have already been attached - * {@link VMThreads} as an {@link IsolateThread}. - * - * The manuallyStarted parameter is true if this thread was started directly by calling - * {@link #ensureCurrentAssigned(String, ThreadGroup, boolean)}. It is false when the thread is - * started via {@link #doStartThread} and {@link #threadStartRoutine}. + * as an {@link IsolateThread}. */ - static void assignCurrent(Thread thread, boolean manuallyStarted) { + @Uninterruptible(reason = "Ensure consistency of nonDaemonThreads.") + static void assignCurrent(Thread thread, boolean wasStartedByCurrentIsolated) { + if (!wasStartedByCurrentIsolated && thread.isDaemon()) { + /* Correct the value of nonDaemonThreads, now that we have a Thread object. */ + decrementNonDaemonThreadsAndNotify(); + } + /* - * First of all, ensure we are in RUNNABLE state. If !manuallyStarted, we race with the - * thread that launched us to set the status and we could still be in status NEW. + * First of all, ensure we are in RUNNABLE state. If wasStartedByCurrentIsolated, we race + * with the thread that launched us to set the status and we could still be in status NEW. */ setThreadStatus(thread, ThreadStatus.RUNNABLE); - assignCurrent0(thread); + } - /* If the thread was manually started, finish initializing it. */ - if (manuallyStarted) { - final ThreadGroup group = thread.getThreadGroup(); - if (JavaVersionUtil.JAVA_SPEC < 19 && !(VirtualThreads.isSupported() && VirtualThreads.singleton().isVirtual(thread))) { - toTarget(group).addUnstarted(); - toTarget(group).add(thread); - } - - if (!thread.isDaemon()) { - nonDaemonThreads.incrementAndGet(); - } + private static void assignThreadToThreadGroup(Thread thread, boolean wasStartedByCurrentIsolated) { + if (JavaVersionUtil.JAVA_SPEC < 19 && !wasStartedByCurrentIsolated && !(VirtualThreads.isSupported() && VirtualThreads.singleton().isVirtual(thread))) { + ThreadGroup group = thread.getThreadGroup(); + toTarget(group).addUnstarted(); + toTarget(group).add(thread); } } @@ -505,10 +517,15 @@ static void setCurrentThread(Thread carrier, Thread thread) { currentVThreadId.set(JavaThreads.getThreadId(thread)); } - @Uninterruptible(reason = "Called during isolate initialization") - public void initializeIsolate() { + @Uninterruptible(reason = "Called during isolate creation.") + public void assignMainThread() { /* The thread that creates the isolate is considered the "main" thread. */ assignCurrent0(mainThread); + + /* + * Note that we can't call ThreadListenerSupport.beforeThreadRun() because the isolate is + * not fully initialized yet. This is done later on, during isolate initialization. + */ } /** @@ -541,9 +558,17 @@ public static void detachThread(IsolateThread vmThread) { if (thread != null) { toTarget(thread).threadData.detach(); toTarget(thread).isolateThread = WordFactory.nullPointer(); + if (!thread.isDaemon()) { - nonDaemonThreads.decrementAndGet(); + decrementNonDaemonThreads(); } + } else if (!VMThreads.wasStartedByCurrentIsolate(vmThread)) { + /* + * Attached threads are treated like non-daemon threads before they are assigned a + * thread object which defines whether they are a daemon thread (which might never + * happen). + */ + decrementNonDaemonThreads(); } } @@ -562,7 +587,7 @@ public boolean joinThreadUnmanaged(OSThreadHandle threadHandle, WordPointer thre @SuppressWarnings("unused") @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void closeOSThreadHandle(OSThreadHandle threadHandle) { - throw VMError.shouldNotReachHere("Shouldn't call PlatformThreads.closeOSThreadHandle directly."); + /* On most platforms, OS thread handles don't need to be closed. */ } static final Method FORK_JOIN_POOL_TRY_TERMINATE_METHOD; @@ -601,7 +626,7 @@ private static boolean tearDownPlatformThreads() { try { thread.interrupt(); // not final and subclasses can unexpectedly throw } catch (Throwable t) { - trace.string(" threw (ignored): ").exception(t); + trace.string(" threw (ignored): ").exception(t).newline(); } trace.newline().flush(); @@ -642,7 +667,7 @@ private static boolean tearDownPlatformThreads() { pool.shutdownNow(); } } catch (Throwable t) { - trace.string(" threw (ignored): ").exception(t); + trace.string(" threw (ignored): ").exception(t).newline(); } trace.newline().flush(); trace.string(" shutdown initiated: ").object(pool).newline().flush(); @@ -678,7 +703,6 @@ private static boolean waitForTearDown() { loopNanos = TimeUtils.doNotLoopTooLong(startNanos, loopNanos, warningNanos, warningMessage); final boolean fatallyTooLong = TimeUtils.maybeFatallyTooLong(startNanos, failureNanos, failureMessage); if (fatallyTooLong) { - /* I took too long to tear down the VM. */ trace.string("Took too long to tear down the VM.").newline(); /* * Debugging tip: Insert a `BreakpointNode.breakpoint()` here to stop in gdb or get @@ -701,21 +725,25 @@ private static boolean isApplicationThread(IsolateThread isolateThread) { @SuppressFBWarnings(value = "NN", justification = "notifyAll is necessary for Java semantics, no shared state needs to be modified beforehand") public static void exit(Thread thread) { ThreadListenerSupport.get().afterThreadRun(); + /* * First call Thread.exit(). This allows waiters on the thread object to observe that a * daemon ThreadGroup is destroyed as well if this thread happens to be the last thread of a * daemon group. */ - toTarget(thread).exit(); - /* - * Then set the threadStatus to TERMINATED. This makes Thread.isAlive() return false and - * allows Thread.join() to complete once we notify all the waiters below. - */ - setThreadStatus(thread, ThreadStatus.TERMINATED); - /* - * And finally, wake up any threads waiting to join this one. - */ + try { + toTarget(thread).exit(); + } catch (Throwable e) { + /* Ignore exception. */ + } + synchronized (thread) { + /* + * Then set the threadStatus to TERMINATED. This makes Thread.isAlive() return false and + * allows Thread.join() to complete once we notify all the waiters below. + */ + setThreadStatus(thread, ThreadStatus.TERMINATED); + /* And finally, wake up any threads waiting to join this one. */ thread.notifyAll(); } } @@ -737,27 +765,67 @@ protected interface ThreadStartData extends PointerBase { } protected T prepareStart(Thread thread, int startDataSize) { - T startData = UnmanagedMemory.malloc(startDataSize); - startData.setIsolate(CurrentIsolate.getIsolate()); - startData.setThreadHandle(ObjectHandles.getGlobal().create(thread)); - if (!thread.isDaemon()) { - nonDaemonThreads.incrementAndGet(); + T startData = WordFactory.nullPointer(); + ObjectHandle threadHandle = WordFactory.zero(); + try { + startData = UnmanagedMemory.malloc(startDataSize); + threadHandle = ObjectHandles.getGlobal().create(thread); + + startData.setIsolate(CurrentIsolate.getIsolate()); + startData.setThreadHandle(threadHandle); + } catch (Throwable e) { + if (startData.isNonNull()) { + UnmanagedMemory.free(startData); + } + if (threadHandle.notEqual(WordFactory.zero())) { + ObjectHandles.getGlobal().destroy(threadHandle); + } + throw e; + } + + /* To ensure that we have consistent thread counts, no exception must be thrown. */ + try { + int numThreads = unattachedStartedThreads.incrementAndGet(); + assert numThreads > 0; + + if (!thread.isDaemon()) { + incrementNonDaemonThreads(); + } + return startData; + } catch (Throwable e) { + throw VMError.shouldNotReachHere("No exception must be thrown after creating the thread start data.", e); } - return startData; } protected void undoPrepareStartOnError(Thread thread, ThreadStartData startData) { if (!thread.isDaemon()) { - undoPrepareNonDaemonStartOnError(); + decrementNonDaemonThreadsAndNotify(); } + + int numThreads = unattachedStartedThreads.decrementAndGet(); + assert numThreads >= 0; + freeStartData(startData); } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + static void incrementNonDaemonThreads() { + int numThreads = nonDaemonThreads.incrementAndGet(); + assert numThreads > 0; + } + + /** A caller must call THREAD_LIST_CONDITION.broadcast() manually. */ + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static void decrementNonDaemonThreads() { + int numThreads = nonDaemonThreads.decrementAndGet(); + assert numThreads >= 0; + } + @Uninterruptible(reason = "Holding threads lock.") - private static void undoPrepareNonDaemonStartOnError() { + private static void decrementNonDaemonThreadsAndNotify() { VMThreads.lockThreadMutexInNativeCode(); try { - nonDaemonThreads.decrementAndGet(); + decrementNonDaemonThreads(); VMThreads.THREAD_LIST_CONDITION.broadcast(); } finally { VMThreads.THREAD_MUTEX.unlock(); @@ -769,10 +837,8 @@ protected static void freeStartData(ThreadStartData startData) { } void startThread(Thread thread, long stackSize) { - unattachedStartedThreads.incrementAndGet(); boolean started = doStartThread(thread, stackSize); if (!started) { - unattachedStartedThreads.decrementAndGet(); throw new OutOfMemoryError("unable to create native thread: possibly out of memory or process/resource limits reached"); } } @@ -785,16 +851,28 @@ void startThread(Thread thread, long stackSize) { */ protected abstract boolean doStartThread(Thread thread, long stackSize); + @CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished) + @CEntryPointOptions(prologue = ThreadStartRoutinePrologue.class, epilogue = CEntryPointSetup.LeaveDetachThreadEpilogue.class) + protected static WordBase threadStartRoutine(ThreadStartData data) { + ObjectHandle threadHandle = data.getThreadHandle(); + freeStartData(data); + + threadStartRoutine(threadHandle); + return WordFactory.nullPointer(); + } + @SuppressFBWarnings(value = "Ru", justification = "We really want to call Thread.run and not Thread.start because we are in the low-level thread start routine") protected static void threadStartRoutine(ObjectHandle threadHandle) { Thread thread = ObjectHandles.getGlobal().get(threadHandle); - assignCurrent(thread, false); - ObjectHandles.getGlobal().destroy(threadHandle); - - singleton().unattachedStartedThreads.decrementAndGet(); - singleton().beforeThreadRun(thread); try { + assignCurrent(thread, true); + assignThreadToThreadGroup(thread, true); + ObjectHandles.getGlobal().destroy(threadHandle); + + singleton().unattachedStartedThreads.decrementAndGet(); + singleton().beforeThreadRun(thread); + if (VMThreads.isTearingDown()) { /* * As a newly started thread, we might not have been interrupted like the Java @@ -834,7 +912,7 @@ protected static void wakeUpVMConditionWaiters(Thread thread) { static StackTraceElement[] getStackTrace(boolean filterExceptions, Thread thread, Pointer callerSP) { assert !isVirtual(thread); - if (thread == currentThread.get()) { + if (thread != null && thread == currentThread.get()) { return StackTraceUtils.getStackTrace(filterExceptions, callerSP, WordFactory.nullPointer()); } assert !filterExceptions : "exception stack traces can be taken only for the current thread"; @@ -842,7 +920,7 @@ static StackTraceElement[] getStackTrace(boolean filterExceptions, Thread thread } public static StackTraceElement[] getStackTraceAtSafepoint(Thread thread, Pointer callerSP) { - assert !isVirtual(thread); + assert thread != null && !isVirtual(thread); IsolateThread isolateThread = getIsolateThread(thread); if (isolateThread == CurrentIsolate.getCurrentThread()) { /* @@ -1011,11 +1089,14 @@ static Thread.State getThreadState(Thread thread) { return Target_jdk_internal_misc_VM.toThreadState(getThreadStatus(thread)); } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public static void setThreadStatus(Thread thread, int threadStatus) { assert !isVirtual(thread); if (JavaVersionUtil.JAVA_SPEC >= 19) { + assert toTarget(thread).holder.threadStatus != ThreadStatus.TERMINATED : "once a thread is marked as terminated, its status must not change"; toTarget(thread).holder.threadStatus = threadStatus; } else { + assert toTarget(thread).threadStatus != ThreadStatus.TERMINATED : "once a thread is marked as terminated, its status must not change"; toTarget(thread).threadStatus = threadStatus; } } @@ -1056,7 +1137,9 @@ private static class GetAllStackTracesOperation extends JavaVMOperation { protected void operate() { for (IsolateThread cur = VMThreads.firstThread(); cur.isNonNull(); cur = VMThreads.nextThread(cur)) { Thread thread = PlatformThreads.fromVMThread(cur); - result.put(thread, StackTraceUtils.getStackTraceAtSafepoint(thread)); + if (thread != null) { + result.put(thread, StackTraceUtils.getStackTraceAtSafepoint(thread)); + } } } } @@ -1190,7 +1273,17 @@ static void blockedOn(Target_sun_nio_ch_Interruptible b) { } } - public interface OSThreadHandle extends PointerBase { + protected static class ThreadStartRoutinePrologue 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()); + } + } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListener.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListener.java index 7db21aad40e8..ce3480130689 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListener.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListener.java @@ -37,9 +37,18 @@ default void beforeThreadStart(IsolateThread isolateThread, Thread javaThread) { default void beforeThreadRun() { } + /** + * Implementations must not throw any exceptions. Note that this method is called on listeners + * in the reverse order of {@link #beforeThreadRun}. + */ + @Uninterruptible(reason = "Only uninterruptible because we need to prevent stack overflow errors. Implementations may execute interruptible code.") default void afterThreadRun() { } + /** + * Implementations must not throw any exceptions. Note that this method is called on listeners + * in the reverse order of {@link #beforeThreadStart}. + */ @Uninterruptible(reason = "Only uninterruptible code may be executed after Thread.exit.") @SuppressWarnings("unused") default void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListenerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListenerSupport.java index 8104fbb28c66..2dcb36ada104 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListenerSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListenerSupport.java @@ -34,6 +34,7 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.util.VMError; @AutomaticallyRegisteredImageSingleton public class ThreadListenerSupport { @@ -60,27 +61,36 @@ public static ThreadListenerSupport get() { @Uninterruptible(reason = "Force that all listeners are uninterruptible.") public void beforeThreadStart(IsolateThread isolateThread, Thread javaThread) { - for (int i = 0; i < listeners.length; i++) { - listeners[i].beforeThreadStart(isolateThread, javaThread); + for (ThreadListener listener : listeners) { + listener.beforeThreadStart(isolateThread, javaThread); } } public void beforeThreadRun() { - for (int i = 0; i < listeners.length; i++) { - listeners[i].beforeThreadRun(); + for (ThreadListener listener : listeners) { + listener.beforeThreadRun(); } } + @Uninterruptible(reason = "Force that all listeners are uninterruptible.") public void afterThreadRun() { - for (int i = 0; i < listeners.length; i++) { - listeners[i].afterThreadRun(); + for (int i = listeners.length - 1; i >= 0; i--) { + try { + listeners[i].afterThreadRun(); + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } } } @Uninterruptible(reason = "Force that all listeners are uninterruptible.") public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { for (int i = listeners.length - 1; i >= 0; i--) { - listeners[i].afterThreadExit(isolateThread, javaThread); + try { + listeners[i].afterThreadExit(isolateThread, javaThread); + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java index a17aa4afaac4..4b04201c0d9f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java @@ -39,6 +39,7 @@ import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; +import com.oracle.svm.core.Isolates; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateOptions.ConcealedOptions; @@ -57,6 +58,7 @@ import com.oracle.svm.core.thread.VMThreads.SafepointBehavior; import com.oracle.svm.core.thread.VMThreads.StatusSupport; import com.oracle.svm.core.util.RingBuffer; +import com.oracle.svm.core.util.TimeUtils; import com.oracle.svm.core.util.VMError; /** @@ -902,7 +904,7 @@ private static class VMOpHistory { @Platforms(Platform.HOSTED_ONLY.class) VMOpHistory() { - history = new RingBuffer<>(15, VMOpStatusChange::new); + history = new RingBuffer<>(SubstrateOptions.DiagnosticBufferSize.getValue(), VMOpStatusChange::new); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -920,7 +922,7 @@ public void add(VMOpStatus status, VMOperation operation, IsolateThread queueing } public void print(Log log, boolean allowJavaHeapAccess) { - log.string("The ").signed(history.size()).string(" most recent VM operation status changes (oldest first):").indent(true); + log.string("The ").signed(history.size()).string(" most recent VM operation status changes:").indent(true); history.foreach(log, allowJavaHeapAccess ? PRINT_WITH_JAVA_HEAP_DATA : PRINT_WITHOUT_JAVA_HEAP_DATA); log.indent(false); } @@ -967,7 +969,8 @@ private static class VMOpStatusChange { void print(Log log, boolean allowJavaHeapAccess) { VMOpStatus localStatus = status; if (localStatus != null) { - log.unsigned(timestamp).string(" - ").spaces(nestingLevel * 2).string(localStatus.name()); + long uptime = timestamp - Isolates.getCurrentStartTimeMillis(); + log.rational(uptime, TimeUtils.millisPerSecond, 3).string("s - ").spaces(nestingLevel * 2).string(localStatus.name()); if (allowJavaHeapAccess) { log.string(" ").string(name); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java index 8f7abe154e14..769f56983764 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java @@ -304,6 +304,7 @@ public int attachThread(IsolateThread thread, boolean startedByCurrentIsolate) { return attachThread(thread); } + /* Needs to be protected due to legacy code. */ @Uninterruptible(reason = "Thread is not attached yet.") protected int attachThread(IsolateThread thread) { assert StatusSupport.isStatusCreated(thread) : "Status should be initialized on creation."; @@ -318,6 +319,11 @@ protected int attachThread(IsolateThread thread) { try { nextTL.set(thread, head); head = thread; + + if (!wasStartedByCurrentIsolate(thread)) { + /* Treat attached threads as non-daemon threads until we know better. */ + PlatformThreads.incrementNonDaemonThreads(); + } Heap.getHeap().attachThread(CurrentIsolate.getCurrentThread()); /* On the initial transition to java code this thread should be synchronized. */ ActionOnTransitionToJavaSupport.setSynchronizeCode(thread); @@ -338,9 +344,11 @@ public void detachThread(IsolateThread thread) { assert thread.equal(CurrentIsolate.getCurrentThread()) : "Cannot detach different thread with this method"; // read thread local data (can't be accessed further below as the IsolateThread is freed) + OSThreadHandle threadHandle = OSThreadHandleTL.get(thread); OSThreadHandle nextOsThreadToCleanup = WordFactory.nullPointer(); - if (wasStartedByCurrentIsolate(thread)) { - nextOsThreadToCleanup = OSThreadHandleTL.get(thread); + boolean wasStartedByCurrentIsolate = wasStartedByCurrentIsolate(thread); + if (wasStartedByCurrentIsolate) { + nextOsThreadToCleanup = threadHandle; } threadExit(thread); @@ -374,6 +382,10 @@ public void detachThread(IsolateThread thread) { THREAD_MUTEX.unlock(); } + if (!wasStartedByCurrentIsolate) { + /* If a thread was attached, we need to free its thread handle. */ + PlatformThreads.singleton().closeOSThreadHandle(threadHandle); + } cleanupExitedOsThread(threadToCleanup); } @@ -606,21 +618,26 @@ public static boolean ownsThreadMutex() { } public static boolean printLocationInfo(Log log, UnsignedWord value, boolean allowUnsafeOperations) { + if (!allowUnsafeOperations && !VMOperation.isInProgressAtSafepoint()) { + /* + * Iterating the threads or accessing thread locals of other threads is unsafe if we are + * outside a VM operation because the IsolateThread data structure could be freed at any + * time (we can't use any locking to prevent races). + */ + return false; + } + for (IsolateThread thread = firstThreadUnsafe(); thread.isNonNull(); thread = nextThread(thread)) { if (thread.equal(value)) { log.string("is a thread"); return true; } - if (allowUnsafeOperations || VMOperation.isInProgressAtSafepoint()) { - // If we are not at a safepoint, then it is unsafe to access thread locals of - // another thread as the IsolateThread could be freed at any time. - UnsignedWord stackBase = StackBase.get(thread); - UnsignedWord stackEnd = StackEnd.get(thread); - if (value.belowThan(stackBase) && value.aboveOrEqual(stackEnd)) { - log.string("points into the stack for thread ").zhex(thread); - return true; - } + UnsignedWord stackBase = StackBase.get(thread); + UnsignedWord stackEnd = StackEnd.get(thread); + if (value.belowThan(stackBase) && value.aboveOrEqual(stackEnd)) { + log.string("points into the stack for thread ").zhex(thread); + return true; } if (SubstrateOptions.MultiThreaded.getValue()) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/threadlocal/VMThreadLocalInfos.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/threadlocal/VMThreadLocalInfos.java index 9539435e422f..a5fbc2d9068e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/threadlocal/VMThreadLocalInfos.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/threadlocal/VMThreadLocalInfos.java @@ -37,6 +37,7 @@ import org.graalvm.word.WordBase; import org.graalvm.word.WordFactory; +import com.oracle.svm.core.SubstrateDiagnostics; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; @@ -61,33 +62,43 @@ public static boolean setInfos(Collection infos) { public static void dumpToLog(Log log, IsolateThread thread, boolean isJavaHeapAccessAllowed) { for (VMThreadLocalInfo info : ImageSingletons.lookup(VMThreadLocalInfos.class).infos) { - log.signed(info.offset).string(" (").signed(info.sizeInBytes).string(" bytes): ").string(info.name).string(" = "); + log.signed(info.offset).string(": ").string(info.name).string(" = "); if (info.threadLocalClass == FastThreadLocalInt.class) { int value = primitiveData(thread).readInt(WordFactory.signed(info.offset)); - log.string("(int) ").signed(value).string(" (").zhex(value).string(")"); + log.string("(int) ").zhex(value).string(" (").signed(value).string(")"); } else if (info.threadLocalClass == FastThreadLocalLong.class) { long value = primitiveData(thread).readLong(WordFactory.signed(info.offset)); - log.string("(long) ").signed(value).string(" (").zhex(value).string(")"); + log.string("(long) ").zhex(value).string(" (").signed(value).string(")"); } else if (info.threadLocalClass == FastThreadLocalWord.class) { WordBase value = primitiveData(thread).readWord(WordFactory.signed(info.offset)); - log.string("(Word) ").signed(value).string(" (").zhex(value).string(")"); + log.string("(Word) ").zhex(value).string(" (").signed(value).string(")"); } else if (info.threadLocalClass == FastThreadLocalObject.class) { if (isJavaHeapAccessAllowed) { Object value = ObjectAccess.readObject(objectData(thread), WordFactory.signed(info.offset)); - log.string("(Object) "); - if (value == null) { - log.string("null"); - } else { - log.string(value.getClass().getName()).string(" (").zhex(Word.objectToUntrackedPointer(value)).string(")"); + log.string("(Object) ").zhex(Word.objectToUntrackedPointer(value)); + if (value != null) { + log.indent(true); + SubstrateDiagnostics.printObjectInfo(log, value); + log.redent(false); } } else { Word value = ReferenceAccess.singleton().readObjectAsUntrackedPointer(Word.objectToUntrackedPointer(objectData(thread)).add(info.offset), true); log.string("(Object) ").zhex(value); } } else if (info.threadLocalClass == FastThreadLocalBytes.class) { - log.string("(bytes) ").indent(true); - log.hexdump(primitiveData(thread).add(WordFactory.signed(info.offset)), 8, info.sizeInBytes / 8); - log.redent(false); + log.string("(bytes) "); + Pointer data = primitiveData(thread).add(WordFactory.signed(info.offset)); + if (info.sizeInBytes == 8) { + log.zhex(data.readWord(0)); + } else { + log.indent(true); + if (info.sizeInBytes % 8 == 0) { + log.hexdump(data, 8, info.sizeInBytes / 8); + } else { + log.hexdump(data, 1, info.sizeInBytes); + } + log.redent(false); + } } else { log.string("unknown class ").string(info.threadLocalClass.getName()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/Counter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/Counter.java index ed8925cc0c35..5e262bb516c0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/Counter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/Counter.java @@ -48,8 +48,8 @@ * option is enabled. Counters are {@link Group grouped} for printing. * * Currently there is no shutdown hook in Substrate VM that is invoked automatically, so - * {@link Counter#logValues} needs to be called manually at the end of the application to print - * counter values. + * {@link CounterSupport#logValues} needs to be called manually at the end of the application to + * print counter values. * * Use this class in the following way: * @@ -206,15 +206,6 @@ public void add(long increment) { public void reset() { value = 0; } - - /** - * Prints all counters of all enabled groups to the {@link Log}. - */ - public static void logValues(Log log) { - for (Counter.Group group : ImageSingletons.lookup(CounterSupport.class).enabledGroups) { - group.logValues(log); - } - } } @TargetClass(com.oracle.svm.core.util.Counter.class) @@ -243,13 +234,3 @@ public void addGroup(Group group) { value.add(group); } } - -class CounterSupport { - - final Counter.Group[] enabledGroups; - - CounterSupport(Counter.Group[] enabledGroups) { - this.enabledGroups = enabledGroups; - } - -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/CounterSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/CounterSupport.java new file mode 100644 index 000000000000..ebab33d660a2 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/CounterSupport.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023, 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 com.oracle.svm.core.util; + +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.log.Log; + +public class CounterSupport { + private final Counter.Group[] enabledGroups; + + @Platforms(Platform.HOSTED_ONLY.class) + CounterSupport(Counter.Group[] enabledGroups) { + this.enabledGroups = enabledGroups; + } + + @Fold + public static CounterSupport singleton() { + return ImageSingletons.lookup(CounterSupport.class); + } + + /** + * Prints all counters of all enabled groups to the {@link Log}. + */ + public void logValues(Log log) { + for (Counter.Group group : enabledGroups) { + group.logValues(log); + } + } + + public boolean hasCounters() { + return enabledGroups != null && enabledGroups.length > 0; + } + +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/RingBuffer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/RingBuffer.java index dd4f6f2ba971..827bfd17d613 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/RingBuffer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/RingBuffer.java @@ -32,8 +32,6 @@ * Keeps the last-n entries and allows to read the out on demand.. */ public final class RingBuffer { - private static final int DEFAULT_BUFFER_SIZE = 30; - private final T[] entries; private int pos; private boolean wrapped; @@ -42,10 +40,6 @@ public interface Consumer { void accept(Object context, T t); } - public RingBuffer() { - this(DEFAULT_BUFFER_SIZE); - } - @SuppressWarnings("unchecked") public RingBuffer(int numEntries) { this.entries = (T[]) new Object[numEntries]; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/VMError.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/VMError.java index 10335e80140b..bbee4ee3dae2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/VMError.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/VMError.java @@ -64,9 +64,6 @@ public static final class HostedError extends Error { } - public static final String msgShouldNotReachHere = "should not reach here"; - public static final String msgShouldNotReachHereAtRuntime = msgShouldNotReachHere + ": this code is expected to be unreachable at runtime"; - public static RuntimeException shouldNotReachHere() { throw new HostedError("should not reach here"); } @@ -83,13 +80,6 @@ public static RuntimeException shouldNotReachHere(String msg, Throwable cause) { throw new HostedError(msg, cause); } - /** - * A hardcoded list of options (if, switch) did not handle the case actually provided. - */ - public static RuntimeException shouldNotReachHereAtRuntime() { - throw new HostedError(msgShouldNotReachHereAtRuntime); - } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void guarantee(boolean condition) { if (!condition) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/json/JsonWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/json/JsonWriter.java index 7f2783f3d554..954eb0ee827e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/json/JsonWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/json/JsonWriter.java @@ -83,7 +83,7 @@ public JsonWriter appendFieldSeparator() throws IOException { } public JsonWriter appendKeyValue(String key, Object value) throws IOException { - return quote(key).appendFieldSeparator().quote(value); + return quote(key).appendFieldSeparator().printValue(value); } @SuppressWarnings("unchecked") @@ -103,7 +103,7 @@ public void print(Map map) throws IOException { } else if (value instanceof List) { print((List) value); // Must always be } else { - quote(value); + printValue(value); } if (keySetIter.hasNext()) { append(','); @@ -132,15 +132,26 @@ public void print(List list, Function mapper) throws IOExcepti append(']'); } - public JsonWriter quote(Object o) throws IOException { + public JsonWriter printValue(Object o) throws IOException { if (o == null) { return append("null"); - } else if (Boolean.TRUE.equals(o)) { - return append("true"); - } else if (Boolean.FALSE.equals(o)) { - return append("false"); - } else if (o instanceof Number) { + } else if (o instanceof Boolean || o instanceof Byte || o instanceof Short || o instanceof Integer || o instanceof Long) { + /* + * Note that sub-integer values here most likely become Integer objects when parsing, + * and comparisons such as equals() or compareTo() on boxed values only work on the + * exact same type. (Boolean values, however, should be deserialized as Boolean). + */ return append(o.toString()); + } else if (o instanceof Float f) { + if (f.isNaN() || f.isInfinite()) { + return quote(f.toString()); // cannot express, best we can do without failing + } + return append(f.toString()); + } else if (o instanceof Double d) { + if (d.isNaN() || d.isInfinite()) { + return quote(d.toString()); // cannot express, best we can do without failing + } + return append(d.toString()); } else { return quote(o.toString()); } diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/BundleSupport.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/BundleSupport.java index ad1011edb3de..0a99dc713451 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/BundleSupport.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/BundleSupport.java @@ -670,8 +670,8 @@ private static Manifest createManifest() { private static final String substitutionMapDstField = "dst"; private static void printPathMapping(Map.Entry entry, JsonWriter w) throws IOException { - w.append('{').quote(substitutionMapSrcField).append(':').quote(entry.getKey()); - w.append(',').quote(substitutionMapDstField).append(':').quote(entry.getValue()); + w.append('{').quote(substitutionMapSrcField).append(':').printValue(entry.getKey()); + w.append(',').quote(substitutionMapDstField).append(':').printValue(entry.getValue()); w.append('}'); } diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java index a1fe78af6f91..945f3d20ad7d 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java @@ -1093,13 +1093,20 @@ private int completeImageBuild() { if (!imageNamePathParent.isAbsolute()) { imageNamePathParent = imagePath.resolve(imageNamePathParent); } - if (!Files.isDirectory(imageNamePathParent)) { - throw NativeImage.showError("Writing image to non-existent directory " + imageNamePathParent + " is not allowed. " + - "Create the missing directory if you want the image to be written to that location."); - } - if (!Files.isWritable(imageNamePathParent)) { - throw NativeImage.showError("Writing image to directory without write access " + imageNamePathParent + " is not possible. " + - "Ensure the directory has write access or specify image path with write access."); + if (!useBundle()) { + /* + * In bundle-mode the value of imagePath is purely virtual before it gets + * substituted by substituteImagePath(imagePath) below. Validating the virtual value + * would make no sense (and cause errors if the path does not exist anymore) + */ + if (!Files.isDirectory(imageNamePathParent)) { + throw NativeImage.showError("Writing image to non-existent directory " + imageNamePathParent + " is not allowed. " + + "Create the missing directory if you want the image to be written to that location."); + } + if (!Files.isWritable(imageNamePathParent)) { + throw NativeImage.showError("Writing image to directory without write access " + imageNamePathParent + " is not possible. " + + "Ensure the directory has write access or specify image path with write access."); + } } imagePath = imageNamePathParent; /* Update arguments passed to builder */ diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java index ab8704e5b8eb..22b024936b65 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java @@ -248,9 +248,15 @@ public void collectMonitorFieldInfo(BigBang bb, HostedUniverse hUniverse, Set getForceMonitorSlotTypes(BigBang bb) { Set forceMonitorTypes = new HashSet<>(); - for (Class forceMonitorType : MultiThreadedMonitorSupport.FORCE_MONITOR_SLOT_TYPES) { - Optional aType = bb.getMetaAccess().optionalLookupJavaType(forceMonitorType); - aType.ifPresent(forceMonitorTypes::add); + for (var entry : MultiThreadedMonitorSupport.FORCE_MONITOR_SLOT_TYPES.entrySet()) { + Optional optionalType = bb.getMetaAccess().optionalLookupJavaType(entry.getKey()); + if (optionalType.isPresent()) { + AnalysisType aType = optionalType.get(); + forceMonitorTypes.add(aType); + if (entry.getValue()) { + forceMonitorTypes.addAll(aType.getAllSubtypes()); + } + } } return forceMonitorTypes; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/codegen/CCompilerInvoker.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/codegen/CCompilerInvoker.java index d53f78e4e220..8e8c9b6c8327 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/codegen/CCompilerInvoker.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/codegen/CCompilerInvoker.java @@ -32,7 +32,6 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; @@ -52,6 +51,7 @@ import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.InterruptImageBuilding; import com.oracle.svm.core.util.UserError; +import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.c.libc.HostedLibCBase; import com.oracle.svm.hosted.c.util.FileUtils; import com.oracle.svm.util.ClassUtil; @@ -138,47 +138,52 @@ protected InputStream getCompilerErrorStream(Process compilingProcess) { @Override protected List getVersionInfoOptions() { - return Collections.emptyList(); + Path detectVersionInfoFile = tempDirectory.resolve("detect-cl-version-info.c").toAbsolutePath(); + try { + Files.write(detectVersionInfoFile, List.of("M_X64=_M_X64", "M_ARM64EC=_M_ARM64EC", "MSC_FULL_VER=_MSC_FULL_VER")); + } catch (IOException ioe) { + throw VMError.shouldNotReachHere("Unable to create file to detect cl version info", ioe); + } + return List.of("/EP", detectVersionInfoFile.toString()); } @Override - protected CompilerInfo createCompilerInfo(Path compilerPath, Scanner outerScanner) { - try (Scanner scanner = new Scanner(outerScanner.nextLine())) { - String targetArch = null; - /* For cl.exe the first line holds all necessary information */ - if (scanner.hasNext("\u7528\u4E8E")) { - /* Simplified-Chinese has targetArch first */ - scanner.next(); - targetArch = scanner.next(); + protected CompilerInfo createCompilerInfo(Path compilerPath, Scanner scanner) { + try { + if (scanner.findInLine("Microsoft.*\\(R\\)") == null) { + return null; // not a Microsoft compiler } - /* - * Some cl.exe print "... Microsoft (R) C/C++ ... ##.##.#####" while others print - * "...C/C++ ... Microsoft (R) ... ##.##.#####". - */ - if (scanner.findInLine("Microsoft.*\\(R\\) C/C\\+\\+") == null && - scanner.findInLine("C/C\\+\\+.*Microsoft.*\\(R\\)") == null) { + scanner.nextLine(); // skip rest of first line + scanner.nextLine(); // skip copyright line + scanner.nextLine(); // skip blank separator line + skipLineIfHasNext(scanner, "detect-cl-version-info.c"); + scanner.nextLine(); // skip blank separator line + skipLineIfHasNext(scanner, "M_X64=100"); // _M_X64 is defined + skipLineIfHasNext(scanner, "M_ARM64EC=_M_ARM64EC"); // _M_ARM64EC is not defined + if (scanner.findInLine("MSC_FULL_VER=") == null) { return null; } - scanner.useDelimiter("\\D"); - while (!scanner.hasNextInt()) { - scanner.next(); - } - int major = scanner.nextInt(); - int minor0 = scanner.nextInt(); - int minor1 = scanner.nextInt(); - if (targetArch == null) { - scanner.reset(); - while (scanner.hasNext()) { - /* targetArch is last token in line */ - targetArch = scanner.next(); - } + String mscFullVerValue = scanner.nextLine(); + if (mscFullVerValue.length() < 5) { + return null; } - return new CompilerInfo(compilerPath, "microsoft", "C/C++ Optimizing Compiler", "cl", major, minor0, minor1, targetArch); - } catch (NoSuchElementException e) { + int major = Integer.parseInt(mscFullVerValue.substring(0, 2)); + int minor0 = Integer.parseInt(mscFullVerValue.substring(2, 4)); + int minor1 = Integer.parseInt(mscFullVerValue.substring(4)); + return new CompilerInfo(compilerPath, "microsoft", "C/C++ Optimizing Compiler", "cl", major, minor0, minor1, "x64"); + } catch (NoSuchElementException | NumberFormatException e) { return null; } } + private static void skipLineIfHasNext(Scanner scanner, String expectedToken) { + if (scanner.hasNext(expectedToken)) { + scanner.nextLine(); + } else { + throw new NoSuchElementException(expectedToken); + } + } + @Override protected void verify() { // See details on _MSC_VER at @@ -189,11 +194,6 @@ protected void verify() { UserError.abort("On Windows, GraalVM Native Image for JDK %s requires %s or later (C/C++ Optimizing Compiler Version %s.%s or later).%nCompiler info detected: %s", JavaVersionUtil.JAVA_SPEC, VISUAL_STUDIO_MINIMUM_REQUIRED_VERSION, minimumMajorVersion, minimumMinorVersion, compilerInfo.getShortDescription()); } - if (guessArchitecture(compilerInfo.targetArch) != AMD64.class) { - String targetPrefix = compilerInfo.targetArch.matches("(.*x|i\\d)86$") ? "32-bit architecture " : ""; - UserError.abort("Native-image building on Windows currently only supports target architecture: %s (%s%s unsupported)", - AMD64.class.getSimpleName(), targetPrefix, compilerInfo.targetArch); - } } @Override @@ -415,7 +415,7 @@ private CompilerInfo getCCompilerInfo() { } protected List getVersionInfoOptions() { - return Arrays.asList("-v"); + return List.of("-v"); } protected abstract CompilerInfo createCompilerInfo(Path compilerPath, Scanner scanner); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/ImageHeapConnectedComponentsPrinter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/ImageHeapConnectedComponentsPrinter.java index d8b53b5f6e64..04d41fd051c6 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/ImageHeapConnectedComponentsPrinter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/ImageHeapConnectedComponentsPrinter.java @@ -214,7 +214,7 @@ public void printSummaryInfoForEveryObjectInConnectedComponents(PrintWriter out) for (Iterator iterator = connectedComponents.iterator(); iterator.hasNext();) { ConnectedComponent connectedComponent = iterator.next(); writer.append('{').newline(); - writer.quote("componentId").append(':').quote(connectedComponent.getId()).append(',').newline(); + writer.quote("componentId").append(':').printValue(connectedComponent.getId()).append(',').newline(); writer.quote("sizeInBytes").append(':').append(String.valueOf(connectedComponent.getSizeInBytes())).append(',').newline(); writer.quote("objects").append(":["); List objects = connectedComponent.getObjects(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/LIRNativeImageCodeCache.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/LIRNativeImageCodeCache.java index a8a9f3519364..42ae894f2f12 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/LIRNativeImageCodeCache.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/LIRNativeImageCodeCache.java @@ -218,7 +218,7 @@ public void layoutMethods(DebugContext debug, BigBang bb, ForkJoinPool threadPoo assert verifyMethodLayout(); - buildRuntimeMetadata(new MethodPointer(getFirstCompilation().getLeft()), WordFactory.unsigned(totalSize)); + buildRuntimeMetadata(threadPool, new MethodPointer(getFirstCompilation().getLeft()), WordFactory.unsigned(totalSize)); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java index f11b8a8d5ddb..262403324d9a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java @@ -63,6 +63,7 @@ import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.util.CompletionExecutor; import com.oracle.objectfile.ObjectFile; import com.oracle.svm.common.meta.MultiMethod; import com.oracle.svm.core.SubstrateOptions; @@ -247,7 +248,7 @@ public int getAlignedConstantsSize() { return ConfigurationValues.getObjectLayout().alignUp(getConstantsSize()); } - public void buildRuntimeMetadata(CFunctionPointer firstMethod, UnsignedWord codeSize) { + public void buildRuntimeMetadata(ForkJoinPool threadPool, CFunctionPointer firstMethod, UnsignedWord codeSize) { // Build run-time metadata. HostedFrameInfoCustomization frameInfoCustomization = new HostedFrameInfoCustomization(); CodeInfoEncoder.Encoders encoders = new CodeInfoEncoder.Encoders(); @@ -403,7 +404,7 @@ public void buildRuntimeMetadata(CFunctionPointer firstMethod, UnsignedWord code verifyDeoptEntries(imageCodeInfo); } - assert verifyMethods(codeInfoEncoder, imageCodeInfo); + assert verifyMethods(hUniverse, threadPool, codeInfoEncoder, imageCodeInfo); } protected HostedImageCodeInfo installCodeInfo(CFunctionPointer firstMethod, UnsignedWord codeSize, CodeInfoEncoder codeInfoEncoder, ReflectionMetadataEncoder reflectionMetadataEncoder) { @@ -545,10 +546,22 @@ private static boolean error(HostedMethod method, long encodedBci, String msg) { return true; } - protected boolean verifyMethods(CodeInfoEncoder codeInfoEncoder, CodeInfo codeInfo) { - for (Pair pair : getOrderedCompilations()) { - HostedMethod method = pair.getLeft(); - CodeInfoEncoder.verifyMethod(method, pair.getRight(), method.getCodeAddressOffset(), codeSizeFor(method), codeInfo); + protected boolean verifyMethods(HostedUniverse hUniverse, ForkJoinPool threadPool, CodeInfoEncoder codeInfoEncoder, CodeInfo codeInfo) { + /* + * Run method verification in parallel to reduce computation time. + */ + BigBang bb = hUniverse.getBigBang(); + CompletionExecutor executor = new CompletionExecutor(bb, threadPool, bb.getHeartbeatCallback()); + try { + executor.init(); + executor.start(); + for (Pair pair : getOrderedCompilations()) { + HostedMethod method = pair.getLeft(); + executor.execute(ignore -> CodeInfoEncoder.verifyMethod(method, pair.getRight(), method.getCodeAddressOffset(), codeSizeFor(method), codeInfo)); + } + executor.complete(); + } catch (InterruptedException e) { + throw VMError.shouldNotReachHere("Failed to verify methods"); } codeInfoEncoder.verifyFrameInfo(codeInfo); return true; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedInstanceClass.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedInstanceClass.java index 7ecc8c2664fa..b7507550ecd1 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedInstanceClass.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedInstanceClass.java @@ -37,7 +37,7 @@ public class HostedInstanceClass extends HostedClass { protected int instanceSize; protected boolean monitorFieldNeeded = false; protected int monitorFieldOffset = 0; - protected int optionalIdentityHashOffset = -1; + protected int optionalIdentityHashOffset = 0; public HostedInstanceClass(HostedUniverse universe, AnalysisType wrapped, JavaKind kind, JavaKind storageKind, HostedClass superClass, HostedInterface[] interfaces) { super(universe, wrapped, kind, storageKind, superClass, interfaces); @@ -120,8 +120,8 @@ public int getOptionalIdentityHashOffset() { } public void setOptionalIdentityHashOffset(int offset) { - assert this.optionalIdentityHashOffset == -1 : "setting identity hashcode field offset more than once"; - assert offset >= 0; + assert this.optionalIdentityHashOffset == 0 : "setting identity hashcode field offset more than once"; + assert offset > 0; this.optionalIdentityHashOffset = offset; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java index 9069ca65dd67..7320cb831c74 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java @@ -1021,7 +1021,7 @@ private void buildHubs() { int layoutHelper; boolean canInstantiateAsInstance = false; int monitorOffset = 0; - int optionalIdHashOffset = -1; + int optionalIdHashOffset = 0; if (type.isInstanceClass()) { HostedInstanceClass instanceClass = (HostedInstanceClass) type; if (instanceClass.isAbstract()) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java index ce27e6b2375a..c7ca53b2704f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java @@ -261,6 +261,15 @@ private void registerClassPlugins(InvocationPlugins plugins) { "getField", "getMethod", "getConstructor", "getDeclaredField", "getDeclaredMethod", "getDeclaredConstructor"); + /* + * The class sun.nio.ch.Reflect contains various reflection lookup methods that then pass + * parameters through to the actual methods in java.lang.Class. But they do additional + * things like calling setAccessible(true), so method inlining before analysis cannot + * constant-fold them automatically. So we register them manually here for folding too. + */ + registerFoldInvocationPlugins(plugins, ReflectionUtil.lookupClass(false, "sun.nio.ch.Reflect"), + "lookupConstructor", "lookupMethod", "lookupField"); + if (MissingReflectionRegistrationUtils.throwMissingRegistrationErrors() && reason.duringAnalysis() && reason != ParsingReason.JITCompilation) { registerBulkInvocationPlugin(plugins, Class.class, "getClasses", RuntimeReflection::registerAllClasses); registerBulkInvocationPlugin(plugins, Class.class, "getDeclaredClasses", RuntimeReflection::registerAllDeclaredClasses); diff --git a/substratevm/src/com.oracle.svm.native.libchelper/include/amd64hotspotcpuinfo.h b/substratevm/src/com.oracle.svm.native.libchelper/include/amd64hotspotcpuinfo.h index 6ba282049f49..ac7d2cc7c505 100644 --- a/substratevm/src/com.oracle.svm.native.libchelper/include/amd64hotspotcpuinfo.h +++ b/substratevm/src/com.oracle.svm.native.libchelper/include/amd64hotspotcpuinfo.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -141,12 +141,11 @@ typedef union { uint32_t LahfSahf : 1, CmpLegacy : 1, : 3, - lzcnt_intel : 1, lzcnt : 1, sse4a : 1, misalignsse : 1, prefetchw : 1, - : 22; + : 23; } bits; } ExtCpuid1Ecx; diff --git a/substratevm/src/com.oracle.svm.native.libchelper/src/cpuid.c b/substratevm/src/com.oracle.svm.native.libchelper/src/cpuid.c index b967d65889b8..1ae435454f44 100644 --- a/substratevm/src/com.oracle.svm.native.libchelper/src/cpuid.c +++ b/substratevm/src/com.oracle.svm.native.libchelper/src/cpuid.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -516,19 +516,16 @@ NO_INLINE static void set_cpufeatures(CPUFeatures *features, CpuidInfo *_cpuid_i // Intel features. if (is_intel(_cpuid_info)) { - if (_cpuid_info->ext_cpuid1_ecx.bits.lzcnt_intel != 0) + if (_cpuid_info->ext_cpuid1_ecx.bits.lzcnt != 0) { features->fLZCNT = 1; - // for Intel, ecx.bits.misalignsse bit (bit 8) indicates support for prefetchw - if (_cpuid_info->ext_cpuid1_ecx.bits.misalignsse != 0) - { + } + if (_cpuid_info->ext_cpuid1_ecx.bits.prefetchw != 0) { features->fAMD_3DNOW_PREFETCH = 1; } - if (_cpuid_info->sef_cpuid7_ebx.bits.clwb != 0) - { + if (_cpuid_info->sef_cpuid7_ebx.bits.clwb != 0) { features->fCLWB = 1; } - if (_cpuid_info->sef_cpuid7_edx.bits.serialize != 0) - { + if (_cpuid_info->sef_cpuid7_edx.bits.serialize != 0) { features->fSERIALIZE = 1; } } @@ -536,11 +533,10 @@ NO_INLINE static void set_cpufeatures(CPUFeatures *features, CpuidInfo *_cpuid_i // ZX features. if (is_zx(_cpuid_info)) { - if (_cpuid_info->ext_cpuid1_ecx.bits.lzcnt_intel != 0) + if (_cpuid_info->ext_cpuid1_ecx.bits.lzcnt != 0) { features->fLZCNT = 1; - // for ZX, ecx.bits.misalignsse bit (bit 8) indicates support for prefetchw - if (_cpuid_info->ext_cpuid1_ecx.bits.misalignsse != 0) - { + } + if (_cpuid_info->ext_cpuid1_ecx.bits.prefetchw != 0) { features->fAMD_3DNOW_PREFETCH = 1; } } diff --git a/substratevm/src/com.oracle.svm.native.libchelper/src/varargs_helper.c b/substratevm/src/com.oracle.svm.native.libchelper/src/varargs_helper.c index fc4048fb76f2..68db4f8a7692 100644 --- a/substratevm/src/com.oracle.svm.native.libchelper/src/varargs_helper.c +++ b/substratevm/src/com.oracle.svm.native.libchelper/src/varargs_helper.c @@ -48,4 +48,9 @@ int openSII(const char *pathname, int flags, int mode) return open(pathname, flags, mode); } +int openatISII(int dirfd, const char *pathname, int flags, int mode) +{ + return openat(dirfd, pathname, flags, mode); +} + #endif diff --git a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleFeature.java b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleFeature.java index 0cae79e8d896..bffe68346e57 100644 --- a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleFeature.java +++ b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleFeature.java @@ -224,7 +224,6 @@ public boolean getAsBoolean() { private final Set blocklistMethods; private final Set tempTargetAllowlistMethods; - private final Set implementationOnlyBlocklist; private final Set warnMethods; private final Set> neverPartOfCompilationViolations; Set runtimeCompiledMethods; @@ -232,7 +231,6 @@ public boolean getAsBoolean() { public TruffleFeature() { blocklistMethods = new HashSet<>(); tempTargetAllowlistMethods = new HashSet<>(); - implementationOnlyBlocklist = new HashSet<>(); warnMethods = new HashSet<>(); neverPartOfCompilationViolations = ConcurrentHashMap.newKeySet(); } @@ -550,16 +548,6 @@ boolean isBlocklisted(ResolvedJavaMethod method) { return blocklistMethods.contains(method); } - boolean isTargetBlocklisted(ResolvedJavaMethod target, ResolvedJavaMethod implementation) { - boolean blocklisted = !((AnalysisMethod) target).allowRuntimeCompilation() || blocklistMethods.contains(target); - - if (blocklisted && !implementation.equals(target) && implementationOnlyBlocklist.contains(target)) { - blocklisted = isBlocklisted(implementation); - } - - return blocklisted; - } - @SuppressWarnings("deprecation") private boolean deoptimizeOnException(ResolvedJavaMethod method) { if (method == null) { @@ -579,9 +567,7 @@ private void initializeMethodBlocklist(MetaAccessProvider metaAccess, FeatureAcc blocklistMethod(metaAccess, String.class, "indexOf", int.class, int.class); blocklistMethod(metaAccess, String.class, "indexOf", String.class); blocklistMethod(metaAccess, String.class, "indexOf", String.class, int.class); - blocklistMethod(metaAccess, Throwable.class, "fillInStackTrace"); - // Implementations which don't call Throwable.fillInStackTrace are allowed - implementationOnlyBlocklist(metaAccess, Throwable.class, "fillInStackTrace"); + blocklistMethod(metaAccess, Throwable.class, "fillInStackTrace", int.class); blocklistMethod(metaAccess, Throwable.class, "initCause", Throwable.class); blocklistMethod(metaAccess, Throwable.class, "addSuppressed", Throwable.class); blocklistMethod(metaAccess, System.class, "getProperty", String.class); @@ -744,24 +730,6 @@ private void removeFromBlocklist(MetaAccessProvider metaAccess, Class clazz, } } - /** - * Methods on this list are allowed to be runtime compiled as long as the method being runtime - * compiled (i.e., the implementation method & not the target) is not on blocklist. - */ - private void implementationOnlyBlocklist(MetaAccessProvider metaAccess, Class clazz, String name, Class... parameterTypes) { - try { - Executable method; - if ("".equals(name)) { - method = clazz.getDeclaredConstructor(parameterTypes); - } else { - method = clazz.getDeclaredMethod(name, parameterTypes); - } - implementationOnlyBlocklist.add(metaAccess.lookupJavaMethod(method)); - } catch (NoSuchMethodException ex) { - throw VMError.shouldNotReachHere(ex); - } - } - private void warnAllMethods(MetaAccessProvider metaAccess, Class clazz) { for (Executable m : clazz.getDeclaredMethods()) { /* @@ -792,7 +760,7 @@ public void beforeCompilation(BeforeCompilationAccess config) { // Determine blocklist violations if (!runtimeCompilationForbidden(candidate.getImplementationMethod())) { - if (isBlocklisted(candidate.getImplementationMethod()) || isTargetBlocklisted(candidate.getTargetMethod(), candidate.getImplementationMethod())) { + if (isBlocklisted(candidate.getImplementationMethod())) { boolean tempAllow = !candidate.getTargetMethod().equals(candidate.getImplementationMethod()) && tempTargetAllowlistMethods.contains(candidate.getTargetMethod()) && !isBlocklisted(candidate.getImplementationMethod()); diff --git a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/isolated/IsolateAwareTruffleCompiler.java b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/isolated/IsolateAwareTruffleCompiler.java index 1ab4523c7c09..2ce848f2224e 100644 --- a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/isolated/IsolateAwareTruffleCompiler.java +++ b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/isolated/IsolateAwareTruffleCompiler.java @@ -151,10 +151,18 @@ protected CompilerIsolateThread beforeCompilation() { Isolate isolate = getSharedIsolate(); if (isolate.isNull()) { if (sharedIsolate.compareAndSet(WordFactory.nullPointer(), (Isolate) ISOLATE_INITIALIZING)) { - CompilerIsolateThread thread = IsolatedGraalUtils.createCompilationIsolate(); - Runtime.getRuntime().addShutdownHook(new Thread(this::sharedIsolateShutdown)); - sharedIsolate.set(Isolates.getIsolate(thread)); - return thread; // (already attached) + try { + /* Adding the shutdown hook may fail if a shutdown is already in progress. */ + Runtime.getRuntime().addShutdownHook(new Thread(this::sharedIsolateShutdown)); + CompilerIsolateThread thread = IsolatedGraalUtils.createCompilationIsolate(); + sharedIsolate.set(Isolates.getIsolate(thread)); + return thread; // (already attached) + } catch (Throwable e) { + /* Reset the value so that the teardown hook doesn't hang. */ + assert sharedIsolate.get().equal(ISOLATE_INITIALIZING); + sharedIsolate.set(WordFactory.nullPointer()); + throw e; + } } isolate = getSharedIsolate(); assert isolate.isNonNull(); @@ -173,9 +181,11 @@ private Isolate getSharedIsolate() { private void sharedIsolateShutdown() { Isolate isolate = getSharedIsolate(); - CompilerIsolateThread context = (CompilerIsolateThread) Isolates.attachCurrentThread(isolate); - compilerIsolateThreadShutdown(context); - Isolates.detachThread(context); + if (isolate.isNonNull()) { + CompilerIsolateThread context = (CompilerIsolateThread) Isolates.attachCurrentThread(isolate); + compilerIsolateThreadShutdown(context); + Isolates.detachThread(context); + } } @CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished) diff --git a/tools/mx.tools/suite.py b/tools/mx.tools/suite.py index 3bbf76bd7b23..dc345800f709 100644 --- a/tools/mx.tools/suite.py +++ b/tools/mx.tools/suite.py @@ -26,7 +26,7 @@ "defaultLicense" : "GPLv2-CPE", "groupId" : "org.graalvm.tools", - "version" : "23.0.3.1", + "version" : "23.0.4.0", "release" : False, "url" : "http://openjdk.java.net/projects/graal", "developer" : { diff --git a/truffle/external_repos/simplelanguage/pom.xml b/truffle/external_repos/simplelanguage/pom.xml index 00435368d5de..6166ead68ea8 100644 --- a/truffle/external_repos/simplelanguage/pom.xml +++ b/truffle/external_repos/simplelanguage/pom.xml @@ -48,7 +48,7 @@ UTF-8 jdt_apt - 23.0.3-dev + 23.0.4-dev 11 11 diff --git a/truffle/external_repos/simplelanguage/sl b/truffle/external_repos/simplelanguage/sl index c9f8cb3fab34..2bdbbe4c12c8 100755 --- a/truffle/external_repos/simplelanguage/sl +++ b/truffle/external_repos/simplelanguage/sl @@ -41,7 +41,7 @@ # # If you update this number make sure the graalvm.version value in ./pom.xml matches -VERSION="23.0.3-dev" +VERSION="23.0.4-dev" MAIN_CLASS="com.oracle.truffle.sl.launcher.SLMain" SCRIPT_HOME="$(cd "$(dirname "$0")" && pwd -P)" diff --git a/truffle/external_repos/simpletool/pom.xml b/truffle/external_repos/simpletool/pom.xml index c62e71a20acc..26ac37d09317 100644 --- a/truffle/external_repos/simpletool/pom.xml +++ b/truffle/external_repos/simpletool/pom.xml @@ -49,7 +49,7 @@ UTF-8 1.8 1.8 - 23.0.3-dev + 23.0.4-dev diff --git a/truffle/mx.truffle/suite.py b/truffle/mx.truffle/suite.py index 74987261eba5..d10f8f704511 100644 --- a/truffle/mx.truffle/suite.py +++ b/truffle/mx.truffle/suite.py @@ -41,7 +41,7 @@ suite = { "mxversion": "6.17.0", "name" : "truffle", - "version" : "23.0.3.1", + "version" : "23.0.4.0", "release" : False, "groupId" : "org.graalvm.truffle", "sourceinprojectwhitelist" : [], diff --git a/vm/ci/ci_includes/vm.jsonnet b/vm/ci/ci_includes/vm.jsonnet index 230d2e4aaa7e..9f1c119b7bdd 100644 --- a/vm/ci/ci_includes/vm.jsonnet +++ b/vm/ci/ci_includes/vm.jsonnet @@ -34,7 +34,7 @@ local graal_common = import '../../../ci/ci_common/common.jsonnet'; short_name:: 'ce', setup+: [ ['set-export', 'VM_ENV', 'ce'], - ['set-export', 'RELEASE_CATALOG', 'https://www.graalvm.org/component-catalog/v2/graal-updater-component-catalog-java${BASE_JDK_SHORT_VERSION}.properties|{ee=GraalVM Enterprise Edition}rest://gds.oracle.com/api/20220101/'], + ['set-export', 'RELEASE_CATALOG', '{ee=GraalVM Enterprise Edition}rest://gds.oracle.com/api/20220101/'], ['set-export', 'RELEASE_PRODUCT_ID', 'D53FAE8052773FFAE0530F15000AA6C6'], ['set-export', 'SNAPSHOT_CATALOG', ['mx', 'urlrewrite', 'http://www.graalvm.org/catalog/ce/java${BASE_JDK_SHORT_VERSION}']], ['cd', 'vm'], diff --git a/vm/mx.vm/suite.py b/vm/mx.vm/suite.py index bf4c798894af..2a47589268b1 100644 --- a/vm/mx.vm/suite.py +++ b/vm/mx.vm/suite.py @@ -1,6 +1,6 @@ suite = { "name": "vm", - "version" : "23.0.3.1", + "version" : "23.0.4.0", "mxversion": "6.17.0", "release" : False, "groupId" : "org.graalvm", @@ -39,7 +39,7 @@ "name": "graal-nodejs", "subdir": True, "dynamic": True, - "version": "99c8a2c475732c770e7e05ae0823d49648cc7ebb", + "version": "f08d6e3aff29e31bcc9bc324a44c592f98ecab3c", "urls" : [ {"url" : "https://github.com/graalvm/graaljs.git", "kind" : "git"}, {"url": "https://curio.ssw.jku.at/nexus/content/repositories/snapshots", "kind": "binary"}, @@ -49,7 +49,7 @@ "name": "graal-js", "subdir": True, "dynamic": True, - "version": "99c8a2c475732c770e7e05ae0823d49648cc7ebb", + "version": "f08d6e3aff29e31bcc9bc324a44c592f98ecab3c", "urls": [ {"url": "https://github.com/graalvm/graaljs.git", "kind" : "git"}, {"url": "https://curio.ssw.jku.at/nexus/content/repositories/snapshots", "kind": "binary"}, @@ -75,7 +75,7 @@ }, { "name": "graalpython", - "version": "249facdaa87860239cb454df7732d6e810224f9e", + "version": "7c785269a7e8fe70066cab0108412894f6203126", "dynamic": True, "urls": [ {"url": "https://github.com/graalvm/graalpython.git", "kind": "git"}, diff --git a/wasm/mx.wasm/suite.py b/wasm/mx.wasm/suite.py index 1ce04f786e66..bee5be1347a0 100644 --- a/wasm/mx.wasm/suite.py +++ b/wasm/mx.wasm/suite.py @@ -42,7 +42,7 @@ "mxversion": "6.17.0", "name" : "wasm", "groupId" : "org.graalvm.wasm", - "version" : "23.0.3.1", + "version" : "23.0.4.0", "versionConflictResolution" : "latest", "url" : "http://graalvm.org/", "developer" : {