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 constStringMap = new Map(99999); private Map constStringUnprocessedMap = new Map(); private Map packageMap = new Map(99); private Map typeSignatureMap = new Map(999); private Map fieldSignatureMap = new Map(9999); private Map methodSignatureMap = new Map(12345); private Map constStringInt32Map = new Map(); private Map constStringInt64Map = new Map(); private Map constStringUint32Map = new Map(); private Map constStringUint64Map = new Map(); private Map constStringFloat32Map = new Map(); private Map constStringFloat64Map = new Map(); 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); } }