diff --git a/slangc/.DS_Store b/slangc/.DS_Store new file mode 100644 index 0000000..9911a92 Binary files /dev/null and b/slangc/.DS_Store differ diff --git a/slangc/api/BytecodeHeap.sauce b/slangc/api/BytecodeHeap.sauce new file mode 100644 index 0000000..fcc5e3d --- /dev/null +++ b/slangc/api/BytecodeHeap.sauce @@ -0,0 +1,1646 @@ +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); + } +} diff --git a/slangc/api/BytecodeInstructionWriter.sauce b/slangc/api/BytecodeInstructionWriter.sauce new file mode 100644 index 0000000..21671a5 --- /dev/null +++ b/slangc/api/BytecodeInstructionWriter.sauce @@ -0,0 +1,1023 @@ +package slangc.api; + +import slang.data.List; +import slangc.api.BytecodeHeap; +import slangc.api.TypeOption; +import slangc.bytecode.FieldSignature; +import slangc.bytecode.MethodSignature; +import slangc.bytecode.TypeSignature; +import slangc.model.BuiltinTypeModel; +import slangc.model.TypeModel; +import slangc.model.UserTypeModel; +import slangc.parser.AnnotationType; +import slangc.parser.ErrorType; +import slangc.parser.LocationAnnotation; +import slangc.parser.Node; + +public class BytecodeInstructionWriter { + public final BytecodeTarget target; + public final TypeModel owner; + public final BytecodeHeap.ObjectLike method; + private BytecodeHeap.ArrayInt32 instructions; + private List sourceStack = new List(); + private List