diff --git a/compiler/src/jdk.graal.compiler.processor/src/jdk/graal/compiler/processor/AbstractProcessor.java b/compiler/src/jdk.graal.compiler.processor/src/jdk/graal/compiler/processor/AbstractProcessor.java
index 030c98250fc3..02f3192b2d29 100644
--- a/compiler/src/jdk.graal.compiler.processor/src/jdk/graal/compiler/processor/AbstractProcessor.java
+++ b/compiler/src/jdk.graal.compiler.processor/src/jdk/graal/compiler/processor/AbstractProcessor.java
@@ -342,7 +342,7 @@ public void createProviderFile(String providerClassName, String serviceClassName
* Determines if a given exception is (most likely) caused by
* Bug 367599.
*/
- private static boolean isBug367599(Throwable t) {
+ protected static boolean isBug367599(Throwable t) {
if (t instanceof FilerException) {
for (StackTraceElement ste : t.getStackTrace()) {
if (ste.toString().contains("org.eclipse.jdt.internal.apt.pluggable.core.filer.IdeFilerImpl.create")) {
diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py
index c5968c40f9d7..b391bccfaa88 100644
--- a/substratevm/mx.substratevm/suite.py
+++ b/substratevm/mx.substratevm/suite.py
@@ -251,7 +251,8 @@
"compiler:GRAAL_PROCESSOR"
],
"requires" : [
- "java.compiler" # javax.annotation.processing.*
+ "java.compiler", # javax.annotation.processing.*
+ "jdk.compiler", # com.sun.source.util.*
],
"javaCompliance" : "21+",
"checkstyle" : "com.oracle.svm.core",
diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsUtils.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsUtils.java
index 382e23b8bd4b..c0808ed094dd 100644
--- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsUtils.java
+++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsUtils.java
@@ -29,6 +29,7 @@
import java.io.FileDescriptor;
import java.io.IOException;
+import com.oracle.svm.core.util.BasedOnJDKFile;
import org.graalvm.nativeimage.StackValue;
import org.graalvm.nativeimage.c.function.CFunctionPointer;
import org.graalvm.nativeimage.c.struct.CPointerTo;
@@ -170,6 +171,8 @@ static void writeBytes(FileDescriptor descriptor, byte[] bytes, int off, int len
/** Retrieve a nanosecond counter for elapsed time measurement. */
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ @BasedOnJDKFile("src/hotspot/os/windows/os_windows.cpp#L970-L977")
+ @BasedOnJDKFile("src/hotspot/os/windows/os_windows.cpp#L1075-L1081")
public static long getNanoCounter() {
if (performanceFrequency == 0L) {
CLongPointer count = StackValue.get(CLongPointer.class);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/amd64/AMD64LibCHelper.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/amd64/AMD64LibCHelper.java
index 40e66a67c958..57ffaa3a92ee 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/amd64/AMD64LibCHelper.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/amd64/AMD64LibCHelper.java
@@ -36,17 +36,14 @@
import org.graalvm.nativeimage.c.type.CCharPointer;
import org.graalvm.word.PointerBase;
-import jdk.graal.compiler.lir.SyncPort;
+import com.oracle.svm.core.util.BasedOnJDKFile;
-// @formatter:off
-@SyncPort(from = "https://github.com/openjdk/jdk/blob/34f85ee94e8b45bcebbf8ba52a38c92a7185b54a/src/hotspot/cpu/x86/vm_version_x86.hpp#L40-L304",
- sha1 = "5cc04c08555379223cc02a15774159076d3cdf1d")
/*
* To be kept in sync with:
* - substratevm/src/com.oracle.svm.native.libchelper/include/amd64hotspotcpuinfo.h
* - substratevm/src/com.oracle.svm.native.libchelper/src/cpuid.c
*/
-// @formatter:on
+@BasedOnJDKFile("src/hotspot/cpu/x86/vm_version_x86.hpp#L40-L304")
@CLibrary(value = "libchelper", requireStatic = true)
public class AMD64LibCHelper {
@Platforms(Platform.AMD64.class)
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/headers/JNIVersionJDKLatest.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/headers/JNIVersionJDKLatest.java
index fa03c24a386c..4541ea6b907f 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/headers/JNIVersionJDKLatest.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/headers/JNIVersionJDKLatest.java
@@ -24,6 +24,7 @@
*/
package com.oracle.svm.core.jni.headers;
+import com.oracle.svm.core.util.BasedOnJDKFile;
import org.graalvm.nativeimage.c.CContext;
import org.graalvm.nativeimage.c.constant.CConstant;
@@ -46,6 +47,7 @@ public final class JNIVersionJDKLatest {
* gets available, the "value" property of the CConstant annotation below must be removed.
*/
@CConstant(value = "JNI_VERSION_21")
+ @BasedOnJDKFile("src/java.base/share/native/include/jni.h#L1985-L1996")
public static native int JNI_VERSION_LATEST();
// Checkstyle: resume
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/BasedOnJDKFile.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/BasedOnJDKFile.java
new file mode 100644
index 000000000000..d9da52e08b74
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/BasedOnJDKFile.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2024, 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 com.oracle.svm.core.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
+
+/**
+ * Documents that the element is based on a JDK source file. This is mainly useful for non-Java
+ * sources like C++ files. For Java classes, {@link BasedOnJDKClass} might be more appropriate.
+ */
+@Repeatable(BasedOnJDKFile.List.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target(value = {ElementType.TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD})
+@Platforms(Platform.HOSTED_ONLY.class)
+public @interface BasedOnJDKFile {
+
+ /**
+ * Path to the source file relative to the repository root (usually
+ * openjdk). The path elements must be separated by
+ * slashes ({@code /}) and since it is a relative path, it must not start with a slash.
+ *
+ * To specify a line range, a suffix of the form {@code #L[0-9]+-L[0-9]+} might be added. Full
+ * example:
+ *
+ *
+ * @BasedOnJDKFile("src/hotspot/cpu/x86/vm_version_x86.hpp#L40-L304")
+ *
+ *
+ * The path and line numbers always apply to the latest supported JDK version. That version can
+ * be retrieved from the {@code jdks.oraclejdk-latest} entry from {@code common.json} in the
+ * root of this repository, or by looking up the latest version in the
+ * {@code JVMCI_MIN_VERSIONS} map in {@link jdk.graal.compiler.hotspot.JVMCIVersionCheck}. At
+ * the time of writing this is {@code jdk-23+8} (formatted as a git tag as used by the openjdk).
+ * That information can also be used to view the respective line range on GitHub via
+ * {@code https://github.com/openjdk/jdk/blob//}. For the example above, it
+ * would translate to
+ * {@code https://github.com/openjdk/jdk/blob/jdk-23+8/src/hotspot/cpu/x86/vm_version_x86.hpp#L40-L304}
+ */
+ String value();
+
+ /**
+ * Support for making {@link BasedOnJDKFile} {@linkplain Repeatable repeatable}.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(value = {ElementType.TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD})
+ @Platforms(Platform.HOSTED_ONLY.class)
+ @interface List {
+ BasedOnJDKFile[] value();
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.processor/src/META-INF/services/javax.annotation.processing.Processor b/substratevm/src/com.oracle.svm.processor/src/META-INF/services/javax.annotation.processing.Processor
index 43e591af1f6c..337184082127 100644
--- a/substratevm/src/com.oracle.svm.processor/src/META-INF/services/javax.annotation.processing.Processor
+++ b/substratevm/src/com.oracle.svm.processor/src/META-INF/services/javax.annotation.processing.Processor
@@ -1,2 +1,3 @@
com.oracle.svm.processor.AutomaticallyRegisteredFeatureProcessor
com.oracle.svm.processor.AutomaticallyRegisteredImageSingletonProcessor
+com.oracle.svm.processor.BasedOnJDKFileProcessor
diff --git a/substratevm/src/com.oracle.svm.processor/src/com/oracle/svm/processor/BasedOnJDKFileProcessor.java b/substratevm/src/com.oracle.svm.processor/src/com/oracle/svm/processor/BasedOnJDKFileProcessor.java
new file mode 100644
index 000000000000..68ddb01cc379
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.processor/src/com/oracle/svm/processor/BasedOnJDKFileProcessor.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2024, 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 com.oracle.svm.processor;
+
+// Checkstyle: allow Class.getSimpleName
+
+import static javax.tools.Diagnostic.Kind.ERROR;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.tools.Diagnostic.Kind;
+import javax.tools.FileObject;
+import javax.tools.StandardLocation;
+
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.LineMap;
+import com.sun.source.util.SourcePositions;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.Trees;
+
+import jdk.graal.compiler.processor.AbstractProcessor;
+
+/**
+ * This processor visits {@code @BasedOnJDKFile} annotated elements, collect information from the
+ * annotation and the annotated element and writes the result as a JSON file to
+ * {@link StandardLocation#SOURCE_OUTPUT} for further processing. See {@link #processAnnotation} for
+ * the details.
+ */
+@SupportedAnnotationTypes({BasedOnJDKFileProcessor.ANNOTATION_CLASS_NAME, BasedOnJDKFileProcessor.ANNOTATION_LIST_CLASS_NAME})
+public class BasedOnJDKFileProcessor extends AbstractProcessor {
+
+ static final String ANNOTATION_CLASS_NAME = "com.oracle.svm.core.util.BasedOnJDKFile";
+ static final String ANNOTATION_LIST_CLASS_NAME = "com.oracle.svm.core.util.BasedOnJDKFile.List";
+ static final Pattern FILE_PATTERN = Pattern.compile("^(?[-_.A-Za-z0-9][-_./A-Za-z0-9]*)(#L(?[0-9]+)-L(?[0-9]+))?$");
+ static final String FILE_PATTERN_STR = "path/to/file.ext(#L[0-9]+-L[0-9]+)?";
+ public static final int FULL_FILE_LINE_MARKER = 0;
+
+ private final Set processed = new HashSet<>();
+ private Trees trees;
+ private boolean isECJ = false;
+
+ @Override
+ public synchronized void init(ProcessingEnvironment pe) {
+ super.init(pe);
+ try {
+ this.trees = Trees.instance(pe);
+ this.isECJ = false;
+ } catch (IllegalArgumentException e) {
+ // probably compiling with ECJ
+ this.isECJ = true;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private List extends AnnotationValue> getAnnotationValues(Element element, TypeElement listTypeElement) {
+ AnnotationMirror listMirror = getAnnotation(element, listTypeElement.asType());
+ return getAnnotationValue(listMirror, "value", List.class);
+ }
+
+ record SourceInfo(String path, long lineStart, long lineEnd) {
+ }
+
+ private static String quoteString(String s) {
+ if (s == null) {
+ return "null";
+ }
+ StringBuilder sb = new StringBuilder(2 + s.length() + 8 /* room for escaping */);
+ sb.append('"');
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (c == '"' || c == '\\') {
+ sb.append('\\');
+ sb.append(c);
+ } else if (c < 0x001F) {
+ sb.append(String.format("\\u%04x", (int) c));
+ } else {
+ sb.append(c);
+ }
+ }
+ sb.append('"');
+ return sb.toString();
+ }
+
+ private void processAnnotation(Element annotatedElement, AnnotationMirror annotationMirror) {
+ String annotationValue = Objects.requireNonNull(getAnnotationValue(annotationMirror, "value", String.class));
+ SourceInfo targetSourceInfo = parseBasedOnJDKFileAnnotation(annotationValue);
+ if (targetSourceInfo == null) {
+ // error parsing the annotation
+ return;
+ }
+
+ String qualifiedName = getQualifiedName(annotatedElement);
+
+ Element[] originatingElements = new Element[]{annotatedElement};
+ String uniqueName = getUniqueName(qualifiedName, targetSourceInfo.path, targetSourceInfo.lineStart, targetSourceInfo.lineEnd);
+
+ String filename = "jdk_source_info/" + URLEncoder.encode(uniqueName, StandardCharsets.UTF_8) + ".json";
+ SourceInfo annotatedSourceInfo = getAnnotatedSourceInfo(annotatedElement);
+ try {
+ FileObject file = processingEnv.getFiler().createResource(StandardLocation.SOURCE_OUTPUT, "", filename, originatingElements);
+ PrintWriter writer = new PrintWriter(new OutputStreamWriter(file.openOutputStream(), "UTF-8"));
+ writer.println("{");
+ writer.println(" \"annotatedElement\": {");
+ writer.println(" \"qualifiedName\": " + quoteString(qualifiedName) + ",");
+ writer.println(" \"annotationValue\": " + quoteString(annotationValue) + ",");
+ writer.println(" \"uniqueName\": " + quoteString(uniqueName) + ",");
+ printSourceInfo(writer, annotatedSourceInfo, " ");
+ writer.println();
+ writer.println(" },");
+ writer.println(" \"target\": {");
+ printSourceInfo(writer, targetSourceInfo, " ");
+ writer.println();
+ writer.println(" }");
+ writer.println("}");
+ writer.close();
+ } catch (IOException e) {
+ processingEnv.getMessager().printMessage(isBug367599(e) ? Kind.NOTE : ERROR, e.getMessage(), originatingElements[0]);
+ }
+ }
+
+ private static String getQualifiedName(Element annotatedElement) {
+ if (annotatedElement instanceof TypeElement typeElement) {
+ return typeElement.getQualifiedName().toString();
+ }
+ if (annotatedElement instanceof ExecutableElement executableElement) {
+ TypeElement enclosingElement = (TypeElement) executableElement.getEnclosingElement();
+ return enclosingElement.getQualifiedName().toString() + "#" + executableElement.getSimpleName();
+ }
+ if (annotatedElement instanceof VariableElement variableElement) {
+ TypeElement enclosingElement = (TypeElement) variableElement.getEnclosingElement();
+ return enclosingElement.getQualifiedName().toString() + "#" + variableElement.getSimpleName();
+ }
+ throw new RuntimeException("Unexpected element class: " + annotatedElement.getClass().getSimpleName());
+ }
+
+ private SourceInfo parseBasedOnJDKFileAnnotation(String annotationValue) {
+ Matcher matcher = FILE_PATTERN.matcher(annotationValue);
+ if (!matcher.matches()) {
+ env().getMessager().printMessage(ERROR, String.format("Invalid path: %s%nShould be %s", annotationValue, FILE_PATTERN_STR));
+ return null;
+ }
+ String lineStart = matcher.group("lineStart");
+ String lineEnd = matcher.group("lineEnd");
+ return new SourceInfo(matcher.group("path"), lineStart == null ? FULL_FILE_LINE_MARKER : Long.parseLong(lineStart), lineEnd == null ? FULL_FILE_LINE_MARKER : Long.parseLong(lineEnd));
+ }
+
+ private SourceInfo getAnnotatedSourceInfo(Element annotatedElement) {
+ TreePath tp = this.trees.getPath(annotatedElement);
+ CompilationUnitTree cut = tp.getCompilationUnit();
+ LineMap lineMap = cut.getLineMap();
+
+ String sourceFileName = cut.getSourceFile().getName();
+
+ SourcePositions sp = trees.getSourcePositions();
+ long start = sp.getStartPosition(cut, tp.getLeaf());
+ long end = sp.getEndPosition(cut, tp.getLeaf());
+
+ return new SourceInfo(sourceFileName, lineMap.getLineNumber(start), lineMap.getLineNumber(end));
+ }
+
+ private static String getUniqueName(String qualifiedName, String path, long lineStart, long lineEnd) {
+ return String.format("%s/%s-%s/%s", path, lineStart, lineEnd, qualifiedName);
+ }
+
+ private static void printSourceInfo(PrintWriter writer, SourceInfo annotatedSourceInfo, String indent) {
+ writer.println(indent + "\"sourceInfo\": {");
+ writer.println(indent + " \"path\": " + quoteString(annotatedSourceInfo.path) + ",");
+ writer.println(indent + " \"lineStart\": " + annotatedSourceInfo.lineStart + ",");
+ writer.println(indent + " \"lineEnd\": " + annotatedSourceInfo.lineEnd);
+ writer.print(indent + "}");
+ }
+
+ @Override
+ public boolean doProcess(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (isECJ) {
+ // ECJ is not supported
+ return true;
+ }
+ if (roundEnv.processingOver()) {
+ return true;
+ }
+
+ // handle single annotations
+ TypeElement annotationType = getTypeElement(ANNOTATION_CLASS_NAME);
+ for (var element : roundEnv.getElementsAnnotatedWith(annotationType)) {
+ assert element.getKind().isClass() : "Only classes supported for now: " + element;
+ if (processed.add(element)) {
+ AnnotationMirror annotationMirror = getAnnotation(element, annotationType.asType());
+ processAnnotation(element, annotationMirror);
+ }
+ }
+
+ // handle repeated annotations
+ TypeElement annotationListType = getTypeElement(ANNOTATION_LIST_CLASS_NAME);
+ for (var element : roundEnv.getElementsAnnotatedWith(annotationListType)) {
+ assert element.getKind().isClass() : "Only classes supported for now: " + element;
+ if (processed.add(element)) {
+ List extends AnnotationValue> list = getAnnotationValues(element, annotationListType);
+ if (list != null) {
+ for (var annotationValue : list) {
+ AnnotationMirror mirror = (AnnotationMirror) annotationValue.getValue();
+ processAnnotation(element, mirror);
+ }
+ }
+ }
+ }
+ return true;
+ }
+}