package com.oracle.svm.core.code;
import static com.oracle.svm.core.util.VMError.shouldNotReachHere;
import org.graalvm.compiler.api.replacements.Fold;
import org.graalvm.compiler.core.common.util.TypeConversion;
import org.graalvm.compiler.options.Option;
import org.graalvm.nativeimage.ImageSingletons;
import com.oracle.svm.core.annotate.AlwaysInline;
import com.oracle.svm.core.annotate.Uninterruptible;
import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.core.util.ByteArrayReader;
import com.oracle.svm.core.util.Counter;
import com.oracle.svm.core.util.NonmovableByteArrayReader;
public final class CodeInfoDecoder {
public static class Options {
@Option(help = "The granularity of the index for looking up code metadata. Should be a power of 2. Larger values make the index smaller, but access slower.")
public static final HostedOptionKey<Integer> CodeInfoIndexGranularity = new HostedOptionKey<>(256);
}
private CodeInfoDecoder() {
}
static long lookupCodeInfoEntryOffset(CodeInfo info, long ip) {
long entryIP = lookupEntryIP(ip);
long entryOffset = loadEntryOffset(info, ip);
do {
int entryFlags = loadEntryFlags(info, entryOffset);
if (entryIP == ip) {
return entryOffset;
}
entryIP = advanceIP(info, entryOffset, entryIP);
entryOffset = advanceOffset(entryOffset, entryFlags);
} while (entryIP <= ip);
return -1;
}
static void lookupCodeInfo(CodeInfo info, long ip, CodeInfoQueryResult codeInfoQueryResult) {
long sizeEncoding = initialSizeEncoding();
long entryIP = lookupEntryIP(ip);
long entryOffset = loadEntryOffset(info, ip);
do {
int entryFlags = loadEntryFlags(info, entryOffset);
sizeEncoding = updateSizeEncoding(info, entryOffset, entryFlags, sizeEncoding);
if (entryIP == ip) {
codeInfoQueryResult.encodedFrameSize = sizeEncoding;
codeInfoQueryResult.exceptionOffset = loadExceptionOffset(info, entryOffset, entryFlags);
codeInfoQueryResult.referenceMapIndex = loadReferenceMapIndex(info, entryOffset, entryFlags);
codeInfoQueryResult.frameInfo = loadFrameInfo(info, entryOffset, entryFlags);
return;
}
entryIP = advanceIP(info, entryOffset, entryIP);
entryOffset = advanceOffset(entryOffset, entryFlags);
} while (entryIP <= ip);
codeInfoQueryResult.encodedFrameSize = sizeEncoding;
codeInfoQueryResult.exceptionOffset = CodeInfoQueryResult.NO_EXCEPTION_OFFSET;
codeInfoQueryResult.referenceMapIndex = CodeInfoQueryResult.NO_REFERENCE_MAP;
codeInfoQueryResult.frameInfo = CodeInfoQueryResult.NO_FRAME_INFO;
}
static void lookupCodeInfo(CodeInfo info, long ip, SimpleCodeInfoQueryResult codeInfoQueryResult) {
long sizeEncoding = initialSizeEncoding();
long entryIP = lookupEntryIP(ip);
long entryOffset = loadEntryOffset(info, ip);
do {
int entryFlags = loadEntryFlags(info, entryOffset);
sizeEncoding = updateSizeEncoding(info, entryOffset, entryFlags, sizeEncoding);
if (entryIP == ip) {
codeInfoQueryResult.setEncodedFrameSize(sizeEncoding);
codeInfoQueryResult.setExceptionOffset(loadExceptionOffset(info, entryOffset, entryFlags));
codeInfoQueryResult.setReferenceMapIndex(loadReferenceMapIndex(info, entryOffset, entryFlags));
return;
}
entryIP = advanceIP(info, entryOffset, entryIP);
entryOffset = advanceOffset(entryOffset, entryFlags);
} while (entryIP <= ip);
codeInfoQueryResult.setEncodedFrameSize(sizeEncoding);
codeInfoQueryResult.setExceptionOffset(CodeInfoQueryResult.NO_EXCEPTION_OFFSET);
codeInfoQueryResult.setReferenceMapIndex(CodeInfoQueryResult.NO_REFERENCE_MAP);
}
static long lookupDeoptimizationEntrypoint(CodeInfo info, long method, long encodedBci, CodeInfoQueryResult codeInfo) {
long sizeEncoding = initialSizeEncoding();
long entryIP = lookupEntryIP(method);
long entryOffset = loadEntryOffset(info, method);
while (true) {
int entryFlags = loadEntryFlags(info, entryOffset);
sizeEncoding = updateSizeEncoding(info, entryOffset, entryFlags, sizeEncoding);
if (entryIP == method) {
break;
}
entryIP = advanceIP(info, entryOffset, entryIP);
entryOffset = advanceOffset(entryOffset, entryFlags);
if (entryIP > method) {
return -1;
}
}
assert entryIP == method;
assert decodeMethodStart(loadEntryFlags(info, entryOffset), sizeEncoding);
do {
int entryFlags = loadEntryFlags(info, entryOffset);
sizeEncoding = updateSizeEncoding(info, entryOffset, entryFlags, sizeEncoding);
if (decodeMethodStart(entryFlags, sizeEncoding) && entryIP != method) {
return -1;
}
if (isDeoptEntryPoint(info, entryOffset, entryFlags, encodedBci)) {
codeInfo.encodedFrameSize = sizeEncoding;
codeInfo.exceptionOffset = loadExceptionOffset(info, entryOffset, entryFlags);
codeInfo.referenceMapIndex = loadReferenceMapIndex(info, entryOffset, entryFlags);
codeInfo.frameInfo = loadFrameInfo(info, entryOffset, entryFlags);
assert codeInfo.frameInfo.isDeoptEntry() && codeInfo.frameInfo.getCaller() == null : "Deoptimization entry must not have inlined frames";
return entryIP;
}
entryIP = advanceIP(info, entryOffset, entryIP);
entryOffset = advanceOffset(entryOffset, entryFlags);
} while (!endOfTable(entryIP));
return -1;
}
static long lookupStackReferenceMapIndex(CodeInfo info, long ip) {
long entryIP = lookupEntryIP(ip);
long entryOffset = loadEntryOffset(info, ip);
do {
int entryFlags = loadEntryFlags(info, entryOffset);
if (entryIP == ip) {
return loadReferenceMapIndex(info, entryOffset, entryFlags);
}
entryIP = advanceIP(info, entryOffset, entryIP);
entryOffset = advanceOffset(entryOffset, entryFlags);
} while (entryIP <= ip);
return CodeInfoQueryResult.NO_REFERENCE_MAP;
}
static long indexGranularity() {
return Options.CodeInfoIndexGranularity.getValue();
}
static long lookupEntryIP(long ip) {
return Long.divideUnsigned(ip, indexGranularity()) * indexGranularity();
}
private static long loadEntryOffset(CodeInfo info, long ip) {
counters().lookupEntryOffsetCount.inc();
long index = Long.divideUnsigned(ip, indexGranularity());
return NonmovableByteArrayReader.getU4(CodeInfoAccess.getCodeInfoIndex(info), index * Integer.BYTES);
}
@AlwaysInline("Make IP-lookup loop call free")
static int loadEntryFlags(CodeInfo info, long curOffset) {
counters().loadEntryFlagsCount.inc();
return NonmovableByteArrayReader.getU1(CodeInfoAccess.getCodeInfoEncodings(info), curOffset);
}
private static final int INVALID_SIZE_ENCODING = 0;
private static int initialSizeEncoding() {
return INVALID_SIZE_ENCODING;
}
@AlwaysInline("Make IP-lookup loop call free")
private static long updateSizeEncoding(CodeInfo info, long entryOffset, int entryFlags, long sizeEncoding) {
switch (extractFS(entryFlags)) {
case FS_NO_CHANGE:
return sizeEncoding;
case FS_SIZE_S1:
return NonmovableByteArrayReader.getS1(CodeInfoAccess.getCodeInfoEncodings(info), offsetFS(entryOffset, entryFlags));
case FS_SIZE_S2:
return NonmovableByteArrayReader.getS2(CodeInfoAccess.getCodeInfoEncodings(info), offsetFS(entryOffset, entryFlags));
case FS_SIZE_S4:
return NonmovableByteArrayReader.getS4(CodeInfoAccess.getCodeInfoEncodings(info), offsetFS(entryOffset, entryFlags));
default:
throw shouldNotReachHere();
}
}
private static long loadExceptionOffset(CodeInfo info, long entryOffset, int entryFlags) {
switch (extractEX(entryFlags)) {
case EX_NO_HANDLER:
return CodeInfoQueryResult.NO_EXCEPTION_OFFSET;
case EX_OFFSET_S1:
return NonmovableByteArrayReader.getS1(CodeInfoAccess.getCodeInfoEncodings(info), offsetEX(entryOffset, entryFlags));
case EX_OFFSET_S2:
return NonmovableByteArrayReader.getS2(CodeInfoAccess.getCodeInfoEncodings(info), offsetEX(entryOffset, entryFlags));
case EX_OFFSET_S4:
return NonmovableByteArrayReader.getS4(CodeInfoAccess.getCodeInfoEncodings(info), offsetEX(entryOffset, entryFlags));
default:
throw shouldNotReachHere();
}
}
private static long loadReferenceMapIndex(CodeInfo info, long entryOffset, int entryFlags) {
switch (extractRM(entryFlags)) {
case RM_NO_MAP:
return CodeInfoQueryResult.NO_REFERENCE_MAP;
case RM_EMPTY_MAP:
return CodeInfoQueryResult.EMPTY_REFERENCE_MAP;
case RM_INDEX_U2:
return NonmovableByteArrayReader.getU2(CodeInfoAccess.getCodeInfoEncodings(info), offsetRM(entryOffset, entryFlags));
case RM_INDEX_U4:
return NonmovableByteArrayReader.getU4(CodeInfoAccess.getCodeInfoEncodings(info), offsetRM(entryOffset, entryFlags));
default:
throw shouldNotReachHere();
}
}
static final int FRAME_SIZE_METHOD_START = 0b001;
static final int FRAME_SIZE_ENTRY_POINT = 0b010;
static final int FRAME_SIZE_HAS_CALLEE_SAVED_REGISTERS = 0b100;
static final int FRAME_SIZE_STATUS_MASK = FRAME_SIZE_METHOD_START | FRAME_SIZE_ENTRY_POINT | FRAME_SIZE_HAS_CALLEE_SAVED_REGISTERS;
@Uninterruptible(reason = "called from uninterruptible code", mayBeInlined = true)
static boolean decodeIsEntryPoint(long sizeEncoding) {
assert sizeEncoding != INVALID_SIZE_ENCODING;
return (sizeEncoding & FRAME_SIZE_ENTRY_POINT) != 0;
}
@Uninterruptible(reason = "called from uninterruptible code", mayBeInlined = true)
static boolean decodeHasCalleeSavedRegisters(long sizeEncoding) {
assert sizeEncoding != INVALID_SIZE_ENCODING;
return (sizeEncoding & FRAME_SIZE_HAS_CALLEE_SAVED_REGISTERS) != 0;
}
@Uninterruptible(reason = "called from uninterruptible code", mayBeInlined = true)
static long decodeTotalFrameSize(long sizeEncoding) {
assert sizeEncoding != INVALID_SIZE_ENCODING;
return sizeEncoding & ~FRAME_SIZE_STATUS_MASK;
}
private static boolean decodeMethodStart(int entryFlags, long sizeEncoding) {
assert sizeEncoding != initialSizeEncoding();
switch (extractFS(entryFlags)) {
case FS_NO_CHANGE:
return false;
case FS_SIZE_S1:
case FS_SIZE_S2:
case FS_SIZE_S4:
return (sizeEncoding & FRAME_SIZE_METHOD_START) != 0;
default:
throw shouldNotReachHere();
}
}
private static boolean isDeoptEntryPoint(CodeInfo info, long entryOffset, int entryFlags, long encodedBci) {
switch (extractFI(entryFlags)) {
case FI_NO_DEOPT:
return false;
case FI_DEOPT_ENTRY_INDEX_S4:
int frameInfoIndex = NonmovableByteArrayReader.getS4(CodeInfoAccess.getCodeInfoEncodings(info), offsetFI(entryOffset, entryFlags));
return FrameInfoDecoder.isFrameInfoMatch(frameInfoIndex, CodeInfoAccess.getFrameInfoEncodings(info), encodedBci);
case FI_INFO_ONLY_INDEX_S4:
return false;
default:
throw shouldNotReachHere();
}
}
static boolean initFrameInfoReader(CodeInfo info, long entryOffset, ReusableTypeReader frameInfoReader) {
int entryFlags = loadEntryFlags(info, entryOffset);
int frameInfoIndex = NonmovableByteArrayReader.getS4(CodeInfoAccess.getCodeInfoEncodings(info), offsetFI(entryOffset, entryFlags));
frameInfoReader.setByteIndex(frameInfoIndex);
frameInfoReader.setData(CodeInfoAccess.getFrameInfoEncodings(info));
return extractFI(entryFlags) != FI_NO_DEOPT;
}
private static FrameInfoQueryResult loadFrameInfo(CodeInfo info, long entryOffset, int entryFlags) {
boolean isDeoptEntry;
switch (extractFI(entryFlags)) {
case FI_NO_DEOPT:
return CodeInfoQueryResult.NO_FRAME_INFO;
case FI_DEOPT_ENTRY_INDEX_S4:
isDeoptEntry = true;
break;
case FI_INFO_ONLY_INDEX_S4:
isDeoptEntry = false;
break;
default:
throw shouldNotReachHere();
}
int frameInfoIndex = NonmovableByteArrayReader.getS4(CodeInfoAccess.getCodeInfoEncodings(info), offsetFI(entryOffset, entryFlags));
return FrameInfoDecoder.decodeFrameInfo(isDeoptEntry, new ReusableTypeReader(CodeInfoAccess.getFrameInfoEncodings(info), frameInfoIndex), info,
FrameInfoDecoder.HeapBasedFrameInfoQueryResultAllocator, FrameInfoDecoder.HeapBasedValueInfoAllocator, true);
}
@AlwaysInline("Make IP-lookup loop call free")
private static long advanceIP(CodeInfo info, long entryOffset, long entryIP) {
int deltaIP = NonmovableByteArrayReader.getU1(CodeInfoAccess.getCodeInfoEncodings(info), offsetIP(entryOffset));
if (deltaIP == DELTA_END_OF_TABLE) {
return Long.MAX_VALUE;
} else {
assert deltaIP > 0;
return entryIP + deltaIP;
}
}
private static boolean endOfTable(long entryIP) {
return entryIP == Long.MAX_VALUE;
}
static final int DELTA_END_OF_TABLE = 0;
static final int FS_BITS = 2;
static final int FS_SHIFT = 0;
static final int FS_MASK_IN_PLACE = ((1 << FS_BITS) - 1) << FS_SHIFT;
static final int FS_NO_CHANGE = 0;
static final int FS_SIZE_S1 = 1;
static final int FS_SIZE_S2 = 2;
static final int FS_SIZE_S4 = 3;
static final int[] FS_MEM_SIZE = {0, Byte.BYTES, Short.BYTES, Integer.BYTES};
static final int EX_BITS = 2;
static final int EX_SHIFT = FS_SHIFT + FS_BITS;
static final int EX_MASK_IN_PLACE = ((1 << EX_BITS) - 1) << EX_SHIFT;
static final int EX_NO_HANDLER = 0;
static final int EX_OFFSET_S1 = 1;
static final int EX_OFFSET_S2 = 2;
static final int EX_OFFSET_S4 = 3;
static final int[] EX_MEM_SIZE = {0, Byte.BYTES, Short.BYTES, Integer.BYTES};
static final int RM_BITS = 2;
static final int RM_SHIFT = EX_SHIFT + EX_BITS;
static final int RM_MASK_IN_PLACE = ((1 << RM_BITS) - 1) << RM_SHIFT;
static final int RM_NO_MAP = 0;
static final int RM_EMPTY_MAP = 1;
static final int RM_INDEX_U2 = 2;
static final int RM_INDEX_U4 = 3;
static final int[] RM_MEM_SIZE = {0, 0, Character.BYTES, Integer.BYTES};
static final int FI_BITS = 2;
static final int FI_SHIFT = RM_SHIFT + RM_BITS;
static final int FI_MASK_IN_PLACE = ((1 << FI_BITS) - 1) << FI_SHIFT;
static final int FI_NO_DEOPT = 0;
static final int FI_DEOPT_ENTRY_INDEX_S4 = 1;
static final int FI_INFO_ONLY_INDEX_S4 = 2;
static final int[] FI_MEM_SIZE = {0, Integer.BYTES, Integer.BYTES, 0};
private static final int TOTAL_BITS = FI_SHIFT + FI_BITS;
private static final byte IP_OFFSET;
private static final byte FS_OFFSET;
private static final byte[] EX_OFFSET;
private static final byte[] RM_OFFSET;
private static final byte[] FI_OFFSET;
private static final byte[] MEM_SIZE;
static {
assert TOTAL_BITS <= Byte.SIZE;
int maxFlag = 1 << TOTAL_BITS;
IP_OFFSET = 1;
FS_OFFSET = 2;
EX_OFFSET = new byte[maxFlag];
RM_OFFSET = new byte[maxFlag];
FI_OFFSET = new byte[maxFlag];
MEM_SIZE = new byte[maxFlag];
for (int i = 0; i < maxFlag; i++) {
EX_OFFSET[i] = TypeConversion.asU1(FS_OFFSET + FS_MEM_SIZE[extractFS(i)]);
RM_OFFSET[i] = TypeConversion.asU1(EX_OFFSET[i] + EX_MEM_SIZE[extractEX(i)]);
FI_OFFSET[i] = TypeConversion.asU1(RM_OFFSET[i] + RM_MEM_SIZE[extractRM(i)]);
MEM_SIZE[i] = TypeConversion.asU1(FI_OFFSET[i] + FI_MEM_SIZE[extractFI(i)]);
}
}
static int (int entryFlags) {
return (entryFlags & FS_MASK_IN_PLACE) >> FS_SHIFT;
}
static int (int entryFlags) {
return (entryFlags & EX_MASK_IN_PLACE) >> EX_SHIFT;
}
static int (int entryFlags) {
return (entryFlags & RM_MASK_IN_PLACE) >> RM_SHIFT;
}
static int (int entryFlags) {
return (entryFlags & FI_MASK_IN_PLACE) >> FI_SHIFT;
}
private static long offsetIP(long entryOffset) {
return entryOffset + IP_OFFSET;
}
private static long offsetFS(long entryOffset, int entryFlags) {
assert extractFS(entryFlags) != FS_NO_CHANGE;
return entryOffset + FS_OFFSET;
}
private static long offsetEX(long entryOffset, int entryFlags) {
assert extractEX(entryFlags) != EX_NO_HANDLER;
return entryOffset + ByteArrayReader.getU1(EX_OFFSET, entryFlags);
}
private static long offsetRM(long entryOffset, int entryFlags) {
assert extractRM(entryFlags) != RM_NO_MAP && extractRM(entryFlags) != RM_EMPTY_MAP;
return entryOffset + ByteArrayReader.getU1(RM_OFFSET, entryFlags);
}
private static long offsetFI(long entryOffset, int entryFlags) {
assert extractFI(entryFlags) != FI_NO_DEOPT;
return entryOffset + ByteArrayReader.getU1(FI_OFFSET, entryFlags);
}
@AlwaysInline("Make IP-lookup loop call free")
private static long advanceOffset(long entryOffset, int entryFlags) {
counters().advanceOffset.inc();
return entryOffset + ByteArrayReader.getU1(MEM_SIZE, entryFlags);
}
@Fold
static CodeInfoDecoderCounters counters() {
return ImageSingletons.lookup(CodeInfoDecoderCounters.class);
}
}
class CodeInfoDecoderCounters {
private final Counter.Group counters = new Counter.Group(CodeInfoTable.Options.CodeCacheCounters, "CodeInfoDecoder");
final Counter lookupEntryOffsetCount = new Counter(counters, "lookupEntryOffset", "");
final Counter loadEntryFlagsCount = new Counter(counters, "loadEntryFlags", "");
final Counter advanceOffset = new Counter(counters, "advanceOffset", "");
}