package com.oracle.svm.hosted.image;
import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.CONTRACT;
import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND;
import java.nio.file.Path;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;
import com.oracle.svm.core.SubstrateOptions;
import org.graalvm.compiler.code.CompilationResult;
import org.graalvm.compiler.code.SourceMapping;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.graph.NodeSourcePosition;
import org.graalvm.nativeimage.ImageSingletons;
import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider;
import com.oracle.graal.pointsto.meta.AnalysisType;
import com.oracle.objectfile.debuginfo.DebugInfoProvider;
import com.oracle.svm.core.graal.code.SubstrateBackend;
import com.oracle.svm.hosted.image.sources.SourceManager;
import com.oracle.svm.hosted.meta.HostedMethod;
import com.oracle.svm.hosted.meta.HostedType;
import jdk.vm.ci.meta.LineNumberTable;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;
class NativeImageDebugInfoProvider implements DebugInfoProvider {
private final DebugContext debugContext;
private final NativeImageCodeCache codeCache;
@SuppressWarnings("unused") private final NativeImageHeap heap;
NativeImageDebugInfoProvider(DebugContext debugContext, NativeImageCodeCache codeCache, NativeImageHeap heap) {
super();
this.debugContext = debugContext;
this.codeCache = codeCache;
this.heap = heap;
}
@Override
public Stream<DebugTypeInfo> typeInfoProvider() {
return Stream.empty();
}
@Override
public Stream<DebugCodeInfo> codeInfoProvider() {
return codeCache.compilations.entrySet().stream().map(entry -> new NativeImageDebugCodeInfo(entry.getKey(), entry.getValue()));
}
@Override
public Stream<DebugDataInfo> dataInfoProvider() {
return Stream.empty();
}
private class NativeImageDebugCodeInfo implements DebugCodeInfo {
private final HostedMethod method;
private final ResolvedJavaType javaType;
private final CompilationResult compilation;
private Path fullFilePath;
private final Path cachePath;
@SuppressWarnings("try")
NativeImageDebugCodeInfo(HostedMethod method, CompilationResult compilation) {
this.method = method;
HostedType declaringClass = method.getDeclaringClass();
Class<?> clazz = declaringClass.getJavaClass();
this.javaType = declaringClass.getWrapped().getWrapped();
this.compilation = compilation;
this.cachePath = SubstrateOptions.getDebugInfoSourceCacheRoot();
SourceManager sourceManager = ImageSingletons.lookup(SourceManager.class);
try (DebugContext.Scope s = debugContext.scope("DebugCodeInfo", declaringClass)) {
fullFilePath = sourceManager.findAndCacheSource(javaType, clazz, debugContext);
} catch (Throwable e) {
throw debugContext.handle(e);
}
}
@SuppressWarnings("try")
@Override
public void debugContext(Consumer<DebugContext> action) {
try (DebugContext.Scope s = debugContext.scope("DebugCodeInfo", method)) {
action.accept(debugContext);
} catch (Throwable e) {
throw debugContext.handle(e);
}
}
@Override
public String fileName() {
if (fullFilePath != null) {
Path filename = fullFilePath.getFileName();
if (filename != null) {
return filename.toString();
}
}
return "";
}
@Override
public Path filePath() {
if (fullFilePath != null) {
return fullFilePath.getParent();
}
return null;
}
@Override
public Path cachePath() {
return cachePath;
}
@Override
public String className() {
return javaType.toClassName();
}
@Override
public String methodName() {
return method.format("%n");
}
@Override
public String symbolNameForMethod() {
return NativeBootImage.localSymbolNameForMethod(method);
}
@Override
public String paramNames() {
return method.format("%P");
}
@Override
public String returnTypeName() {
return method.format("%R");
}
@Override
public int addressLo() {
return method.getCodeAddressOffset();
}
@Override
public int addressHi() {
return method.getCodeAddressOffset() + compilation.getTargetCodeSize();
}
@Override
public int line() {
LineNumberTable lineNumberTable = method.getLineNumberTable();
if (lineNumberTable != null) {
return lineNumberTable.getLineNumber(0);
}
return -1;
}
@Override
public Stream<DebugLineInfo> lineInfoProvider() {
if (fileName().length() == 0) {
return Stream.empty();
}
return compilation.getSourceMappings().stream().map(sourceMapping -> new NativeImageDebugLineInfo(sourceMapping));
}
@Override
public int getFrameSize() {
return compilation.getTotalFrameSize();
}
@Override
public List<DebugFrameSizeChange> getFrameSizeChanges() {
List<DebugFrameSizeChange> frameSizeChanges = new LinkedList<>();
for (CompilationResult.CodeMark mark : compilation.getMarks()) {
if (mark.id.equals(SubstrateBackend.SubstrateMarkId.PROLOGUE_DECD_RSP)) {
NativeImageDebugFrameSizeChange sizeChange = new NativeImageDebugFrameSizeChange(mark.pcOffset, EXTEND);
frameSizeChanges.add(sizeChange);
} else if (mark.id.equals(SubstrateBackend.SubstrateMarkId.EPILOGUE_INCD_RSP)) {
NativeImageDebugFrameSizeChange sizeChange = new NativeImageDebugFrameSizeChange(mark.pcOffset, CONTRACT);
frameSizeChanges.add(sizeChange);
} else if (mark.id.equals(SubstrateBackend.SubstrateMarkId.EPILOGUE_END) && mark.pcOffset < compilation.getTargetCodeSize()) {
NativeImageDebugFrameSizeChange sizeChange = new NativeImageDebugFrameSizeChange(mark.pcOffset, EXTEND);
frameSizeChanges.add(sizeChange);
}
}
return frameSizeChanges;
}
@Override
public boolean isDeoptTarget() {
return methodName().endsWith(HostedMethod.METHOD_NAME_DEOPT_SUFFIX);
}
}
private class NativeImageDebugLineInfo implements DebugLineInfo {
private final int bci;
private final ResolvedJavaMethod method;
private final int lo;
private final int hi;
private Path cachePath;
private Path fullFilePath;
NativeImageDebugLineInfo(SourceMapping sourceMapping) {
NodeSourcePosition position = sourceMapping.getSourcePosition();
int posbci = position.getBCI();
this.bci = (posbci >= 0 ? posbci : 0);
this.method = position.getMethod();
this.lo = sourceMapping.getStartOffset();
this.hi = sourceMapping.getEndOffset();
this.cachePath = SubstrateOptions.getDebugInfoSourceCacheRoot();
computeFullFilePath();
}
@Override
public String fileName() {
if (fullFilePath != null) {
Path fileName = fullFilePath.getFileName();
if (fileName != null) {
return fileName.toString();
}
}
return null;
}
@Override
public Path filePath() {
if (fullFilePath != null) {
return fullFilePath.getParent();
}
return null;
}
@Override
public Path cachePath() {
return cachePath;
}
@Override
public String className() {
return method.format("%H");
}
@Override
public String methodName() {
return method.format("%n");
}
@Override
public String symbolNameForMethod() {
return NativeBootImage.localSymbolNameForMethod(method);
}
@Override
public int addressLo() {
return lo;
}
@Override
public int addressHi() {
return hi;
}
@Override
public int line() {
LineNumberTable lineNumberTable = method.getLineNumberTable();
if (lineNumberTable != null) {
return lineNumberTable.getLineNumber(bci);
}
return -1;
}
@SuppressWarnings("try")
private void computeFullFilePath() {
ResolvedJavaType declaringClass = method.getDeclaringClass();
Class<?> clazz = null;
if (declaringClass instanceof OriginalClassProvider) {
clazz = ((OriginalClassProvider) declaringClass).getJavaClass();
}
if (declaringClass instanceof HostedType) {
declaringClass = ((HostedType) declaringClass).getWrapped();
}
if (declaringClass instanceof AnalysisType) {
declaringClass = ((AnalysisType) declaringClass).getWrapped();
}
SourceManager sourceManager = ImageSingletons.lookup(SourceManager.class);
try (DebugContext.Scope s = debugContext.scope("DebugCodeInfo", declaringClass)) {
fullFilePath = sourceManager.findAndCacheSource(declaringClass, clazz, debugContext);
} catch (Throwable e) {
throw debugContext.handle(e);
}
}
}
private class NativeImageDebugFrameSizeChange implements DebugFrameSizeChange {
private int offset;
private Type type;
NativeImageDebugFrameSizeChange(int offset, Type type) {
this.offset = offset;
this.type = type;
}
@Override
public int getOffset() {
return offset;
}
@Override
public Type getType() {
return type;
}
}
}