/*
* Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.graalvm.compiler.code;
import static jdk.vm.ci.meta.MetaUtil.identityHashCodeString;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Objects;
import java.util.function.BiConsumer;
import org.graalvm.compiler.code.DataSection.Data;
import jdk.vm.ci.code.site.DataSectionReference;
import jdk.vm.ci.meta.SerializableConstant;
import jdk.vm.ci.meta.VMConstant;
public final class DataSection implements Iterable<Data> {
public interface Patches {
void registerPatch(VMConstant c);
}
public abstract static class Data {
private int alignment;
private final int size;
private DataSectionReference ref;
protected Data(int alignment, int size) {
this.alignment = alignment;
this.size = size;
// initialized in DataSection.insertData(Data)
ref = null;
}
protected abstract void emit(ByteBuffer buffer, Patches patches);
public void updateAlignment(int newAlignment) {
if (newAlignment == alignment) {
return;
}
alignment = lcm(alignment, newAlignment);
}
public int getAlignment() {
return alignment;
}
public int getSize() {
return size;
}
@Override
public int hashCode() {
// Data instances should not be used as hash map keys
throw new UnsupportedOperationException("hashCode");
}
@Override
public String toString() {
return identityHashCodeString(this);
}
@Override
public boolean equals(Object obj) {
assert ref != null;
if (obj == this) {
return true;
}
if (obj instanceof Data) {
Data that = (Data) obj;
if (this.alignment == that.alignment && this.size == that.size && this.ref.equals(that.ref)) {
return true;
}
}
return false;
}
}
public static final class RawData extends Data {
private final byte[] data;
public RawData(byte[] data, int alignment) {
super(alignment, data.length);
this.data = data;
}
@Override
protected void emit(ByteBuffer buffer, Patches patches) {
buffer.put(data);
}
}
public static final class SerializableData extends Data {
private final SerializableConstant constant;
public SerializableData(SerializableConstant constant) {
this(constant, 1);
}
public SerializableData(SerializableConstant constant, int alignment) {
super(alignment, constant.getSerializedSize());
this.constant = constant;
}
@Override
protected void emit(ByteBuffer buffer, Patches patches) {
int position = buffer.position();
constant.serialize(buffer);
assert buffer.position() - position == constant.getSerializedSize() : "wrong number of bytes written";
}
}
public static class ZeroData extends Data {
protected ZeroData(int alignment, int size) {
super(alignment, size);
}
public static ZeroData create(int alignment, int size) {
switch (size) {
case 1:
return new ZeroData(alignment, size) {
@Override
protected void emit(ByteBuffer buffer, Patches patches) {
buffer.put((byte) 0);
}
};
case 2:
return new ZeroData(alignment, size) {
@Override
protected void emit(ByteBuffer buffer, Patches patches) {
buffer.putShort((short) 0);
}
};
case 4:
return new ZeroData(alignment, size) {
@Override
protected void emit(ByteBuffer buffer, Patches patches) {
buffer.putInt(0);
}
};
case 8:
return new ZeroData(alignment, size) {
@Override
protected void emit(ByteBuffer buffer, Patches patches) {
buffer.putLong(0);
}
};
default:
return new ZeroData(alignment, size);
}
}
@Override
protected void emit(ByteBuffer buffer, Patches patches) {
int rest = getSize();
while (rest > 8) {
buffer.putLong(0L);
rest -= 8;
}
while (rest > 0) {
buffer.put((byte) 0);
rest--;
}
}
}
public static final class PackedData extends Data {
private final Data[] nested;
private PackedData(int alignment, int size, Data[] nested) {
super(alignment, size);
this.nested = nested;
}
public static PackedData create(Data[] nested) {
int size = 0;
int alignment = 1;
for (int i = 0; i < nested.length; i++) {
assert size % nested[i].getAlignment() == 0 : "invalid alignment in packed constants";
alignment = DataSection.lcm(alignment, nested[i].getAlignment());
size += nested[i].getSize();
}
return new PackedData(alignment, size, nested);
}
@Override
protected void emit(ByteBuffer buffer, Patches patches) {
for (Data data : nested) {
data.emit(buffer, patches);
}
}
}
private final ArrayList<Data> dataItems = new ArrayList<>();
private boolean closed;
private int sectionAlignment;
private int sectionSize;
@Override
public int hashCode() {
// DataSection instances should not be used as hash map keys
throw new UnsupportedOperationException("hashCode");
}
@Override
public String toString() {
return identityHashCodeString(this);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof DataSection) {
DataSection that = (DataSection) obj;
if (this.closed == that.closed && this.sectionAlignment == that.sectionAlignment && this.sectionSize == that.sectionSize && Objects.equals(this.dataItems, that.dataItems)) {
return true;
}
}
return false;
}
Inserts a Data
item into the data section. If the item is already in the data section, the same DataSectionReference
is returned. Params: - data – the
Data
item to be inserted
Returns: a unique DataSectionReference
identifying the Data
item
/**
* Inserts a {@link Data} item into the data section. If the item is already in the data
* section, the same {@link DataSectionReference} is returned.
*
* @param data the {@link Data} item to be inserted
* @return a unique {@link DataSectionReference} identifying the {@link Data} item
*/
public DataSectionReference insertData(Data data) {
checkOpen();
synchronized (data) {
if (data.ref == null) {
data.ref = new DataSectionReference();
dataItems.add(data);
}
return data.ref;
}
}
Transfers all Data
from the provided other DataSection
to this DataSection
, and empties the other section. /**
* Transfers all {@link Data} from the provided other {@link DataSection} to this
* {@link DataSection}, and empties the other section.
*/
public void addAll(DataSection other) {
checkOpen();
other.checkOpen();
for (Data data : other.dataItems) {
assert data.ref != null;
dataItems.add(data);
}
other.dataItems.clear();
}
Determines if this object has been closed
. /**
* Determines if this object has been {@link #close() closed}.
*/
public boolean closed() {
return closed;
}
Computes the layout of the data section and closes this object to further updates.
This must be called exactly once.
/**
* Computes the layout of the data section and closes this object to further updates.
*
* This must be called exactly once.
*/
public void close() {
checkOpen();
closed = true;
// simple heuristic: put items with larger alignment requirement first
dataItems.sort((a, b) -> a.alignment - b.alignment);
int position = 0;
int alignment = 1;
for (Data d : dataItems) {
alignment = lcm(alignment, d.alignment);
position = align(position, d.alignment);
d.ref.setOffset(position);
position += d.size;
}
sectionAlignment = alignment;
sectionSize = position;
}
Gets the size of the data section. This must only be called once this object has been closed. /**
* Gets the size of the data section.
*
* This must only be called once this object has been {@linkplain #closed() closed}.
*/
public int getSectionSize() {
checkClosed();
return sectionSize;
}
Gets the minimum alignment requirement of the data section. This must only be called once this object has been closed. /**
* Gets the minimum alignment requirement of the data section.
*
* This must only be called once this object has been {@linkplain #closed() closed}.
*/
public int getSectionAlignment() {
checkClosed();
return sectionAlignment;
}
Builds the data section into a given buffer. This must only be called once this object has been closed. Params: - buffer – the
ByteBuffer
where the data section should be built. The buffer must hold at least getSectionSize()
bytes. - patch – a
Patches
instance to receive constants
for relocations in the data section
/**
* Builds the data section into a given buffer.
*
* This must only be called once this object has been {@linkplain #closed() closed}.
*
* @param buffer the {@link ByteBuffer} where the data section should be built. The buffer must
* hold at least {@link #getSectionSize()} bytes.
* @param patch a {@link Patches} instance to receive {@link VMConstant constants} for
* relocations in the data section
*/
public void buildDataSection(ByteBuffer buffer, Patches patch) {
buildDataSection(buffer, patch, (r, s) -> {
});
}
Builds the data section into a given buffer. This must only be called once this object has been closed. When this method returns, the buffers' position is just after the last data item. Params: - buffer – the
ByteBuffer
where the data section should be built. The buffer must hold at least getSectionSize()
bytes. - patch – a
Patches
instance to receive constants
for - onEmit – a function that is called before emitting each data item with the
DataSectionReference
and the size of the data.
/**
* Builds the data section into a given buffer.
*
* This must only be called once this object has been {@linkplain #closed() closed}. When this
* method returns, the buffers' position is just after the last data item.
*
* @param buffer the {@link ByteBuffer} where the data section should be built. The buffer must
* hold at least {@link #getSectionSize()} bytes.
* @param patch a {@link Patches} instance to receive {@link VMConstant constants} for
* @param onEmit a function that is called before emitting each data item with the
* {@link DataSectionReference} and the size of the data.
*/
public void buildDataSection(ByteBuffer buffer, Patches patch, BiConsumer<DataSectionReference, Integer> onEmit) {
checkClosed();
assert buffer.remaining() >= sectionSize;
int start = buffer.position();
for (Data d : dataItems) {
buffer.position(start + d.ref.getOffset());
onEmit.accept(d.ref, d.getSize());
d.emit(buffer, patch);
}
buffer.position(start + sectionSize);
}
public Data findData(DataSectionReference ref) {
for (Data d : dataItems) {
if (d.ref == ref) {
return d;
}
}
return null;
}
public static void emit(ByteBuffer buffer, Data data, Patches patch) {
data.emit(buffer, patch);
}
@Override
public Iterator<Data> iterator() {
return dataItems.iterator();
}
private static int lcm(int x, int y) {
if (x == 0) {
return y;
} else if (y == 0) {
return x;
}
int a = Math.max(x, y);
int b = Math.min(x, y);
while (b > 0) {
int tmp = a % b;
a = b;
b = tmp;
}
int gcd = a;
return x * y / gcd;
}
private static int align(int position, int alignment) {
return ((position + alignment - 1) / alignment) * alignment;
}
private void checkClosed() {
if (!closed) {
throw new IllegalStateException();
}
}
private void checkOpen() {
if (closed) {
throw new IllegalStateException();
}
}
public void clear() {
checkOpen();
this.dataItems.clear();
this.sectionAlignment = 0;
this.sectionSize = 0;
}
}