package com.oracle.svm.core;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.graalvm.compiler.api.replacements.Fold;
import org.graalvm.compiler.debug.MethodFilter;
import org.graalvm.compiler.graph.Node.NodeIntrinsic;
import org.graalvm.compiler.java.LambdaUtils;
import org.graalvm.compiler.nodes.BreakpointNode;
import org.graalvm.nativeimage.CurrentIsolate;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
import org.graalvm.nativeimage.c.function.CodePointer;
import org.graalvm.nativeimage.c.type.CCharPointer;
import org.graalvm.nativeimage.c.type.CCharPointerPointer;
import org.graalvm.nativeimage.c.type.CTypeConversion;
import org.graalvm.util.GuardedAnnotationAccess;
import org.graalvm.word.Pointer;
import org.graalvm.word.UnsignedWord;
import org.graalvm.word.WordFactory;
import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.annotate.RecomputeFieldValue;
import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind;
import com.oracle.svm.core.annotate.RestrictHeapAccess;
import com.oracle.svm.core.annotate.TargetClass;
import com.oracle.svm.core.annotate.Uninterruptible;
import com.oracle.svm.core.code.CodeInfo;
import com.oracle.svm.core.code.CodeInfoAccess;
import com.oracle.svm.core.code.CodeInfoTable;
import com.oracle.svm.core.code.UntetheredCodeInfo;
import com.oracle.svm.core.deopt.DeoptimizationSupport;
import com.oracle.svm.core.deopt.DeoptimizedFrame;
import com.oracle.svm.core.deopt.Deoptimizer;
import com.oracle.svm.core.log.Log;
import com.oracle.svm.core.stack.JavaFrameAnchor;
import com.oracle.svm.core.stack.JavaFrameAnchors;
import com.oracle.svm.core.stack.JavaStackWalker;
import com.oracle.svm.core.stack.ThreadStackPrinter;
import com.oracle.svm.core.stack.ThreadStackPrinter.StackFramePrintVisitor;
import com.oracle.svm.core.stack.ThreadStackPrinter.Stage0StackFramePrintVisitor;
import com.oracle.svm.core.stack.ThreadStackPrinter.Stage1StackFramePrintVisitor;
import com.oracle.svm.core.thread.JavaThreads;
import com.oracle.svm.core.thread.VMOperationControl;
import com.oracle.svm.core.thread.VMThreads;
import com.oracle.svm.core.threadlocal.VMThreadLocalInfos;
import com.oracle.svm.core.util.Counter;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.services.Services;
public class SubstrateUtil {
public static final boolean HOSTED;
static {
HOSTED = true;
}
public static String getArchitectureName() {
String arch = System.getProperty("os.arch");
switch (arch) {
case "x86_64":
arch = "amd64";
break;
case "arm64":
arch = "aarch64";
break;
case "sparcv9":
arch = "sparc";
break;
}
return arch;
}
public static boolean isBuildingLibgraal() {
return Services.IS_BUILDING_NATIVE_IMAGE;
}
public static boolean isInLibgraal() {
return Services.IS_IN_NATIVE_IMAGE;
}
private static final Pattern SAFE_SHELL_ARG = Pattern.compile("[A-Za-z0-9@%_\\-+=:,./]+");
public static String quoteShellArg(String arg) {
if (arg.isEmpty()) {
return "''";
}
Matcher m = SAFE_SHELL_ARG.matcher(arg);
if (m.matches()) {
return arg;
}
return "'" + arg.replace("'", "'\"'\"'") + "'";
}
public static String getShellCommandString(List<String> cmd, boolean multiLine) {
StringBuilder sb = new StringBuilder();
for (String arg : cmd) {
sb.append(quoteShellArg(arg));
if (multiLine) {
sb.append(" \\\n");
} else {
sb.append(' ');
}
}
return sb.toString();
}
@TargetClass(com.oracle.svm.core.SubstrateUtil.class)
static final class Target_com_oracle_svm_core_SubstrateUtil {
@Alias @RecomputeFieldValue(kind = Kind.FromAlias, isFinal = true)
private static boolean HOSTED = false;
}
@TargetClass(java.io.FileOutputStream.class)
static final class Target_java_io_FileOutputStream {
@Alias
FileDescriptor fd;
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static FileDescriptor getFileDescriptor(FileOutputStream out) {
return SubstrateUtil.cast(out, Target_java_io_FileOutputStream.class).fd;
}
public static String[] getArgs(int argc, CCharPointerPointer argv) {
String[] args = new String[argc - 1];
for (int i = 1; i < argc; ++i) {
args[i - 1] = CTypeConversion.toJavaString(argv.read(i));
}
return args;
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static UnsignedWord strlen(CCharPointer str) {
UnsignedWord n = WordFactory.zero();
while (((Pointer) str).readByte(n) != 0) {
n = n.add(1);
}
return n;
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static CCharPointer strchr(CCharPointer str, int c) {
int index = 0;
while (true) {
byte b = str.read(index);
if (b == c) {
return str.addressOf(index);
}
if (b == 0) {
return WordFactory.zero();
}
index += 1;
}
}
@SuppressWarnings({"unused", "unchecked"})
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static <T> T cast(Object obj, Class<T> toType) {
return (T) obj;
}
@SuppressWarnings("all")
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static boolean assertionsEnabled() {
boolean assertionsEnabled = false;
assert assertionsEnabled = true;
return assertionsEnabled;
}
@NodeIntrinsic(BreakpointNode.class)
public static native void breakpoint(Object arg0);
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static boolean isPowerOf2(long value) {
return (value & (value - 1)) == 0;
}
@FunctionalInterface
public interface Thunk {
void invoke();
}
private static volatile boolean diagnosticsInProgress = false;
public static boolean isPrintDiagnosticsInProgress() {
return diagnosticsInProgress;
}
public static void printDiagnostics(Log log, Pointer sp, CodePointer ip) {
printDiagnostics(log, sp, ip, WordFactory.nullPointer());
}
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate during printing diagnostics.")
static void printDiagnostics(Log log, Pointer sp, CodePointer ip, RegisterDumper.Context context) {
log.newline();
if (diagnosticsInProgress) {
log.string("Error: printDiagnostics already in progress.").newline();
log.newline();
return;
}
diagnosticsInProgress = true;
try {
dumpRegisters(log, context);
} catch (Exception e) {
dumpException(log, "dumpRegisters", e);
}
try {
dumpJavaFrameAnchors(log);
} catch (Exception e) {
dumpException(log, "dumpJavaFrameAnchors", e);
}
try {
dumpDeoptStubPointer(log);
} catch (Exception e) {
dumpException(log, "dumpDeoptStubPointer", e);
}
try {
dumpTopFrame(log, sp, ip);
} catch (Exception e) {
dumpException(log, "dumpTopFrame", e);
}
try {
dumpVMThreads(log);
} catch (Exception e) {
dumpException(log, "dumpVMThreads", e);
}
IsolateThread currentThread = CurrentIsolate.getCurrentThread();
try {
dumpVMThreadState(log, currentThread);
} catch (Exception e) {
dumpException(log, "dumpVMThreadState", e);
}
try {
dumpRecentVMOperations(log);
} catch (Exception e) {
dumpException(log, "dumpRecentVMOperations", e);
}
dumpRuntimeCompilation(log);
try {
dumpCounters(log);
} catch (Exception e) {
dumpException(log, "dumpCounters", e);
}
try {
dumpStacktraceRaw(log, sp);
} catch (Exception e) {
dumpException(log, "dumpStacktraceRaw", e);
}
dumpStacktrace(log, sp, ip);
if (VMOperationControl.isFrozen()) {
for (IsolateThread vmThread = VMThreads.firstThreadUnsafe(); vmThread.isNonNull(); vmThread = VMThreads.nextThread(vmThread)) {
if (vmThread == CurrentIsolate.getCurrentThread()) {
continue;
}
try {
dumpStacktrace(log, vmThread);
} catch (Exception e) {
dumpException(log, "dumpStacktrace", e);
}
}
}
try {
DiagnosticThunkRegister.getSingleton().callDiagnosticThunks(log);
} catch (Exception e) {
dumpException(log, "callThunks", e);
}
diagnosticsInProgress = false;
}
private static void dumpException(Log log, String context, Exception e) {
log.newline().string("[!!! Exception during ").string(context).string(": ").string(e.getClass().getName()).string("]").newline();
}
private static void dumpRegisters(Log log, RegisterDumper.Context context) {
if (context.isNonNull()) {
log.string("General Purpose Register Set values:").newline();
log.indent(true);
RegisterDumper.singleton().dumpRegisters(log, context);
log.indent(false);
}
}
private static void dumpJavaFrameAnchors(Log log) {
log.string("JavaFrameAnchor dump:").newline();
log.indent(true);
JavaFrameAnchor anchor = JavaFrameAnchors.getFrameAnchor();
if (anchor.isNull()) {
log.string("No anchors").newline();
}
while (anchor.isNonNull()) {
log.string("Anchor ").zhex(anchor.rawValue()).string(" LastJavaSP ").zhex(anchor.getLastJavaSP().rawValue()).string(" LastJavaIP ").zhex(anchor.getLastJavaIP().rawValue()).newline();
anchor = anchor.getPreviousAnchor();
}
log.indent(false);
}
private static void dumpDeoptStubPointer(Log log) {
if (DeoptimizationSupport.enabled()) {
log.string("DeoptStubPointer address: ").zhex(DeoptimizationSupport.getDeoptStubPointer().rawValue()).newline().newline();
}
}
private static void dumpTopFrame(Log log, Pointer sp, CodePointer ip) {
log.string("TopFrame info:").newline();
log.indent(true);
if (sp.isNonNull() && ip.isNonNull()) {
long totalFrameSize = getTotalFrameSize(sp, ip);
DeoptimizedFrame deoptFrame = Deoptimizer.checkDeoptimized(sp);
if (deoptFrame != null) {
log.string("RSP ").zhex(sp.rawValue()).string(" frame was deoptimized:").newline();
log.string("SourcePC ").zhex(deoptFrame.getSourcePC().rawValue()).newline();
log.string("SourceTotalFrameSize ").signed(totalFrameSize).newline();
} else if (totalFrameSize != -1) {
log.string("TotalFrameSize in CodeInfoTable ").signed(totalFrameSize).newline();
}
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.rawValue()).newline();
Pointer lastSp = anchor.getLastJavaSP();
log.string("LastJavaSP ").zhex(lastSp.rawValue()).newline();
CodePointer lastIp = anchor.getLastJavaIP();
log.string("LastJavaIP ").zhex(lastIp.rawValue()).newline();
}
}
}
log.indent(false);
}
@Uninterruptible(reason = "Prevent deoptimization of stack frames while in this method.")
private static long getTotalFrameSize(Pointer sp, CodePointer ip) {
DeoptimizedFrame deoptFrame = Deoptimizer.checkDeoptimized(sp);
if (deoptFrame != null) {
return deoptFrame.getSourceTotalFrameSize();
}
UntetheredCodeInfo untetheredInfo = CodeInfoTable.lookupCodeInfo(ip);
if (untetheredInfo.isNonNull()) {
Object tether = CodeInfoAccess.acquireTether(untetheredInfo);
try {
CodeInfo codeInfo = CodeInfoAccess.convert(untetheredInfo, tether);
return getTotalFrameSize0(ip, codeInfo);
} finally {
CodeInfoAccess.releaseTether(untetheredInfo, tether);
}
}
return -1;
}
@Uninterruptible(reason = "Wrap the now safe call to interruptibly look up the frame size.", calleeMustBe = false)
private static long getTotalFrameSize0(CodePointer ip, CodeInfo codeInfo) {
return CodeInfoAccess.lookupTotalFrameSize(codeInfo, CodeInfoAccess.relativeIP(codeInfo, ip));
}
private static void dumpVMThreads(Log log) {
log.string("VMThreads info:").newline();
log.indent(true);
for (IsolateThread vmThread = VMThreads.firstThreadUnsafe(); vmThread.isNonNull(); vmThread = VMThreads.nextThread(vmThread)) {
log.string("VMThread ").zhex(vmThread.rawValue()).spaces(2).string(VMThreads.StatusSupport.getStatusString(vmThread))
.spaces(2).object(JavaThreads.fromVMThread(vmThread)).newline();
}
log.indent(false);
}
private static void dumpVMThreadState(Log log, IsolateThread currentThread) {
log.string("VM Thread State for current thread ").zhex(currentThread.rawValue()).string(":").newline();
log.indent(true);
VMThreadLocalInfos.dumpToLog(log, currentThread);
log.indent(false);
}
private static void dumpRecentVMOperations(Log log) {
log.string("VMOperation dump:").newline();
log.indent(true);
VMOperationControl.logRecentEvents(log);
log.indent(false);
}
static void dumpRuntimeCompilation(Log log) {
if (DeoptimizationSupport.enabled()) {
log.newline().string("RuntimeCodeCache dump:").newline();
log.indent(true);
try {
dumpRecentRuntimeCodeCacheOperations(log);
} catch (Exception e) {
dumpException(log, "dumpRecentRuntimeCodeCacheOperations", e);
}
log.newline();
try {
dumpRuntimeCodeCacheTable(log);
} catch (Exception e) {
dumpException(log, "dumpRuntimeCodeCacheTable", e);
}
log.indent(false);
try {
dumpRecentDeopts(log);
} catch (Exception e) {
dumpException(log, "dumpRecentDeopts", e);
}
}
}
private static void dumpRecentRuntimeCodeCacheOperations(Log log) {
CodeInfoTable.getRuntimeCodeCache().logRecentOperations(log);
}
private static void dumpRuntimeCodeCacheTable(Log log) {
CodeInfoTable.getRuntimeCodeCache().logTable(log);
}
private static void dumpRecentDeopts(Log log) {
log.string("Deoptimizer dump:").newline();
log.indent(true);
Deoptimizer.logRecentDeoptimizationEvents(log);
log.indent(false);
}
private static void dumpCounters(Log log) {
log.string("Dump Counters:").newline();
log.indent(true);
Counter.logValues();
log.indent(false);
}
private static void dumpStacktraceRaw(Log log, Pointer sp) {
log.string("Raw Stacktrace:").newline();
log.indent(true);
log.hexdump(sp, 8, 16);
log.indent(false);
}
private static final Stage0StackFramePrintVisitor[] PRINT_VISITORS = new Stage0StackFramePrintVisitor[]{Stage0StackFramePrintVisitor.SINGLETON, Stage1StackFramePrintVisitor.SINGLETON,
StackFramePrintVisitor.SINGLETON};
private static void dumpStacktrace(Log log, Pointer sp, CodePointer ip) {
for (int i = 0; i < PRINT_VISITORS.length; i++) {
try {
log.string("Stacktrace Stage ").signed(i).string(":").newline();
log.indent(true);
ThreadStackPrinter.printStacktrace(sp, ip, PRINT_VISITORS[i], log);
log.indent(false);
} catch (Exception e) {
dumpException(log, "dumpStacktrace", e);
}
}
}
private static void dumpStacktrace(Log log, IsolateThread vmThread) {
log.string("Full Stacktrace for VMThread ").zhex(vmThread.rawValue()).string(":").newline();
log.indent(true);
JavaStackWalker.walkThread(vmThread, StackFramePrintVisitor.SINGLETON, log);
log.indent(false);
}
@FunctionalInterface
public interface DiagnosticThunk {
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate during printing diagnostics.")
void invokeWithoutAllocation(Log log);
}
public static class DiagnosticThunkRegister {
DiagnosticThunk[] diagnosticThunkRegistry;
@Fold
public static synchronized DiagnosticThunkRegister getSingleton() {
if (!ImageSingletons.contains(SubstrateUtil.DiagnosticThunkRegister.class)) {
ImageSingletons.add(SubstrateUtil.DiagnosticThunkRegister.class, new DiagnosticThunkRegister());
}
return ImageSingletons.lookup(SubstrateUtil.DiagnosticThunkRegister.class);
}
@Platforms(Platform.HOSTED_ONLY.class)
DiagnosticThunkRegister() {
this.diagnosticThunkRegistry = new DiagnosticThunk[0];
}
@Platforms(Platform.HOSTED_ONLY.class)
public synchronized void register(DiagnosticThunk diagnosticThunk) {
final DiagnosticThunk[] newArray = Arrays.copyOf(diagnosticThunkRegistry, diagnosticThunkRegistry.length + 1);
newArray[newArray.length - 1] = diagnosticThunk;
diagnosticThunkRegistry = newArray;
}
void callDiagnosticThunks(Log log) {
for (int i = 0; i < diagnosticThunkRegistry.length; i += 1) {
diagnosticThunkRegistry[i].invokeWithoutAllocation(log);
}
}
}
public static String[] split(String value, String separator) {
return split(value, separator, 0);
}
public static String[] split(String value, String separator, int limit) {
int offset = 0;
int next;
ArrayList<String> list = null;
while ((next = value.indexOf(separator, offset)) != -1) {
if (list == null) {
list = new ArrayList<>();
}
boolean limited = limit > 0;
if (!limited || list.size() < limit - 1) {
list.add(value.substring(offset, next));
offset = next + separator.length();
} else {
break;
}
}
if (offset == 0) {
return new String[]{value};
}
list.add(value.substring(offset));
return list.toArray(new String[list.size()]);
}
public static String toHex(byte[] data) {
return LambdaUtils.toHex(data);
}
public static String digest(String value) {
return LambdaUtils.digest(value);
}
public static String uniqueShortName(ResolvedJavaMethod m) {
StringBuilder fullName = new StringBuilder();
fullName.append(m.getDeclaringClass().toClassName()).append(".").append(m.getName()).append("(");
for (int i = 0; i < m.getSignature().getParameterCount(false); i++) {
fullName.append(m.getSignature().getParameterType(i, null).toClassName()).append(",");
}
fullName.append(')');
if (!m.isConstructor()) {
fullName.append(m.getSignature().getReturnType(null).toClassName());
}
return stripPackage(m.getDeclaringClass().toJavaName()) + "_" +
(m.isConstructor() ? "constructor" : m.getName()) + "_" +
SubstrateUtil.digest(fullName.toString());
}
public static String uniqueShortName(Member m) {
StringBuilder fullName = new StringBuilder();
fullName.append(m.getDeclaringClass().getName()).append(".");
if (m instanceof Constructor) {
fullName.append("<init>");
} else {
fullName.append(m.getName());
}
if (m instanceof Executable) {
fullName.append("(");
for (Class<?> c : ((Executable) m).getParameterTypes()) {
fullName.append(c.getName()).append(",");
}
fullName.append(')');
if (m instanceof Method) {
fullName.append(((Method) m).getReturnType().getName());
}
}
return stripPackage(m.getDeclaringClass().getTypeName()) + "_" +
(m instanceof Constructor ? "constructor" : m.getName()) + "_" +
SubstrateUtil.digest(fullName.toString());
}
private static String stripPackage(String qualifiedClassName) {
return qualifiedClassName.substring(qualifiedClassName.lastIndexOf(".") + 1);
}
@Platforms(Platform.HOSTED_ONLY.class)
public static String mangleName(String methodName) {
StringBuilder out = new StringBuilder();
for (int i = 0; i < methodName.length(); ++i) {
char c = methodName.charAt(i);
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (i == 0 && c == '.') || (i > 0 && c >= '0' && c <= '9')) {
out.append(c);
} else if (c == '_') {
out.append("__");
} else {
out.append('_');
out.append(String.format("%04x", (int) c));
}
}
String mangled = out.toString();
assert mangled.matches("[a-zA-Z\\._][a-zA-Z0-9_]*");
return mangled;
}
public static class NativeImageLoadingShield {
@Platforms(Platform.HOSTED_ONLY.class)
public static boolean isNeverInline(ResolvedJavaMethod method) {
String[] neverInline = SubstrateOptions.NeverInline.getValue();
return GuardedAnnotationAccess.isAnnotationPresent(method, com.oracle.svm.core.annotate.NeverInline.class) ||
(neverInline != null && Arrays.stream(neverInline).anyMatch(re -> MethodFilter.parse(re).matches(method)));
}
}
}