Rework static memory segment creation; Fix stdlib gc hooks not marking own fields; Align everything to 8 bytes that might be touched by GC

This commit is contained in:
dcodeIO
2018-08-04 00:36:59 +02:00
parent 61de7cf962
commit 631478c7c9
61 changed files with 11944 additions and 5286 deletions

View File

@ -7,7 +7,8 @@ import {
compileCall as compileBuiltinCall,
compileAllocate,
compileAbort,
compileIterateRoots
compileIterateRoots,
ensureGCHook
} from "./builtins";
import {
@ -6226,53 +6227,51 @@ export class Compiler extends DiagnosticEmitter {
/** Ensures that the specified string exists in static memory and returns a pointer to it. */
ensureStaticString(stringValue: string): ExpressionRef {
var program = this.program;
var module = this.module;
var options = this.options;
var stringSegments = this.stringSegments;
var needsGCHeader = program.hasGC;
var hasGC = program.hasGC;
var gcHeaderSize = program.gcHeaderSize;
var stringInstance = assert(program.stringInstance);
var stringSegment: MemorySegment;
var stringOffset: I64;
if (!stringSegments.has(stringValue)) {
let stringLength = stringValue.length;
let stringSize = 4 + stringLength * 2;
let offset = 0;
let gcHeaderSize = program.gcHeaderSize;
if (needsGCHeader) {
stringSize += gcHeaderSize;
offset += gcHeaderSize;
}
let stringBuffer = new Uint8Array(stringSize);
stringBuffer[offset ] = stringLength & 0xff;
stringBuffer[offset + 1] = (stringLength >>> 8) & 0xff;
stringBuffer[offset + 2] = (stringLength >>> 16) & 0xff;
stringBuffer[offset + 3] = (stringLength >>> 24) & 0xff;
for (let i = 0; i < stringLength; ++i) {
stringBuffer[offset + 4 + i * 2] = stringValue.charCodeAt(i) & 0xff;
stringBuffer[offset + 5 + i * 2] = (stringValue.charCodeAt(i) >>> 8) & 0xff;
}
stringSegment = this.addMemorySegment(stringBuffer, options.usizeType.byteSize);
stringSegments.set(stringValue, stringSegment);
if (needsGCHeader) {
stringOffset = i64_add(stringSegment.offset, i64_new(gcHeaderSize, 0));
// if the string already exists, reuse it
var segments = this.stringSegments;
if (segments.has(stringValue)) {
stringSegment = <MemorySegment>segments.get(stringValue);
// otherwise create it
} else {
let length = stringValue.length;
let headerSize = (stringInstance.currentMemoryOffset + 1) & ~1;
let totalSize = headerSize + length * 2;
let buf: Uint8Array;
let pos: u32;
if (hasGC) {
buf = new Uint8Array(gcHeaderSize + totalSize);
pos = gcHeaderSize;
writeI32(ensureGCHook(this, stringInstance), buf, program.gcHookOffset);
} else {
stringOffset = stringSegment.offset;
buf = new Uint8Array(totalSize);
pos = 0;
}
} else {
stringSegment = <MemorySegment>stringSegments.get(stringValue);
stringOffset = stringSegment.offset;
writeI32(length, buf, pos + stringInstance.offsetof("length"));
pos += headerSize;
for (let i = 0; i < length; ++i) {
writeI16(stringValue.charCodeAt(i), buf, pos + (i << 1));
}
stringSegment = this.addMemorySegment(buf);
segments.set(stringValue, stringSegment);
}
if (program.typesLookup.has("string")) {
let stringType = <Type>program.typesLookup.get("string");
this.currentType = stringType;
var stringOffset = stringSegment.offset;
if (hasGC) stringOffset = i64_add(stringOffset, i64_new(gcHeaderSize));
this.currentType = stringInstance.type;
if (this.options.isWasm64) {
return this.module.createI64(i64_low(stringOffset), i64_high(stringOffset));
} else {
this.currentType = options.usizeType;
}
if (options.isWasm64) {
return module.createI64(i64_low(stringOffset), i64_high(stringOffset));
} else {
assert(i64_is_i32(stringOffset));
return module.createI32(i64_low(stringOffset));
assert(i64_is_u32(stringOffset));
return this.module.createI32(i64_low(stringOffset));
}
}
@ -6282,52 +6281,32 @@ export class Compiler extends DiagnosticEmitter {
/** Ensures that the specified array exists in static memory and returns a pointer to it. */
ensureStaticArray(elementType: Type, values: ExpressionRef[]): ExpressionRef {
var program = this.program;
var hasGC = program.hasGC;
var gcHeaderSize = program.gcHeaderSize;
var length = values.length;
var byteSize = elementType.byteSize;
var byteLength = length * byteSize;
var usizeTypeSize = this.options.usizeType.byteSize;
// determine the size of the Array header
var arrayHeaderSize = (usizeTypeSize + 4 + 7) & ~7; // .buffer_ + .length_ + alignment
var arrayTotalSize = arrayHeaderSize;
var buf: Uint8Array;
var pos: u32;
// determine the size of the ArrayBuffer
var bufferHeaderSize = (4 + 7) & ~7; // .byteLength + alignment
var bufferTotalSize = 1 << (32 - clz(byteLength + bufferHeaderSize - 1)); // see internals
var program = this.program;
var needsGC = program.hasGC;
var gcHeaderSize = program.gcHeaderSize;
var offset = 0;
if (needsGC) {
offset += gcHeaderSize; // start writing after GC header
arrayTotalSize += gcHeaderSize;
bufferTotalSize += gcHeaderSize;
}
// create a compound segment holding both the the Array header and the ArrayBuffer
var buffer = new Uint8Array(arrayHeaderSize + bufferTotalSize);
var segment = this.addMemorySegment(buffer);
// write the Array header first
if (usizeTypeSize == 8) {
writeI64(i64_add(segment.offset, i64_new(arrayHeaderSize)), buffer, offset); // .buffer_
offset += 8;
// create the backing ArrayBuffer segment
var bufferInstance = assert(program.arrayBufferInstance);
var bufferHeaderSize = (bufferInstance.currentMemoryOffset + 7) & ~7;
var bufferTotalSize = 1 << (32 - clz(bufferHeaderSize + byteLength - 1));
if (hasGC) {
buf = new Uint8Array(gcHeaderSize + bufferTotalSize);
pos = gcHeaderSize;
writeI32(ensureGCHook(this, bufferInstance), buf, program.gcHookOffset);
} else {
assert(i64_is_u32(segment.offset));
writeI32(i64_low(segment.offset) + arrayHeaderSize, buffer, offset); // .buffer_
offset += 4;
buf = new Uint8Array(bufferTotalSize);
pos = 0;
}
writeI32(length, buffer, offset); // .length_
offset += 4;
assert(((offset + 7) & ~7) == arrayTotalSize); // incl. GC header if applicable
// append the ArrayBuffer
offset = arrayTotalSize;
if (needsGC) offset += gcHeaderSize;
writeI32(byteLength, buffer, offset); // .byteLength
offset += bufferHeaderSize; // align
writeI32(byteLength, buf, pos + bufferInstance.offsetof("byteLength"));
pos += bufferHeaderSize;
var nativeType = elementType.toNativeType();
switch (nativeType) {
case NativeType.I32: {
@ -6337,8 +6316,8 @@ export class Compiler extends DiagnosticEmitter {
let value = values[i];
assert(getExpressionType(value) == nativeType);
assert(getExpressionId(value) == ExpressionId.Const);
writeI8(getConstValueI32(value), buffer, offset);
offset += 1;
writeI8(getConstValueI32(value), buf, pos);
pos += 1;
}
break;
}
@ -6347,8 +6326,8 @@ export class Compiler extends DiagnosticEmitter {
let value = values[i];
assert(getExpressionType(value) == nativeType);
assert(getExpressionId(value) == ExpressionId.Const);
writeI16(getConstValueI32(value), buffer, offset);
offset += 2;
writeI16(getConstValueI32(value), buf, pos);
pos += 2;
}
break;
}
@ -6357,8 +6336,8 @@ export class Compiler extends DiagnosticEmitter {
let value = values[i];
assert(getExpressionType(value) == nativeType);
assert(getExpressionId(value) == ExpressionId.Const);
writeI32(getConstValueI32(value), buffer, offset);
offset += 4;
writeI32(getConstValueI32(value), buf, pos);
pos += 4;
}
break;
}
@ -6371,8 +6350,8 @@ export class Compiler extends DiagnosticEmitter {
let value = values[i];
assert(getExpressionType(value) == nativeType);
assert(getExpressionId(value) == ExpressionId.Const);
writeI64(i64_new(getConstValueI64Low(value), getConstValueI64High(value)), buffer, offset);
offset += 8;
writeI64(i64_new(getConstValueI64Low(value), getConstValueI64High(value)), buf, pos);
pos += 8;
}
break;
}
@ -6381,8 +6360,8 @@ export class Compiler extends DiagnosticEmitter {
let value = values[i];
assert(getExpressionType(value) == nativeType);
assert(getExpressionId(value) == ExpressionId.Const);
writeF32(getConstValueF32(value), buffer, offset);
offset += 4;
writeF32(getConstValueF32(value), buf, pos);
pos += 4;
}
break;
}
@ -6391,35 +6370,43 @@ export class Compiler extends DiagnosticEmitter {
let value = values[i];
assert(getExpressionType(value) == nativeType);
assert(getExpressionId(value) == ExpressionId.Const);
writeF64(getConstValueF64(value), buffer, offset);
offset += 8;
writeF64(getConstValueF64(value), buf, pos);
pos += 8;
}
break;
}
default: assert(false);
}
assert(offset <= arrayTotalSize + bufferTotalSize); // might have empty trailing space
var bufferSegment = this.addMemorySegment(buf);
var bufferOffset = bufferSegment.offset;
if (hasGC) bufferOffset = i64_add(bufferOffset, i64_new(gcHeaderSize));
var arrayPrototype = this.program.arrayPrototype;
if (arrayPrototype) {
let arrayInstance = this.resolver.resolveClass(arrayPrototype, [ elementType ], null, ReportMode.REPORT);
if (!arrayInstance) {
this.currentType = this.options.usizeType;
return this.module.createUnreachable();
}
this.currentType = arrayInstance.type;
// create the Array segment and return a pointer to it
var arrayPrototype = assert(program.arrayPrototype);
var arrayInstance = assert(this.resolver.resolveClass(arrayPrototype, [ elementType ]));
var arrayHeaderSize = (arrayInstance.currentMemoryOffset + 7) & ~7;
if (hasGC) {
buf = new Uint8Array(gcHeaderSize + arrayHeaderSize);
pos = gcHeaderSize;
writeI32(ensureGCHook(this, arrayInstance), buf, program.gcHookOffset);
} else {
this.currentType = this.options.usizeType;
buf = new Uint8Array(arrayHeaderSize);
pos = 0;
}
// return a pointer at the array header (skip GC header if present)
var address = segment.offset;
if (needsGC) address = i64_add(address, i64_new(gcHeaderSize, 0));
var arraySegment = this.addMemorySegment(buf);
var arrayOffset = arraySegment.offset;
if (hasGC) arrayOffset = i64_add(arrayOffset, i64_new(gcHeaderSize));
this.currentType = arrayInstance.type;
if (usizeTypeSize == 8) {
return this.module.createI64(i64_low(address), i64_high(address));
writeI64(bufferOffset, buf, pos + arrayInstance.offsetof("buffer_"));
writeI32(length, buf, pos + arrayInstance.offsetof("length_"));
return this.module.createI64(i64_low(arrayOffset), i64_high(arrayOffset));
} else {
assert(i64_is_u32(address));
return this.module.createI32(i64_low(address));
assert(i64_is_u32(bufferOffset));
writeI32(i64_low(bufferOffset), buf, pos + arrayInstance.offsetof("buffer_"));
writeI32(length, buf, pos + arrayInstance.offsetof("length_"));
assert(i64_is_u32(arrayOffset));
return this.module.createI32(i64_low(arrayOffset));
}
}
@ -6433,17 +6420,20 @@ export class Compiler extends DiagnosticEmitter {
// find out whether all elements are constant (array is static)
var length = expressions.length;
var values = new Array<ExpressionRef>(length);
var compiledValues = new Array<ExpressionRef>(length);
var constantValues = new Array<ExpressionRef>(length);
var nativeElementType = elementType.toNativeType();
var isStatic = true;
for (let i = 0; i < length; ++i) {
values[i] = expressions[i]
let expr = expressions[i]
? this.compileExpression(<Expression>expressions[i], elementType, ConversionKind.IMPLICIT, WrapMode.NONE)
: elementType.toNativeZero(module);
compiledValues[i] = expr;
if (isStatic) {
let expr = module.precomputeExpression(values[i]);
expr = module.precomputeExpression(compiledValues[i]);
if (getExpressionId(expr) == ExpressionId.Const) {
assert(getExpressionType(expr) == nativeElementType);
constantValues[i] = expr;
} else {
if (isConst) {
this.warning(
@ -6457,7 +6447,7 @@ export class Compiler extends DiagnosticEmitter {
}
// make a static array if possible
if (isStatic) return this.ensureStaticArray(elementType, values);
if (isStatic) return this.ensureStaticArray(elementType, constantValues);
// otherwise obtain the array type
var arrayPrototype = assert(this.program.arrayPrototype);
@ -6491,7 +6481,7 @@ export class Compiler extends DiagnosticEmitter {
stmts[index++] = this.makeCallDirect(setter, [
module.createGetLocal(tempLocal.index, nativeArrayType), // this
module.createI32(i),
values[i]
compiledValues[i]
]);
}
assert(index + 1 == stmts.length);

View File

@ -328,6 +328,8 @@ export class Program extends DiagnosticEmitter {
/** Module-level exports by exported name. */
moduleLevelExports: Map<string,ModuleExport> = new Map();
/** ArrayBuffer instance reference. */
arrayBufferInstance: Class | null = null;
/** Array prototype reference. */
arrayPrototype: ClassPrototype | null = null;
/** String instance reference. */
@ -351,6 +353,8 @@ export class Program extends DiagnosticEmitter {
gcMarkInstance: Function | null = null;
/** Size of a managed object header. */
gcHeaderSize: u32 = 0;
/** Offset of the GC hook. */
gcHookOffset: u32 = 0;
/** Currently processing filespace. */
currentFilespace: Filespace;
@ -603,6 +607,13 @@ export class Program extends DiagnosticEmitter {
}
}
// register 'ArrayBuffer'
if (this.elementsLookup.has("ArrayBuffer")) {
let element = assert(this.elementsLookup.get("ArrayBuffer"));
assert(element.kind == ElementKind.CLASS_PROTOTYPE);
this.arrayBufferInstance = resolver.resolveClass(<ClassPrototype>element, null);
}
// register 'Array'
if (this.elementsLookup.has("Array")) {
let element = assert(this.elementsLookup.get("Array"));
@ -708,7 +719,9 @@ export class Program extends DiagnosticEmitter {
this.gcAllocateInstance = gcAllocateInstance;
this.gcLinkInstance = gcLinkInstance;
this.gcMarkInstance = gcMarkInstance;
this.gcHeaderSize = (2 * options.usizeType.byteSize + 4 + 7) & ~7; // TODO: hardcoded atm
let gcHookOffset = 2 * options.usizeType.byteSize; // .next + .prev
this.gcHookOffset = gcHookOffset;
this.gcHeaderSize = (gcHookOffset + 4 + 7) & ~7; // + .hook index + alignment
this.hasGC = true;
}
}
@ -2911,6 +2924,14 @@ export class Class extends Element {
return null;
}
offsetof(fieldName: string): u32 {
var members = assert(this.members);
assert(members.has(fieldName));
var field = <Element>members.get(fieldName);
assert(field.kind == ElementKind.FIELD);
return (<Field>field).memoryOffset;
}
toString(): string {
return this.simpleName;
}