package com.oracle.svm.core.heap;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import org.graalvm.collections.EconomicMap;
import org.graalvm.collections.Equivalence;
import org.graalvm.compiler.core.common.NumUtil;
import com.oracle.svm.core.FrameAccess;
import com.oracle.svm.core.config.ConfigurationValues;
import jdk.vm.ci.code.ReferenceMap;
import jdk.vm.ci.code.StackSlot;
import jdk.vm.ci.meta.Value;
public class SubstrateReferenceMap extends ReferenceMap implements ReferenceMapEncoder.Input {
private BitSet shiftedOffsets;
private int shift;
private EconomicMap<Integer, Set<Integer>> derived;
private Map<Integer, Object> debugAllUsedRegisters;
private Map<Integer, Object> debugAllUsedStackSlots;
public SubstrateReferenceMap() {
assert ConfigurationValues.getObjectLayout().getReferenceSize() > 2 : "needs to be three bits or more for encoding and validation";
}
public boolean isOffsetMarked(int offset) {
return shiftedOffsets != null && offset + shift >= 0 && shiftedOffsets.get(offset + shift);
}
public void markReferenceAtOffset(int offset, boolean compressed) {
if (shiftedOffsets == null) {
shiftedOffsets = new BitSet();
}
if (offset < -shift) {
int newShift = NumUtil.roundUp(-offset, Long.SIZE);
int shiftDelta = newShift - shift;
assert shiftDelta > 0 && NumUtil.roundUp(shiftDelta, Long.SIZE) == shiftDelta;
long[] oldData = shiftedOffsets.toLongArray();
long[] newData = new long[oldData.length + shiftDelta / Long.SIZE];
System.arraycopy(oldData, 0, newData, shiftDelta / Long.SIZE, oldData.length);
shiftedOffsets = BitSet.valueOf(newData);
shift = newShift;
}
assert isValidToMark(offset, compressed) : "already marked or would overlap with predecessor or successor";
shiftedOffsets.set(offset + shift);
if (compressed) {
shiftedOffsets.set(offset + 1 + shift);
}
}
public void markReferenceAtOffset(int offset, int baseOffset, boolean compressed) {
if (offset == baseOffset) {
if (derived == null || !derived.containsKey(baseOffset)) {
markReferenceAtOffset(baseOffset, compressed);
}
return;
}
if (!isOffsetMarked(baseOffset)) {
markReferenceAtOffset(baseOffset, compressed);
}
if (derived == null) {
derived = EconomicMap.create(Equivalence.DEFAULT);
}
Set<Integer> derivedOffsets = derived.get(baseOffset);
if (derivedOffsets == null) {
derivedOffsets = new HashSet<>();
derived.put(baseOffset, derivedOffsets);
}
assert !derivedOffsets.contains(offset);
derivedOffsets.add(offset);
}
private boolean isValidToMark(int offset, boolean isCompressed) {
int uncompressedSize = FrameAccess.uncompressedReferenceSize();
int compressedSize = ConfigurationValues.getObjectLayout().getReferenceSize();
int previousShiftedOffset = shiftedOffsets.previousSetBit(offset - 1 + shift);
if (previousShiftedOffset != -1) {
int minShiftedOffset = previousShiftedOffset + uncompressedSize;
if (previousShiftedOffset != 0 && shiftedOffsets.get(previousShiftedOffset - 1)) {
previousShiftedOffset--;
minShiftedOffset = previousShiftedOffset + compressedSize;
}
if (offset + shift < minShiftedOffset) {
return false;
}
}
int size = isCompressed ? compressedSize : uncompressedSize;
int nextShiftedOffset = shiftedOffsets.nextSetBit(offset + shift);
return (nextShiftedOffset == -1) || (offset + shift + size <= nextShiftedOffset);
}
public Map<Integer, Object> getDebugAllUsedRegisters() {
return debugAllUsedRegisters;
}
boolean debugMarkRegister(int offset, Value value) {
if (debugAllUsedRegisters == null) {
debugAllUsedRegisters = new HashMap<>();
}
debugAllUsedRegisters.put(offset, value);
return true;
}
public Map<Integer, Object> getDebugAllUsedStackSlots() {
return debugAllUsedStackSlots;
}
boolean debugMarkStackSlot(int offset, StackSlot value) {
if (debugAllUsedStackSlots == null) {
debugAllUsedStackSlots = new HashMap<>();
}
debugAllUsedStackSlots.put(offset, value);
return true;
}
@Override
public boolean isEmpty() {
return shiftedOffsets == null || shiftedOffsets.isEmpty();
}
@Override
public ReferenceMapEncoder.OffsetIterator getOffsets() {
return new ReferenceMapEncoder.OffsetIterator() {
private int nextShiftedOffset = shiftedOffsets == null ? -1 : shiftedOffsets.nextSetBit(0);
@Override
public boolean hasNext() {
return (nextShiftedOffset != -1);
}
@Override
public int nextInt() {
if (!hasNext()) {
throw new NoSuchElementException();
}
int result = nextShiftedOffset - shift;
nextShiftedOffset = shiftedOffsets.nextSetBit(nextShiftedOffset + 2);
return result;
}
@Override
public boolean isNextCompressed() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return shiftedOffsets.get(nextShiftedOffset + 1);
}
@Override
public boolean isNextDerived() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return derived != null && derived.containsKey(nextShiftedOffset - shift);
}
@Override
public Set<Integer> getDerivedOffsets(int baseOffset) {
if (derived == null || !derived.containsKey(baseOffset)) {
throw new NoSuchElementException();
}
return derived.get(baseOffset);
}
};
}
@Override
public int hashCode() {
return shift ^ (shiftedOffsets == null ? 0 : shiftedOffsets.hashCode()) ^ (derived == null ? 0 : derived.hashCode());
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (obj instanceof SubstrateReferenceMap) {
SubstrateReferenceMap other = (SubstrateReferenceMap) obj;
if (shift != other.shift || !Objects.equals(shiftedOffsets, other.shiftedOffsets)) {
return false;
}
if (derived == null || other.derived == null) {
return derived == null && other.derived == null;
}
if (derived.size() != other.derived.size()) {
return false;
}
for (int base : derived.getKeys()) {
if (!derived.get(base).equals(other.derived.get(base))) {
return false;
}
}
return true;
} else {
return false;
}
}
public boolean hasNoDerivedOffsets() {
return derived == null || derived.isEmpty();
}
public void verify() {
if (derived == null) {
return;
}
for (int baseOffset : derived.getKeys()) {
for (int derivedOffset : derived.get(baseOffset)) {
assert !derived.containsKey(derivedOffset);
}
}
}
public StringBuilder dump(StringBuilder builder) {
if (shiftedOffsets == null || shiftedOffsets.isEmpty()) {
builder.append("[]");
return builder;
}
builder.append('[');
shiftedOffsets.stream().forEach(shiftedOffset -> {
int offset = shiftedOffset - shift;
builder.append(offset);
if (derived != null && derived.containsKey(offset)) {
builder.append(" -> {");
for (int derivedOffset : derived.get(offset)) {
builder.append(derivedOffset);
builder.append(", ");
}
builder.replace(builder.length() - 2, builder.length(), "}");
}
builder.append(", ");
});
builder.replace(builder.length() - 2, builder.length(), "]");
return builder;
}
@Override
public String toString() {
return dump(new StringBuilder()).toString();
}
}