package com.oracle.svm.hosted.image;
import static com.oracle.svm.core.util.VMError.shouldNotReachHere;
import java.lang.reflect.Array;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import org.graalvm.compiler.core.common.CompressEncoding;
import org.graalvm.compiler.core.common.NumUtil;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.debug.Indent;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.c.function.CFunctionPointer;
import org.graalvm.nativeimage.c.function.RelocatedPointer;
import org.graalvm.word.WordBase;
import com.oracle.graal.pointsto.util.AnalysisError;
import com.oracle.objectfile.ObjectFile;
import com.oracle.svm.core.FrameAccess;
import com.oracle.svm.core.StaticFieldsSupport;
import com.oracle.svm.core.config.ConfigurationValues;
import com.oracle.svm.core.config.ObjectLayout;
import com.oracle.svm.core.heap.Heap;
import com.oracle.svm.core.heap.ObjectHeader;
import com.oracle.svm.core.hub.DynamicHub;
import com.oracle.svm.core.image.ImageHeapLayoutInfo;
import com.oracle.svm.core.meta.SubstrateObjectConstant;
import com.oracle.svm.hosted.config.HybridLayout;
import com.oracle.svm.hosted.image.NativeImageHeap.ObjectInfo;
import com.oracle.svm.hosted.meta.HostedClass;
import com.oracle.svm.hosted.meta.HostedField;
import com.oracle.svm.hosted.meta.HostedMethod;
import com.oracle.svm.hosted.meta.MaterializedConstantFields;
import com.oracle.svm.hosted.meta.MethodPointer;
import jdk.vm.ci.meta.JavaConstant;
import jdk.vm.ci.meta.JavaKind;
import jdk.vm.ci.meta.ResolvedJavaMethod;
public final class NativeImageHeapWriter {
private final NativeImageHeap heap;
private final ImageHeapLayoutInfo heapLayout;
private long sectionOffsetOfARelocatablePointer;
public NativeImageHeapWriter(NativeImageHeap heap, ImageHeapLayoutInfo heapLayout) {
this.heap = heap;
this.heapLayout = heapLayout;
this.sectionOffsetOfARelocatablePointer = -1;
}
@SuppressWarnings("try")
public long writeHeap(DebugContext debug, RelocatableBuffer buffer) {
try (Indent perHeapIndent = debug.logAndIndent("BootImageHeap.writeHeap:")) {
for (ObjectInfo info : heap.getObjects()) {
assert !heap.isBlacklisted(info.getObject());
writeObject(info, buffer);
}
writeStaticFields(buffer);
heap.getLayouter().writeMetadata(buffer.getByteBuffer(), 0);
}
return sectionOffsetOfARelocatablePointer;
}
private void writeStaticFields(RelocatableBuffer buffer) {
ObjectInfo primitiveFields = heap.getObjectInfo(StaticFieldsSupport.getStaticPrimitiveFields());
ObjectInfo objectFields = heap.getObjectInfo(StaticFieldsSupport.getStaticObjectFields());
for (HostedField field : heap.getUniverse().getFields()) {
if (Modifier.isStatic(field.getModifiers()) && field.hasLocation()) {
assert field.isWritten() || MaterializedConstantFields.singleton().contains(field.wrapped);
ObjectInfo fields = (field.getStorageKind() == JavaKind.Object) ? objectFields : primitiveFields;
writeField(buffer, fields, field, null, null);
}
}
}
private static Object readObjectField(HostedField field, JavaConstant receiver) {
return SubstrateObjectConstant.asObject(field.readStorageValue(receiver));
}
private int referenceSize() {
return heap.getObjectLayout().getReferenceSize();
}
private void mustBeReferenceAligned(int index) {
assert (index % heap.getObjectLayout().getReferenceSize() == 0) : "index " + index + " must be reference-aligned.";
}
private static void verifyTargetDidNotChange(Object target, Object reason, Object targetInfo) {
if (targetInfo == null) {
throw NativeImageHeap.reportIllegalType(target, reason);
}
}
private void writeField(RelocatableBuffer buffer, ObjectInfo fields, HostedField field, JavaConstant receiver, ObjectInfo info) {
int index = fields.getIndexInBuffer(field.getLocation());
JavaConstant value;
try {
value = field.readValue(receiver);
} catch (AnalysisError.TypeNotFoundError ex) {
throw NativeImageHeap.reportIllegalType(ex.getType(), info);
}
if (value.getJavaKind() == JavaKind.Object && SubstrateObjectConstant.asObject(value) instanceof RelocatedPointer) {
addNonDataRelocation(buffer, index, (RelocatedPointer) SubstrateObjectConstant.asObject(value));
} else {
write(buffer, index, value, info != null ? info : field);
}
}
private void write(RelocatableBuffer buffer, int index, JavaConstant con, Object reason) {
if (con.getJavaKind() == JavaKind.Object) {
writeReference(buffer, index, SubstrateObjectConstant.asObject(con), reason);
} else {
writePrimitive(buffer, index, con);
}
}
void writeReference(RelocatableBuffer buffer, int index, Object target, Object reason) {
assert !(target instanceof WordBase) : "word values are not references";
mustBeReferenceAligned(index);
if (target != null) {
ObjectInfo targetInfo = heap.getObjectInfo(target);
verifyTargetDidNotChange(target, reason, targetInfo);
if (NativeImageHeap.useHeapBase()) {
CompressEncoding compressEncoding = ImageSingletons.lookup(CompressEncoding.class);
int shift = compressEncoding.getShift();
writeReferenceValue(buffer, index, targetInfo.getAddress() >>> shift);
} else {
addDirectRelocationWithoutAddend(buffer, index, referenceSize(), target);
}
}
}
private void writeConstant(RelocatableBuffer buffer, int index, JavaKind kind, Object value, ObjectInfo info) {
if (value instanceof RelocatedPointer) {
addNonDataRelocation(buffer, index, (RelocatedPointer) value);
return;
}
final JavaConstant con;
if (value instanceof WordBase) {
con = JavaConstant.forIntegerKind(FrameAccess.getWordKind(), ((WordBase) value).rawValue());
} else if (value == null && kind == FrameAccess.getWordKind()) {
con = JavaConstant.forIntegerKind(FrameAccess.getWordKind(), 0);
} else {
assert kind == JavaKind.Object || value != null : "primitive value must not be null";
con = SubstrateObjectConstant.forBoxedValue(kind, value);
}
write(buffer, index, con, info);
}
private void (RelocatableBuffer buffer, int index, ObjectInfo obj) {
mustBeReferenceAligned(index);
DynamicHub hub = obj.getClazz().getHub();
assert hub != null : "Null DynamicHub found during native image generation.";
ObjectInfo hubInfo = heap.getObjectInfo(hub);
assert hubInfo != null : "Unknown object " + hub.toString() + " found. Static field or an object referenced from a static field changed during native image generation?";
ObjectHeader objectHeader = Heap.getHeap().getObjectHeader();
if (NativeImageHeap.useHeapBase()) {
long targetOffset = hubInfo.getAddress();
long headerBits = objectHeader.encodeAsImageHeapObjectHeader(obj, targetOffset);
writeReferenceValue(buffer, index, headerBits);
} else {
long headerBits = objectHeader.encodeAsImageHeapObjectHeader(obj, 0L);
addDirectRelocationWithAddend(buffer, index, hub, headerBits);
}
}
private void addDirectRelocationWithoutAddend(RelocatableBuffer buffer, int index, int size, Object target) {
assert size == 4 || size == 8;
assert !NativeImageHeap.spawnIsolates() || heapLayout.isReadOnlyRelocatable(index);
buffer.addRelocationWithoutAddend(index, size == 8 ? ObjectFile.RelocationKind.DIRECT_8 : ObjectFile.RelocationKind.DIRECT_4, target);
if (sectionOffsetOfARelocatablePointer == -1) {
sectionOffsetOfARelocatablePointer = index;
}
}
private void addDirectRelocationWithAddend(RelocatableBuffer buffer, int index, DynamicHub target, long objectHeaderBits) {
assert !NativeImageHeap.spawnIsolates() || heapLayout.isReadOnlyRelocatable(index);
buffer.addRelocationWithAddend(index, referenceSize() == 8 ? ObjectFile.RelocationKind.DIRECT_8 : ObjectFile.RelocationKind.DIRECT_4, objectHeaderBits, target);
if (sectionOffsetOfARelocatablePointer == -1) {
sectionOffsetOfARelocatablePointer = index;
}
}
private void addNonDataRelocation(RelocatableBuffer buffer, int index, RelocatedPointer pointer) {
mustBeReferenceAligned(index);
assert pointer instanceof CFunctionPointer : "unknown relocated pointer " + pointer;
assert pointer instanceof MethodPointer : "cannot create relocation for unknown FunctionPointer " + pointer;
ResolvedJavaMethod method = ((MethodPointer) pointer).getMethod();
HostedMethod hMethod = method instanceof HostedMethod ? (HostedMethod) method : heap.getUniverse().lookup(method);
if (hMethod.isCompiled()) {
int pointerSize = ConfigurationValues.getTarget().wordSize;
addDirectRelocationWithoutAddend(buffer, index, pointerSize, pointer);
}
}
private static void writePrimitive(RelocatableBuffer buffer, int index, JavaConstant con) {
ByteBuffer bb = buffer.getByteBuffer();
switch (con.getJavaKind()) {
case Boolean:
bb.put(index, (byte) con.asInt());
break;
case Byte:
bb.put(index, (byte) con.asInt());
break;
case Char:
bb.putChar(index, (char) con.asInt());
break;
case Short:
bb.putShort(index, (short) con.asInt());
break;
case Int:
bb.putInt(index, con.asInt());
break;
case Long:
bb.putLong(index, con.asLong());
break;
case Float:
bb.putFloat(index, con.asFloat());
break;
case Double:
bb.putDouble(index, con.asDouble());
break;
default:
throw shouldNotReachHere(con.getJavaKind().toString());
}
}
private void writeReferenceValue(RelocatableBuffer buffer, int index, long value) {
if (referenceSize() == Long.BYTES) {
buffer.getByteBuffer().putLong(index, value);
} else if (referenceSize() == Integer.BYTES) {
buffer.getByteBuffer().putInt(index, NumUtil.safeToInt(value));
} else {
throw shouldNotReachHere("Unsupported reference size: " + referenceSize());
}
}
private void writeObject(ObjectInfo info, RelocatableBuffer buffer) {
ObjectLayout objectLayout = heap.getObjectLayout();
final int indexInBuffer = info.getIndexInBuffer(objectLayout.getHubOffset());
assert objectLayout.isAligned(indexInBuffer);
writeObjectHeader(buffer, indexInBuffer, info);
ByteBuffer bufferBytes = buffer.getByteBuffer();
HostedClass clazz = info.getClazz();
if (clazz.isInstanceClass()) {
JavaConstant con = SubstrateObjectConstant.forObject(info.getObject());
HybridLayout<?> hybridLayout = heap.getHybridLayout(clazz);
HostedField hybridArrayField = null;
HostedField hybridTypeIDSlotsField = null;
int maxBitIndex = -1;
int maxTypeIDSlotIndex = -1;
Object hybridArray = null;
if (hybridLayout != null) {
hybridArrayField = hybridLayout.getArrayField();
hybridArray = readObjectField(hybridArrayField, con);
hybridTypeIDSlotsField = hybridLayout.getTypeIDSlotsField();
if (hybridTypeIDSlotsField != null) {
short[] typeIDSlots = (short[]) readObjectField(hybridTypeIDSlotsField, con);
if (typeIDSlots != null) {
int length = typeIDSlots.length;
for (int i = 0; i < length; i++) {
final int index = info.getIndexInBuffer(HybridLayout.getTypeIDSlotsFieldOffset(objectLayout)) + (i * 2);
if (index + 1 > maxTypeIDSlotIndex) {
maxTypeIDSlotIndex = index + 1;
}
short value = typeIDSlots[i];
bufferBytes.putShort(index, value);
}
}
}
}
for (HostedField field : clazz.getInstanceFields(true)) {
if (!field.equals(hybridArrayField) &&
!field.equals(hybridTypeIDSlotsField) &&
field.isInImageHeap()) {
assert field.getLocation() >= 0;
assert info.getIndexInBuffer(field.getLocation()) > maxBitIndex;
assert info.getIndexInBuffer(field.getLocation()) > maxTypeIDSlotIndex;
writeField(buffer, info, field, con, info);
}
}
bufferBytes.putInt(info.getIndexInBuffer(objectLayout.getIdentityHashCodeOffset()), info.getIdentityHashCode());
if (hybridArray != null) {
int length = Array.getLength(hybridArray);
bufferBytes.putInt(info.getIndexInBuffer(objectLayout.getArrayLengthOffset()), length);
for (int i = 0; i < length; i++) {
final int elementIndex = info.getIndexInBuffer(hybridLayout.getArrayElementOffset(i));
final JavaKind elementStorageKind = hybridLayout.getArrayElementStorageKind();
final Object array = Array.get(hybridArray, i);
writeConstant(buffer, elementIndex, elementStorageKind, array, info);
}
}
} else if (clazz.isArray()) {
JavaKind kind = clazz.getComponentType().getStorageKind();
Object array = info.getObject();
int length = Array.getLength(array);
bufferBytes.putInt(info.getIndexInBuffer(objectLayout.getArrayLengthOffset()), length);
bufferBytes.putInt(info.getIndexInBuffer(objectLayout.getIdentityHashCodeOffset()), info.getIdentityHashCode());
if (array instanceof Object[]) {
Object[] oarray = (Object[]) array;
assert oarray.length == length;
for (int i = 0; i < length; i++) {
final int elementIndex = info.getIndexInBuffer(objectLayout.getArrayElementOffset(kind, i));
Object element;
try {
element = heap.getAnalysisUniverse().replaceObject(oarray[i]);
} catch (AnalysisError.TypeNotFoundError ex) {
throw NativeImageHeap.reportIllegalType(ex.getType(), info);
}
assert (oarray[i] instanceof RelocatedPointer) == (element instanceof RelocatedPointer);
writeConstant(buffer, elementIndex, kind, element, info);
}
} else {
for (int i = 0; i < length; i++) {
final int elementIndex = info.getIndexInBuffer(objectLayout.getArrayElementOffset(kind, i));
final Object element = Array.get(array, i);
writeConstant(buffer, elementIndex, kind, element, info);
}
}
} else {
throw shouldNotReachHere();
}
}
}