slcom/slangc/api/BytecodeHeap.sauce

1647 lines
46 KiB
Plaintext
Raw Normal View History

package slangc.api;
import slang.data.Map;
import slangc.bytecode.FieldSignature;
import slangc.bytecode.MethodSignature;
import slangc.bytecode.TypeSignature;
import slangc.sdk.SimpleBytecodeOutput;
public class BytecodeHeap {
private Record[] records = new Record[0];
private int numberFree;
private final int maximumRecords;
private Map<String, ConstString> constStringMap = new Map<String, ConstString>(99999);
private Map<String, ObjectLike> constStringUnprocessedMap = new Map<String, ObjectLike>();
private Map<String, ObjectLike> packageMap = new Map<String, ObjectLike>(99);
private Map<TypeSignature, ObjectLike> typeSignatureMap = new Map<TypeSignature, ObjectLike>(999);
private Map<FieldSignature, ObjectLike> fieldSignatureMap = new Map<FieldSignature, ObjectLike>(9999);
private Map<MethodSignature, ObjectLike> methodSignatureMap = new Map<MethodSignature, ObjectLike>(12345);
private Map<String, ObjectLike> constStringInt32Map = new Map<String, ObjectLike>();
private Map<String, ObjectLike> constStringInt64Map = new Map<String, ObjectLike>();
private Map<String, ObjectLike> constStringUint32Map = new Map<String, ObjectLike>();
private Map<String, ObjectLike> constStringUint64Map = new Map<String, ObjectLike>();
private Map<String, ObjectLike> constStringFloat32Map = new Map<String, ObjectLike>();
private Map<String, ObjectLike> constStringFloat64Map = new Map<String, ObjectLike>();
public enum RecordType {
INVALID,
STRING_UTF8,
ARRAY_INT8,
ARRAY_INT16,
ARRAY_INT32,
ARRAY_INT64,
ARRAY_FLOAT32,
ARRAY_FLOAT64,
OBJECT_LIKE,
CONST_INT32,
CONST_INT64,
CONST_FLOAT32,
CONST_FLOAT64
}
/** This is used to specify the "class" of an encoded OBJECT_LIKE typeName */
public enum StandardClass {
NULL,
FALSE,
TRUE,
SIMPLEARRAY,
SIMPLETYPEREF,
ARRAYTYPEREF, // TODO...
SPECIALTYPEREF, // TODO...
STATICFIELDREF,
STATICMETHODREF,
INSTANCEFIELDREF,
INSTANCEMETHODREF,
INTERFACEMETHODREF, // TODO...
/** Used to defer number-processing to a secondary stage. This may allow future versions of the
* compiler to work on platforms that don't have full number stack such as JavaScript, as well as
* being a reference encoding for future e.g. bigint/bigdecimal types) */
STRING_INT32,
STRING_INT64,
STRING_FLOAT32,
STRING_FLOAT64,
STRING_UTF8, // Processed UTF-8 string data
STRING_UNPROCESSED, // A string with arbitrary \escape sequences encoded as written in source
CONST_INT32,
CONST_INT64,
CONST_FLOAT32,
CONST_FLOAT64,
ARRAY_INT8,
ARRAY_INT16,
ARRAY_INT32,
ARRAY_INT64,
ARRAY_FLOAT32,
ARRAY_FLOAT64,
ARRAY_BYTECODE, // TODO...
ARRAY_DEBUG16, // TODO...
ARRAY_LABELS, // TODO...
PACKAGE,
TYPE,
STATIC_FIELD,
INSTANCE_FIELD,
STATIC_METHOD,
INSTANCE_METHOD,
INTERFACE_METHOD,
FILE,
FILE_BYTES,
DEPENDS,
PROVIDES,
ENTRYPOINT,
RUNTIMEDATA,
METADATA,
CONSTRUCTORMETHODREF,
STATICINITMETHODREF,
INSTANCE_CONSTRUCTOR,
STATIC_INIT,
STRING_UINT32,
STRING_UINT64
}
public static abstract class Record {
public final BytecodeHeap heap;
public final int recordIndex;
public Record(BytecodeHeap heap, int recordIndex) {
super();
this.heap = heap;
this.recordIndex = recordIndex;
heap.assignRecord(this);
}
public boolean usingCompressedFormat() {
return false;
}
public BytecodeHeap getHeap() {
return heap;
}
public int getRecordIndex() {
return recordIndex;
}
public abstract RecordType getRecordType();
public abstract StandardClass getStandardClass();
public void writeArrayPart(BytecodeOutput o) {
/* No array part by default. */
}
public int elementsLength() {
return 0;
}
private int cachedSize = -1;
public void clearCachedSize() {
cachedSize = -1;
}
public int getArraySizeInBytes() {
if (cachedSize >= 0) {
return cachedSize;
}
if (isEmbeddedData()) {
return 0;
}
/*if (elementsLength() == 0) {
return 0;
}*/
BytecodeOutput o = new SimpleBytecodeOutput();
writeArrayPart(o);
int result = o.getCount();
o.endOfFile();
cachedSize = result;
return result;
}
public void writeObjectHead(BytecodeOutput output) {
output.write8((byte) getRecordType().value);
output.write8((byte) getStandardClass().value);
output.write8((byte) 0);
output.write8((byte) 0);
output.write32(0);
}
public boolean isEmbeddedData() {
return false;
}
protected void innerWriteEmbeddedData(BytecodeOutput output) {
throw new Error("innerWriteEmbeddedData should be overridden for types with embedded data");
}
public final void writeEmbeddedData(BytecodeOutput output) {
if (!isEmbeddedData()) {
throw new Error("This record doesn't have embedded data (a pointer should be written instead)");
}
int countBefore = output.getCount();
innerWriteEmbeddedData(output);
/* Automatically pad if necessary. */
while (output.getCount() < countBefore + 8) {
output.write8((byte)0);
}
if (output.getCount() != countBefore + 8) {
throw new Error("Embedded data was too large! Expected 8 bytes but got " + (output.getCount() - countBefore));
}
}
public int getTableEntrySize(int compressionLevel) {
switch (compressionLevel) {
case 0:
return 16;
case 1:
return 8;
case 2:
return 4;
case 3:
if (getArraySizeInBytes() < 256) {
return 2;
} else {
return 4;
}
default:
throw new Error("Invalid compression level: " + compressionLevel + " (expecting 0-3)");
}
}
public String innerDebugString() {
return "TODO";
}
public String debugString() {
return getRecordType().name() + "#" + recordIndex + "(" + innerDebugString() + ")";
}
}
public static class ConstString extends Record {
private final String value;
public ConstString(BytecodeHeap heap, int recordIndex, String value) {
super(heap, recordIndex);
if (value == null) {
throw new Error("Bad string (got null)");
}
this.value = value;
}
public String getStringValue() {
return value;
}
public String innerDebugString() {
return "\"" + getStringValue() + "\"";
}
@Override
public RecordType getRecordType() {
return RecordType.STRING_UTF8;
}
@Override
public StandardClass getStandardClass() {
return StandardClass.STRING_UTF8;
}
@Override
public void writeArrayPart(BytecodeOutput output) {
try {
byte[] bytes = getStringValue().signedBytes();
for (int i = 0; i < bytes.length; i++) {
output.write8(bytes[i]);
}
} catch (Error e) {
throw new Error("Unable to encode string as UTF-8", e);
}
}
}
public static class ConstInt32 extends Record {
private final int value;
public ConstInt32(BytecodeHeap heap, int recordIndex, int value) {
super(heap, recordIndex);
this.value = value;
}
public int getValue() {
return value;
}
@Override
public RecordType getRecordType() {
return RecordType.CONST_INT32;
}
@Override
public StandardClass getStandardClass() {
return StandardClass.CONST_INT32;
}
}
public static class ConstInt64 extends Record {
private final long value;
public ConstInt64(BytecodeHeap heap, int recordIndex, long value) {
super(heap, recordIndex);
this.value = value;
}
public long getValue() {
return value;
}
@Override
public RecordType getRecordType() {
return RecordType.CONST_INT64;
}
@Override
public StandardClass getStandardClass() {
return StandardClass.CONST_INT64;
}
}
public static class ConstFloat32 extends Record {
private final float value;
public ConstFloat32(BytecodeHeap heap, int recordIndex, float value) {
super(heap, recordIndex);
this.value = value;
}
public float getValue() {
return value;
}
@Override
public RecordType getRecordType() {
return RecordType.CONST_FLOAT32;
}
@Override
public StandardClass getStandardClass() {
return StandardClass.CONST_FLOAT32;
}
}
public static class ConstFloat64 extends Record {
private final double value;
public ConstFloat64(BytecodeHeap heap, int recordIndex, double value) {
super(heap, recordIndex);
this.value = value;
}
public double getValue() {
return value;
}
@Override
public RecordType getRecordType() {
return RecordType.CONST_FLOAT64;
}
@Override
public StandardClass getStandardClass() {
return StandardClass.CONST_FLOAT64;
}
}
public static class ObjectLike extends Record {
private final StandardClass standardClass;
private Record[] elements;
public ObjectLike(BytecodeHeap heap, int recordIndex, StandardClass standardClass, int size) {
super(heap, recordIndex);
this.standardClass = standardClass;
elements = new Record[size];
for (int i = 0; i < size; i++) {
elements[i] = heap.getNull();
}
}
public String innerDebugString() {
String result = "";
for (int i = 0; i < elements.length; i++) {
if (i > 0) {
result = result + ",";
}
result = result+elements[i].debugString();
}
return result;
}
@Override
public boolean usingCompressedFormat() {
for (int i = 0; i < elements.length; i++) {
if (elements[i].getRecordIndex() >= 65536) {
return false;
}
}
return true;
}
public void resizeElements(int newSize) {
Record[] newElements = new Record[newSize];
for (int i = 0; i < newElements.length; i++) {
if (i < elements.length) {
newElements[i] = elements[i];
} else {
newElements[i] = getHeap().getNull();
}
}
elements = newElements;
}
public void appendElement(Record r) {
int idx = elementsLength();
resizeElements(idx + 1);
setElement(idx, r);
}
public StandardClass getStandardClass() {
return standardClass;
}
@Override
public int elementsLength() {
return elements.length;
}
public Record getElement(int i) {
return elements[i];
}
public void setElement(int i, Record r) {
clearCachedSize();
elements[i] = r;
}
@Override
public RecordType getRecordType() {
return RecordType.OBJECT_LIKE;
}
@Override
public void writeArrayPart(BytecodeOutput output) {
if (usingCompressedFormat()) {
for (int i = 0; i < elements.length; i++) {
output.write16((short) elements[i].getRecordIndex());
}
} else {
for (int i = 0; i < elements.length; i++) {
output.write32(elements[i].getRecordIndex());
}
}
}
}
public static class ArrayInt8 extends Record {
private final StandardClass standardClass;
private byte[] elements;
public ArrayInt8(BytecodeHeap heap, int recordIndex, StandardClass standardClass, int size) {
super(heap, recordIndex);
this.standardClass = standardClass;
elements = new byte[size];
}
@Override
public int elementsLength() {
return elements.length;
}
public byte getElement(int i) {
return elements[i];
}
public void setElement(int i, byte r) {
clearCachedSize();
elements[i] = r;
}
public void resizeElements(int newSize) {
byte[] newElements = new byte[newSize];
for (int i = 0; i < newElements.length; i++) {
if (i < elements.length) {
newElements[i] = elements[i];
} else {
//newElements[i] = 0;
}
}
elements = newElements;
}
public void appendElement(byte r) {
int idx = elementsLength();
resizeElements(idx + 1);
setElement(idx, r);
}
@Override
public RecordType getRecordType() {
return RecordType.ARRAY_INT8;
}
@Override
public StandardClass getStandardClass() {
return standardClass;
}
@Override
public void writeArrayPart(BytecodeOutput output) {
boolean cmp = usingCompressedFormat();
for (int i = 0; i < elements.length; i++) {
output.write8(elements[i]);
}
}
@Override
public boolean usingCompressedFormat() {
return false;
}
}
public static class ArrayInt16 extends Record {
private final StandardClass standardClass;
private short[] elements;
public ArrayInt16(BytecodeHeap heap, int recordIndex, StandardClass standardClass, int size) {
super(heap, recordIndex);
this.standardClass = standardClass;
elements = new short[size];
}
@Override
public int elementsLength() {
return elements.length;
}
public short getElement(int i) {
return elements[i];
}
public void setElement(int i, short r) {
clearCachedSize();
elements[i] = r;
}
public void resizeElements(int newSize) {
short[] newElements = new short[newSize];
for (int i = 0; i < newElements.length; i++) {
if (i < elements.length) {
newElements[i] = elements[i];
} else {
//newElements[i] = 0;
}
}
elements = newElements;
}
public void appendElement(short r) {
int idx = elementsLength();
resizeElements(idx + 1);
setElement(idx, r);
}
@Override
public RecordType getRecordType() {
return RecordType.ARRAY_INT16;
}
@Override
public StandardClass getStandardClass() {
return standardClass;
}
@Override
public void writeArrayPart(BytecodeOutput output) {
boolean cmp = usingCompressedFormat();
for (int i = 0; i < elements.length; i++) {
if (cmp) {
output.write8((byte)elements[i]);
} else {
output.write16(elements[i]);
}
}
}
@Override
public boolean usingCompressedFormat() {
for (int i = 0; i < elements.length; i++) {
byte tmp = (byte) elements[i];
if ((((short) tmp) & 0xFF) != elements[i]) {
return false;
}
}
return true;
}
}
public static class ArrayInt32 extends Record {
private final StandardClass standardClass;
private int[] elements;
public ArrayInt32(BytecodeHeap heap, int recordIndex, StandardClass standardClass, int size) {
super(heap, recordIndex);
this.standardClass = standardClass;
elements = new int[size];
}
@Override
public int elementsLength() {
return elements.length;
}
public int getElement(int i) {
return elements[i];
}
public void setElement(int i, int r) {
//if ((r & 0xFFFF) != r) throw new Error("XXX temporary error: Can't compress " + r);
clearCachedSize();
elements[i] = r;
}
public void resizeElements(int newSize) {
int[] newElements = new int[newSize];
for (int i = 0; i < newElements.length; i++) {
if (i < elements.length) {
newElements[i] = elements[i];
} else {
//newElements[i] = 0;
}
}
elements = newElements;
}
public void appendElement(int r) {
int idx = elementsLength();
resizeElements(idx + 1);
setElement(idx, r);
}
@Override
public RecordType getRecordType() {
return RecordType.ARRAY_INT32;
}
@Override
public StandardClass getStandardClass() {
return standardClass;
}
@Override
public void writeArrayPart(BytecodeOutput output) {
boolean cmp = usingCompressedFormat();
for (int i = 0; i < elements.length; i++) {
if (cmp) {
output.write16((short)elements[i]);
} else {
output.write32(elements[i]);
}
}
}
@Override
public boolean usingCompressedFormat() {
for (int i = 0; i < elements.length; i++) {
int tmp = elements[i];
if ((tmp & 0xFFFF) != elements[i]) {
return false;
}
}
return true;
}
}
public BytecodeHeap(int maximumRecords, boolean initialise) {
this.maximumRecords = maximumRecords;
if (initialise) {
initialiseHeap();
}
}
public void initialiseHeap() {
int nullId = nextFreeRecordIndex();
if (nullId != 0) {
throw new Error("Can't initialise heap, null id already seems to exist!");
}
if (newObjectLike(StandardClass.NULL, 0).getRecordIndex() != 0) {
throw new Error("Failed to create initial records");
}
if (newObjectLike(StandardClass.FALSE, 0).getRecordIndex() != 1) {
throw new Error("Failed to create initial records");
}
if (newObjectLike(StandardClass.TRUE, 0).getRecordIndex() != 2) {
throw new Error("Failed to create initial records");
}
if (newObjectLike(StandardClass.SIMPLEARRAY, 0).getRecordIndex() != 3) { // Package array
throw new Error("Failed to create initial records");
}
if (newObjectLike(StandardClass.SIMPLEARRAY, 0).getRecordIndex() != 4) { // Provides/depends array
throw new Error("Failed to create initial records");
}
if (newObjectLike(StandardClass.SIMPLEARRAY, 0).getRecordIndex() != 5) { // Entrypoints array
throw new Error("Failed to create initial records");
}
if (newObjectLike(StandardClass.SIMPLEARRAY, 32).getRecordIndex() != 6) { // Runtimedata array
throw new Error("Failed to create initial records");
}
if (newObjectLike(StandardClass.SIMPLEARRAY, 0).getRecordIndex() != 7) { // Important metadata array
throw new Error("Failed to create initial records");
}
if (newObjectLike(StandardClass.SIMPLEARRAY, 0).getRecordIndex() != 8) { // Other metadata array
throw new Error("Failed to create initial records");
}
}
public ObjectLike getNull() {
ObjectLike result = (ObjectLike)records[0];
if (result == null) {
throw new Error("Invalid null record");
}
return result;
}
public ObjectLike getFalse() {
ObjectLike result = (ObjectLike)records[1];
if (result == null) {
throw new Error("Invalid false record");
}
return result;
}
public ObjectLike getTrue() {
ObjectLike result = (ObjectLike)records[2];
if (result == null) {
throw new Error("Invalid true record");
}
return result;
}
public ObjectLike getPackageArray() {
ObjectLike result = (ObjectLike)records[3];
if (result == null) {
throw new Error("Invalid package-array record");
}
return result;
}
public ObjectLike getDependsArray() {
ObjectLike result = (ObjectLike)records[4];
if (result == null) {
throw new Error("Invalid depends-array record");
}
return result;
}
public ObjectLike getEntrypointsArray() {
ObjectLike result = (ObjectLike)records[5];
if (result == null) {
throw new Error("Invalid entrypoints-array record");
}
return result;
}
public ObjectLike getRuntimedataArray() {
ObjectLike result = (ObjectLike)records[6];
if (result == null) {
throw new Error("Invalid runtimeinfo-array record");
}
return result;
}
public ObjectLike getImportantMetadataArray() {
ObjectLike result = (ObjectLike)records[7];
if (result == null) {
throw new Error("Invalid importantmetadata-array record");
}
return result;
}
public ObjectLike getOtherMetadataArray() {
ObjectLike result = (ObjectLike)records[8];
if (result == null) {
throw new Error("Invalid othermetadata-array record");
}
return result;
}
public int getCurrentHeapSize() {
return records.length;
}
public int getHeapAllocationGranularity() {
return 1024;
}
public void expandHeap(int atLeast) {
int newLimit = getCurrentHeapSize() + atLeast;
while (newLimit < getMaximumRecords() && (newLimit % getHeapAllocationGranularity()) != 0) {
newLimit++;
}
if (newLimit > getMaximumRecords()) {
throw new Error("Unable to expand heap (hit the maximum of " + getMaximumRecords() + " records)");
}
numberFree += (newLimit - getCurrentHeapSize());
Record[] newRecords = new Record[newLimit];
for (int i = 0; i < getCurrentHeapSize(); i++) {
newRecords[i] = records[i];
}
records = newRecords;
}
public int objectTableSize(int compressionLevel) {
return objectTableSize(compressionLevel, getCurrentHeapSize());
}
public int objectTableSize(int compressionLevel, int upto) {
switch (compressionLevel) {
case 0:
return (highestUsedRecordIndex() + 1) * 16;
case 1:
return (highestUsedRecordIndex() + 1) * 8;
case 2:
return (highestUsedRecordIndex() + 1) * 4;
default: {
int sz = 0;
for (int i = 0; i < upto; i++) {
if (records[i] != null) {
sz += records[i].getTableEntrySize(compressionLevel);
}
}
return sz;
}
}
}
public void writeObjectTable(BytecodeOutput output, int compressionLevel, int dataGranularity) {
int dataOffset = 0;
for (int i = 0; i <= highestUsedRecordIndex(); i++) {
Record r = records[i];
if (r == null) {
switch (compressionLevel) {
case 0:
output.write64(0);
output.write64(0);
break;
case 1:
output.write64(0);
break;
case 2:
output.write32(0);
break;
case 3:
output.write16((short) 0);
break;
default:
throw new Error("Bad compression level " + compressionLevel);
}
} else {
int cl = r.getStandardClass().value;
if (r.usingCompressedFormat()) {
cl |= (1<<7);
}
int len = r.getArraySizeInBytes();
switch (compressionLevel) {
case 0:
output.write32(cl);
output.write32(len);
output.write64(dataOffset);
break;
case 1:
output.write32((len << 8) | (cl & 0xFF));
output.write32(dataOffset);
break;
case 2:
output.write32((len << 8) | (cl & 0xFF));
break;
case 3:
if (r.getTableEntrySize(compressionLevel) == 2) {
output.write16((short) ((len << 8) | ((cl | (1<<6)) & 0xFF)));
} else {
output.write32((len << 8) | (cl & 0xFF));
}
break;
default:
throw new Error("Bad compression level " + compressionLevel);
}
dataOffset += records[i].getArraySizeInBytes();
while ((dataOffset % dataGranularity) != 0) {
dataOffset++;
}
}
}
}
public int objectTableIndexSize(int compressionLevel, int maxpool) {
switch (compressionLevel) {
case 3: {
int sz = 0;
int top = this.highestUsedRecordIndex();
for (int i = 0; i <= top; i++) {
if (i % maxpool == 0) {
sz += 16;
}
}
return sz;
}
default:
return 0; // An index is not required unless the object table is compressed
}
}
public int writeObjectTableIndex(BytecodeOutput output, int compressionLevel, int maxpool, int dataGranularity) {
switch (compressionLevel) {
case 3: {
int sz = 0;
int tableoffset = 0;
int top = this.highestUsedRecordIndex();
boolean writtenDots = false;
for (int i = 0; i <= top; i++) {
Record r = records[i];
if (i % maxpool == 0) {
output.write64(tableoffset);
output.write64(dataOffset(i, dataGranularity));
if (i > maxpool*4 && i < top-maxpool*4) {
if (!writtenDots) {
Log.line("...");
writtenDots = true;
}
} else {
Log.line("Writing index #" + (i/maxpool) + ": table offset " + tableoffset + " data offset " + dataOffset(i, dataGranularity));
}
sz += 16;
}
tableoffset += r.getTableEntrySize(compressionLevel);
}
return sz;
}
default:
return 0; // An index is not required unless the object table is compressed
}
}
public void writeData(BytecodeOutput output, int granularity) {
int offset = 0;
for (int i = 0; i <= highestUsedRecordIndex(); i++) {
Record r = records[i];
if (r != null) {
r.writeArrayPart(output);
offset += r.getArraySizeInBytes();
while ((offset % granularity) != 0) {
output.write8((byte)0);
offset++;
}
}
}
}
public int fileHeaderSize(int compressionLevel, int maxpool, int dataGranularity, int sectionGranularity) {
return 64 * 4;
}
private void writeStringTag(BytecodeOutput output, String data, int size) {
try {
byte[] x = data.signedBytes();
if (x.length > size) {
throw new Error("String is too long (expected maximum of " + size + " bytes but got " + x.length + " bytes)");
}
for (int i = 0; i < size; i++) {
output.write8((byte) (i < x.length ? x[i] : 0));
}
} catch (Error e) {
throw new Error("Unable to encode string (system error?)", e);
}
}
private void writeFileHeader(BytecodeOutput output, String tagdata, int compressionLevel, int maxpool,
int dataGranularity, int sectionGranularity) {
// Section 0 (file top header)
writeStringTag(output, tagdata, 32); // Optional user-defined tag, may reference interpreter on Unix-like systems
writeStringTag(output, "BYTECODE FILE01", 16); // Magic number
output.write8((byte)1); // File format version
output.write8((byte)4); // Number of file headers (including this top-header part)
output.write16((short)64);
output.write32(0); // Reserved for checksum
output.write64(fileSize(compressionLevel, maxpool, dataGranularity, sectionGranularity));
int sectionDataOffset = fileHeaderSize(compressionLevel, maxpool, dataGranularity, sectionGranularity);
while ((sectionDataOffset % sectionGranularity) != 0) {
sectionDataOffset++;
}
// Section 1 (index, if provided)
if (objectTableIndexSize(compressionLevel, maxpool) == 0) {
for (int i = 0; i < 64; i++) {
output.write8((byte)0);
}
} else {
output.write32(maxpool);
output.write32(compressionLevel);
output.write32(0);
output.write32(0);
output.write32(0);
output.write32(0);
output.write32(0);
output.write32(0);
writeStringTag(output, "INDEX", 8);
output.write64(0);
output.write64(sectionDataOffset);
output.write64(objectTableIndexSize(compressionLevel, maxpool));
sectionDataOffset += objectTableIndexSize(compressionLevel, maxpool);
while ((sectionDataOffset % sectionGranularity) != 0) {
sectionDataOffset++;
}
}
// Section 2 (object table)
output.write32(highestUsedRecordIndex() + 1);
output.write32(compressionLevel);
output.write32(0);
output.write32(0);
output.write32(0);
output.write32(0);
output.write32(0);
output.write32(0);
writeStringTag(output, "TABLE", 8);
output.write64(0);
output.write64(sectionDataOffset);
output.write64(objectTableSize(compressionLevel));
sectionDataOffset += objectTableSize(compressionLevel);
while ((sectionDataOffset % sectionGranularity) != 0) {
sectionDataOffset++;
}
// Section 3 (object data)
output.write32(dataGranularity);//sectionDataOffset += objectTableIndexSize(compressionLevel, maxpool);
output.write32(0);
output.write32(0);
output.write32(0);
output.write32(0);
output.write32(0);
output.write32(0);
output.write32(0);
writeStringTag(output, "DATA", 8);
output.write64(0);
output.write64(sectionDataOffset);
output.write64(dataSize(dataGranularity));
sectionDataOffset += dataSize(dataGranularity);
while ((sectionDataOffset % sectionGranularity) != 0) {
sectionDataOffset++;
}
}
public int fileSize(int compressionLevel, int maxpool, int dataGranularity, int sectionGranularity) {
int sz = fileHeaderSize(compressionLevel, maxpool, dataGranularity, sectionGranularity);
while (sz % sectionGranularity != 0) {
sz++;
}
sz += objectTableIndexSize(compressionLevel, maxpool);
while (sz % sectionGranularity != 0) {
sz++;
}
sz += objectTableSize(compressionLevel);
while (sz % sectionGranularity != 0) {
sz++;
}
sz += dataSize(dataGranularity);
while (sz % sectionGranularity != 0) {
sz++;
}
return sz;
}
public void writeAll(BytecodeOutput output, String tagdata, int compressionLevel, int maxpool, int dataGranularity, int sectionGranularity) {
Log.line("aA");
int base = output.getCount();
int expectedOffset = 0;
Log.line("A");
writeFileHeader(output, tagdata, compressionLevel, maxpool, dataGranularity, sectionGranularity);
Log.line("B");
expectedOffset = fileHeaderSize(compressionLevel, maxpool, dataGranularity, sectionGranularity);
Log.line("C");
while (expectedOffset % sectionGranularity != 0) {
output.write8((byte)0);
expectedOffset++;
}
Log.line("D");
writeObjectTableIndex(output, compressionLevel, maxpool, dataGranularity);
expectedOffset += objectTableIndexSize(compressionLevel, maxpool);
while (expectedOffset % sectionGranularity != 0) {
output.write8((byte)0);
expectedOffset++;
}
Log.line("E");
writeObjectTable(output, compressionLevel, dataGranularity);
expectedOffset += objectTableSize(compressionLevel);
while (expectedOffset % sectionGranularity != 0) {
output.write8((byte)0);
expectedOffset++;
}
Log.line("F");
writeData(output, dataGranularity);
expectedOffset += dataSize(dataGranularity);
while (expectedOffset % sectionGranularity != 0) {
output.write8((byte)0);
expectedOffset++;
}
Log.line("G");
}
public int dataOffset(int index, int granularity) {
int total = 0;
for (int i = 0; i < index; i++) {
if (records[i] != null) {
total += records[i].getArraySizeInBytes();
while ((total % granularity) != 0) {
total++;
}
}
}
return total;
}
public int dataSize(int granularity) {
return dataOffset(getCurrentHeapSize(), granularity);
}
public int highestUsedRecordIndex() {
return highestUsedIndex;
///*
int highestUsed = -1;
for (int i = 0; i < getCurrentHeapSize(); i++) {
if (records[i] != null) {
highestUsed = i;
}
}
return highestUsed;
//*/
}
private int nextFreeHint = 0;
public int nextFreeRecordIndex() {
if (numberFree < 1) {
expandHeap(1);
}
if (numberFree < 1) {
throw new Error("Heap expansion failed unexpectedly");
}
for (int i = nextFreeHint; i < getCurrentHeapSize(); i++) {
if (records[i] == null) {
nextFreeHint = i;
return i;
}
}
throw new Error("Internal free count doesn't match - no free records found");
}
private int highestUsedIndex = -1;
/* This is called internally from the Record constructor, so the creator only needs to find a free index. */
private void assignRecord(Record r) {
assert r.heap == this;
assert records[r.recordIndex] == null;
records[r.recordIndex] = r;
if (r.recordIndex > highestUsedIndex) {
highestUsedIndex = r.recordIndex;
}
numberFree--;
}
public ObjectLike newObjectLike(StandardClass cl, int size) {
int idx = nextFreeRecordIndex();
return new ObjectLike(this, idx, cl, size);
}
public ArrayInt8 newArrayInt8(StandardClass standardClass, int size) {
int idx = nextFreeRecordIndex();
return new ArrayInt8(this, idx, standardClass, size);
}
public ArrayInt16 newArrayInt16(StandardClass standardClass, int size) {
int idx = nextFreeRecordIndex();
return new ArrayInt16(this, idx, standardClass, size);
}
public ArrayInt32 newArrayInt32(StandardClass standardClass, int size) {
int idx = nextFreeRecordIndex();
return new ArrayInt32(this, idx, standardClass, size);
}
public ArrayInt32 newInstructions(int size) {
return newArrayInt32(StandardClass.ARRAY_BYTECODE, size);
}
public ArrayInt32 newLabels(int size) {
return newArrayInt32(StandardClass.ARRAY_LABELS, size);
}
public ArrayInt16 newDebug(int size) {
return newArrayInt16(StandardClass.ARRAY_DEBUG16, size);
}
public ConstString getConstString(String value) {
if (constStringMap.hasKey(value)) {
return constStringMap.get(value);
}
int idx = nextFreeRecordIndex();
ConstString str = new ConstString(this, idx, value);
constStringMap.set(value, str);
return str;
}
/** Used for encoding quoted strings which may contain escape codes. If the
* string doesn't contain any fancy formatting, then it's encoded directly as
* a regular string constant (in UTF-8 format). Otherwise it's encoded as
* STRING_UNPROCESSED object containing a reference to the string exactly
* as written in the source code (including quotes, since future versions
* may allow string modifiers to specify a certain encoding or even regex
* syntax etc.).
*/
public Record getConstStringLiteral(String value) {
if (value.startsWith("\"") && value.endsWith("\"") && !value.search("\\")) {
return getConstString(value.sub(1, value.ints().length - 1));
}
if (constStringUnprocessedMap.hasKey(value)) {
return constStringUnprocessedMap.get(value);
}
ConstString strval = getConstString(value);
ObjectLike result = newObjectLike(StandardClass.STRING_UNPROCESSED, 1);
result.setElement(0, strval);
constStringUnprocessedMap.set(value, result);
return result;
}
public Record getConstInt32(String value) {
if (constStringInt32Map.hasKey(value)) {
return constStringInt32Map.get(value);
}
ConstString strval = getConstString(value);
ObjectLike result = newObjectLike(StandardClass.STRING_INT32, 1);
result.setElement(0, strval);
constStringInt32Map.set(value, result);
return result;
}
public Record getConstInt64(String value) {
if (constStringInt64Map.hasKey(value)) {
return constStringInt64Map.get(value);
}
ConstString strval = getConstString(value);
ObjectLike result = newObjectLike(StandardClass.STRING_INT64, 1);
result.setElement(0, strval);
constStringInt64Map.set(value, result);
return result;
}
public Record getConstUint32(String value) {
if (constStringUint32Map.hasKey(value)) {
return constStringUint32Map.get(value);
}
ConstString strval = getConstString(value);
ObjectLike result = newObjectLike(StandardClass.STRING_UINT32, 1);
result.setElement(0, strval);
constStringUint32Map.set(value, result);
return result;
}
public Record getConstUint64(String value) {
if (constStringUint64Map.hasKey(value)) {
return constStringUint64Map.get(value);
}
ConstString strval = getConstString(value);
ObjectLike result = newObjectLike(StandardClass.STRING_UINT64, 1);
result.setElement(0, strval);
constStringUint64Map.set(value, result);
return result;
}
public Record getConstFloat32(String value) {
if (constStringFloat32Map.hasKey(value)) {
return constStringFloat32Map.get(value);
}
ConstString strval = getConstString(value);
ObjectLike result = newObjectLike(StandardClass.STRING_FLOAT32, 1);
result.setElement(0, strval);
constStringFloat32Map.set(value, result);
return result;
}
public Record getConstFloat64(String value) {
if (constStringFloat64Map.hasKey(value)) {
return constStringFloat64Map.get(value);
}
ConstString strval = getConstString(value);
ObjectLike result = newObjectLike(StandardClass.STRING_FLOAT64, 1);
result.setElement(0, strval);
constStringFloat64Map.set(value, result);
return result;
}
public ObjectLike getPackage(String value) {
if (packageMap.hasKey(value)) {
return packageMap.get(value);
}
ConstString strval = getConstString(value);
ObjectLike result = newObjectLike(StandardClass.PACKAGE, 3);
result.setElement(0, strval);
result.setElement(1, getNull());
result.setElement(2, newObjectLike(StandardClass.SIMPLEARRAY, 0));
getPackageArray().appendElement(result);
packageMap.set(value, result);
return result;
}
public ObjectLike getTypeSignature(TypeSignature value) {
//Log.line("Looking up type signature " + value.toString());
if (typeSignatureMap.hasKey(value)) {
//Log.line("Using " + typeSignatureMap.get(value).debugString());
return typeSignatureMap.get(value);
}
ObjectLike result = newObjectLike(StandardClass.SIMPLETYPEREF, 2);
result.setElement(0, value.packageName == null ? getNull() : (ObjectLike) getConstString(value.packageName));
result.setElement(1, getConstString(value.typeName));
typeSignatureMap.set(value, result);
//Log.line("Created " + typeSignatureMap.get(value).debugString());
return result;
}
public ObjectLike getFieldSignature(FieldSignature value) {
if (fieldSignatureMap.hasKey(value)) {
return fieldSignatureMap.get(value);
}
ObjectLike result = newObjectLike(value.isStatic ? StandardClass.STATICFIELDREF : StandardClass.INSTANCEFIELDREF, 3);
result.setElement(0, getTypeSignature(value.owner));
result.setElement(1, getTypeSignature(value.storageType));
result.setElement(2, getConstString(value.name));
fieldSignatureMap.set(value, result);
return result;
}
public ObjectLike getMethodSignature(MethodSignature value) {
if (methodSignatureMap.hasKey(value)) {
return methodSignatureMap.get(value);
}
StandardClass cl;
switch (value.kind) {
case MethodSignature.Kind.CONSTRUCTOR:
cl = StandardClass.CONSTRUCTORMETHODREF;
break;
case MethodSignature.Kind.STATIC_INIT:
cl = StandardClass.STATICINITMETHODREF;
break;
case MethodSignature.Kind.STATIC_METHOD:
cl = StandardClass.STATICMETHODREF;
break;
case MethodSignature.Kind.INSTANCE_METHOD:
cl = StandardClass.INSTANCEMETHODREF;
break;
case MethodSignature.Kind.INTERFACE_METHOD:
cl = StandardClass.INTERFACEMETHODREF;
break;
default:
throw new Error("Internal error: Unrecognised method kind " + value.kind);
}
ObjectLike result = newObjectLike(cl, 4);
result.setElement(0, getTypeSignature(value.owner));
result.setElement(1, getTypeSignature(value.returnType));
result.setElement(2, getConstString(value.name));
if (value.argumentTypes != null && value.argumentTypes.length > 0) {
ObjectLike argtypes = newObjectLike(StandardClass.SIMPLEARRAY, value.argumentTypes.length);
result.setElement(3, argtypes);
for (int i = 0; i < value.argumentTypes.length; i++) {
argtypes.setElement(i, getTypeSignature(value.argumentTypes[i]));
}
}
methodSignatureMap.set(value, result);
return result;
}
public static enum FileFields {
PACKAGENAME,
INNERNAME,
DATA,
META,
DEBUGNAME
}
public ObjectLike newFile(String packagename, String innername, byte[] data, String typeinfo, String debugname) {
ObjectLike result = newObjectLike(StandardClass.FILE, FileFields.lookupCount(FileFields.class));
result.setElement(FileFields.PACKAGENAME.value, packagename == null ? getNull() : (ObjectLike) getConstString(packagename));
result.setElement(FileFields.INNERNAME.value, getConstString(innername));
result.setElement(FileFields.META.value, typeinfo == null ? getNull() : (ObjectLike) getConstString(typeinfo));
result.setElement(FileFields.DEBUGNAME.value, debugname == null ? getNull() : (ObjectLike) getConstString(debugname));
ArrayInt8 dataarray = newArrayInt8(StandardClass.FILE_BYTES, data.length);
for (int i = 0; i < data.length; i++) {
dataarray.setElement(i, data[i]);
}
result.setElement(FileFields.DATA.value, dataarray);
ObjectLike pkgtypes = (ObjectLike) getPackage(packagename).getElement(2);
pkgtypes.appendElement(result);
return result;
}
public static enum DependsFields {
NAME,
VERSION,
HINT,
HASH
}
public ObjectLike newDepends(String name, String version, String hint, String hash) {
ObjectLike result = newObjectLike(StandardClass.DEPENDS, DependsFields.lookupCount(DependsFields.class));
result.setElement(DependsFields.NAME.value, name == null ? getNull() : (ObjectLike) getConstString(name));
result.setElement(DependsFields.VERSION.value, version == null ? getNull() : (ObjectLike) getConstString(version));
result.setElement(DependsFields.HINT.value, hint == null ? getNull() : (ObjectLike) getConstString(hint));
result.setElement(DependsFields.HASH.value, hash == null ? getNull() : (ObjectLike) getConstString(hash));
getDependsArray().appendElement(result);
return result;
}
public static enum ProvidesFields {
NAME,
VERSION
}
public ObjectLike newProvides(String name, String version) {
ObjectLike result = newObjectLike(StandardClass.PROVIDES, ProvidesFields.lookupCount(ProvidesFields.class));
result.setElement(ProvidesFields.NAME.value, name == null ? getNull() : (ObjectLike) getConstString(name));
result.setElement(ProvidesFields.VERSION.value, version == null ? getNull() : (ObjectLike) getConstString(version));
getDependsArray().appendElement(result);
return result;
}
public static enum MetadataFields {
KEY,
VALUE,
}
public ObjectLike newMetadata(boolean important, String key, String value) {
ObjectLike result = newObjectLike(StandardClass.METADATA, MetadataFields.lookupCount(MetadataFields.class));
result.setElement(MetadataFields.KEY.value, key == null ? getNull() : (ObjectLike) getConstString(key));
result.setElement(MetadataFields.VALUE.value, value == null ? getNull() : (ObjectLike) getConstString(value));
if (important) {
getImportantMetadataArray().appendElement(result);
} else {
getOtherMetadataArray().appendElement(result);
}
return result;
}
public static enum RuntimedataFields {
TYPE,
FORMATOPTS,
FLAGS
}
public ObjectLike registerRuntimeType(int indexOrNeg, int kind, TypeSignature type, String formatopts, boolean isBuiltin, boolean isNumber, boolean isFloat, boolean isSigned, int nbits) {
int flags = kind;
if (isBuiltin) {
flags |= (1 << 8);
}
if (isNumber) {
flags |= (1 << 9);
}
if (isFloat) {
flags |= (1 << 10);
}
if (isSigned) {
flags |= (1 << 11);
}
flags |= (nbits << 16);
ObjectLike result = newObjectLike(StandardClass.RUNTIMEDATA, RuntimedataFields.lookupCount(RuntimedataFields.class));
//System.err.println("Adding runtime data #" + indexOrNeg + " at index #" + result.getRecordIndex());
result.setElement(RuntimedataFields.TYPE.value, type == null ? getNull() : getTypeSignature(type));
result.setElement(RuntimedataFields.FORMATOPTS.value, formatopts == null ? getNull() : (ObjectLike) getConstString(formatopts));
result.setElement(RuntimedataFields.FLAGS.value, getConstInt32("" + flags));
if (indexOrNeg < 0) {
getRuntimedataArray().appendElement(result);
} else {
getRuntimedataArray().setElement(indexOrNeg, result);
}
return result;
}
public static enum TypeFields {
SIGNATURE,
FLAGS,
BASE,
INTERFACES,
MEMBERS,
FILENAME
}
public ObjectLike newType(int flags, TypeSignature sig) {
ObjectLike result = newObjectLike(StandardClass.TYPE, TypeFields.lookupCount(TypeFields.class));
result.setElement(0, getTypeSignature(sig));
result.setElement(1, getNull());//newObjectLike(StandardClass.SIMPLEARRAY, 0));
result.setElement(2, getNull());
result.setElement(3, getNull());//newObjectLike(StandardClass.SIMPLEARRAY, 0));
result.setElement(4, getNull());
result.setElement(TypeFields.FLAGS.value, getConstInt32("" + flags));
ObjectLike pkgtypes = (ObjectLike) getPackage(sig.packageName).getElement(2);
pkgtypes.appendElement(result);
return result;
}
public void addMember(ObjectLike type, Record member) {
if (type.getElement(TypeFields.MEMBERS.value) == getNull()) {
type.setElement(TypeFields.MEMBERS.value, newObjectLike(StandardClass.SIMPLEARRAY, 0));
}
ObjectLike memberArray = (ObjectLike) type.getElement(TypeFields.MEMBERS.value);
memberArray.appendElement(member);
}
public static enum FieldFields {
SIGNATURE,
FLAGS
}
public ObjectLike newField(ObjectLike type, int flags, FieldSignature sig) {
ObjectLike result = newObjectLike(sig.isStatic ? StandardClass.STATIC_FIELD : StandardClass.INSTANCE_FIELD, FieldFields.lookupCount(FieldFields.class));
result.setElement(FieldFields.SIGNATURE.value, getFieldSignature(sig));
result.setElement(FieldFields.FLAGS.value, getConstInt32("" + flags));
addMember(type, result);
return result;
}
public static enum MethodFields {
SIGNATURE,
FLAGS,
BYTECODE,
EXCEPTIONS,
DEBUG,
LOCALS,
MAXSTACK,
THROWS
}
static int methodFieldsCount = MethodFields.lookupCount(MethodFields.class);
public ObjectLike newMethod(ObjectLike type, int flags, MethodSignature sig, int nlocals) {
StandardClass t;
switch (sig.kind) {
case MethodSignature.Kind.INSTANCE_METHOD:
t = StandardClass.INSTANCE_METHOD;
break;
case MethodSignature.Kind.STATIC_METHOD:
t = StandardClass.STATIC_METHOD;
break;
case MethodSignature.Kind.CONSTRUCTOR:
t = StandardClass.INSTANCE_CONSTRUCTOR;
break;
case MethodSignature.Kind.STATIC_INIT:
t = StandardClass.STATIC_INIT;
break;
case MethodSignature.Kind.INTERFACE_METHOD:
t = StandardClass.INTERFACE_METHOD;
break;
default:
throw new Error("Unexpected method signature kind: " + sig.kind);
}
ObjectLike result = newObjectLike(t, methodFieldsCount);
result.setElement(MethodFields.SIGNATURE.value, getMethodSignature(sig));
result.setElement(MethodFields.FLAGS.value, getConstInt32("" + flags));
result.setElement(MethodFields.LOCALS.value, getConstInt32("" + nlocals));
addMember(type, result);
return result;
}
public int getMaximumRecords() {
return maximumRecords;
}
public ObjectLike getMemberExtendedFlags(ObjectLike member) {
if (member.getElement(MethodFields.FLAGS.value).getStandardClass() != StandardClass.SIMPLEARRAY) {
ObjectLike result = newObjectLike(StandardClass.SIMPLEARRAY, 1);
result.setElement(0, member.getElement(MethodFields.FLAGS.value));
member.setElement(MethodFields.FLAGS.value, result);
}
return (ObjectLike) member.getElement(MethodFields.FLAGS.value);
}
public void addMemberTranslation(ObjectLike member, String language, String name) {
ObjectLike tr = newObjectLike(StandardClass.METADATA, MetadataFields.lookupCount(MetadataFields.class));
tr.setElement(MetadataFields.KEY.value, language == null ? getNull() : (ObjectLike) getConstString(language));
tr.setElement(MetadataFields.VALUE.value, name == null ? getNull() : (ObjectLike) getConstString(name));
getMemberExtendedFlags(member).appendElement(tr);
}
}