From 968b0321a094cc3f9713045835e1b0732e605da2 Mon Sep 17 00:00:00 2001 From: dcode Date: Fri, 15 Mar 2019 09:26:31 +0100 Subject: [PATCH] decisions --- src/builtins.ts | 12 +- src/common.ts | 10 +- src/compiler.ts | 287 +++++++++++++++-------- src/program.ts | 93 +++----- src/types.ts | 2 +- std/assembly/array.ts | 54 ++--- std/assembly/arraybuffer.ts | 17 +- std/assembly/collector/itcm.ts | 6 +- std/assembly/dataview.ts | 4 +- std/assembly/gc.ts | 25 +- std/assembly/index.d.ts | 30 +-- std/assembly/map.ts | 14 +- std/assembly/runtime.ts | 259 +++++++++++--------- std/assembly/set.ts | 4 +- std/assembly/string.ts | 75 +++--- std/assembly/typedarray.ts | 7 +- std/assembly/util/number.ts | 27 +-- tests/compiler/std/runtime.optimized.wat | 73 +++--- tests/compiler/std/runtime.ts | 39 +-- tests/compiler/std/runtime.untouched.wat | 138 +++++------ 20 files changed, 619 insertions(+), 557 deletions(-) diff --git a/src/builtins.ts b/src/builtins.ts index e4c1baab..94b0ec3f 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -478,9 +478,9 @@ export namespace BuiltinSymbols { export const memory_copy = "~lib/memory/memory.copy"; export const memory_fill = "~lib/memory/memory.fill"; - // std/gc.ts - export const gc_classId = "~lib/gc/gc.classId"; - export const gc_iterateRoots = "~lib/gc/gc.iterateRoots"; + // std/runtime.ts + export const CLASSID = "~lib/runtime/CLASSID"; + export const ITERATEROOTS = "~lib/runtime/ITERATEROOTS"; // std/typedarray.ts export const Int8Array = "~lib/typedarray/Int8Array"; @@ -641,7 +641,7 @@ export function compileCall( return module.createI32(getExpressionId(expr) == ExpressionId.Const ? 1 : 0); } case BuiltinSymbols.isManaged: { // isManaged() -> bool - if (!compiler.program.hasGC) { + if (!compiler.program.gcImplemented) { compiler.currentType = Type.bool; return module.createI32(0); } @@ -3621,7 +3621,7 @@ export function compileCall( // === Internal runtime ======================================================================= - case BuiltinSymbols.gc_classId: { + case BuiltinSymbols.CLASSID: { let type = evaluateConstantType(compiler, typeArguments, operands, reportNode); compiler.currentType = Type.u32; if (!type) return module.createUnreachable(); @@ -3629,7 +3629,7 @@ export function compileCall( if (!classReference) return module.createUnreachable(); return module.createI32(classReference.id); } - case BuiltinSymbols.gc_iterateRoots: { + case BuiltinSymbols.ITERATEROOTS: { if ( checkTypeAbsent(typeArguments, reportNode, prototype) | checkArgsRequired(operands, 1, reportNode, compiler) diff --git a/src/common.ts b/src/common.ts index f46ffff1..f79dcd40 100644 --- a/src/common.ts +++ b/src/common.ts @@ -179,10 +179,14 @@ export namespace LibrarySymbols { export const Math = "Math"; export const Mathf = "Mathf"; // runtime - export const memory = "memory"; - export const allocate = "allocate"; export const abort = "abort"; - export const main = "main"; + export const ALLOCATE = "ALLOCATE"; + export const REALLOCATE = "REALLOCATE"; + export const DISCARD = "DISCARD"; + // gc + export const gc = "gc"; + export const register = "register"; + export const link = "link"; // other export const length = "length"; export const byteLength = "byteLength"; diff --git a/src/compiler.ts b/src/compiler.ts index 37c2f838..1a2c1863 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -4690,7 +4690,7 @@ export class Compiler extends DiagnosticEmitter { let isUnchecked = flow.is(FlowFlags.UNCHECKED_CONTEXT); let indexedSet = (target).lookupOverload(OperatorKind.INDEXED_SET, isUnchecked); if (!indexedSet) { - let arrayBufferView = this.program.arrayBufferView; + let arrayBufferView = this.program.arrayBufferViewInstance; if (arrayBufferView) { if ((target).prototype.extends(arrayBufferView.prototype)) { return compileArraySet( @@ -5910,7 +5910,7 @@ export class Compiler extends DiagnosticEmitter { let isUnchecked = this.currentFlow.is(FlowFlags.UNCHECKED_CONTEXT); let indexedGet = (target).lookupOverload(OperatorKind.INDEXED_GET, isUnchecked); if (!indexedGet) { - let arrayBufferView = this.program.arrayBufferView; + let arrayBufferView = this.program.arrayBufferViewInstance; if (arrayBufferView) { if ((target).prototype.extends(arrayBufferView.prototype)) { return compileArrayGet( @@ -6427,34 +6427,17 @@ export class Compiler extends DiagnosticEmitter { return this.ensureStaticString(expression.value); } - /** Ensures that the specified array exists in static memory and returns a pointer to it. */ - ensureStaticArray(elementType: Type, values: ExpressionRef[]): ExpressionRef { + ensureStaticArrayBuffer(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; - - var buf: Uint8Array; - var pos: u32; - - // 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 { - buf = new Uint8Array(bufferTotalSize); - pos = 0; - } - writeI32(byteLength, buf, pos + bufferInstance.offsetof(LibrarySymbols.byteLength)); - pos += bufferHeaderSize; + var runtimeHeaderSize = program.runtimeHeaderSize; + + var buf = new Uint8Array(runtimeHeaderSize + byteLength); + program.writeRuntimeHeader(buf, 0, bufferInstance, byteLength); + var pos = runtimeHeaderSize; var nativeType = elementType.toNativeType(); switch (nativeType) { case NativeType.I32: { @@ -6525,45 +6508,153 @@ export class Compiler extends DiagnosticEmitter { } default: assert(false); } - var bufferSegment = this.addMemorySegment(buf); - var bufferOffset = bufferSegment.offset; - if (hasGC) bufferOffset = i64_add(bufferOffset, i64_new(gcHeaderSize)); + assert(pos == buf.length); - // create the Array segment and return a pointer to it - var arrayPrototype = assert(program.arrayPrototype); - var arrayInstance = assert(this.resolver.resolveClass( - arrayPrototype, - [ elementType ], - makeMap() - )); - var arrayHeaderSize = (arrayInstance.currentMemoryOffset + 7) & ~7; - if (hasGC) { - buf = new Uint8Array(gcHeaderSize + arrayHeaderSize); - pos = gcHeaderSize; - writeI32(ensureGCHook(this, arrayInstance), buf, program.gcHookOffset); - } else { - buf = new Uint8Array(arrayHeaderSize); - pos = 0; - } - var arraySegment = this.addMemorySegment(buf); - var arrayOffset = arraySegment.offset; - if (hasGC) arrayOffset = i64_add(arrayOffset, i64_new(gcHeaderSize)); - this.currentType = arrayInstance.type; - var buffer_offset = pos + arrayInstance.offsetof("buffer_"); - var length_offset = pos + arrayInstance.offsetof("length_"); - if (usizeTypeSize == 8) { - writeI64(bufferOffset, buf, buffer_offset); - writeI32(length, buf, length_offset); - return this.module.createI64(i64_low(arrayOffset), i64_high(arrayOffset)); - } else { - assert(i64_is_u32(bufferOffset)); - writeI32(i64_low(bufferOffset), buf, buffer_offset); - writeI32(length, buf, length_offset); - assert(i64_is_u32(arrayOffset)); - return this.module.createI32(i64_low(arrayOffset)); - } + var segment = this.addMemorySegment(buf); + var offset = i64_add(segment.offset, i64_new(runtimeHeaderSize)); + this.currentType = bufferInstance.type; + return program.options.isWasm64 + ? this.module.createI64(i64_low(offset), i64_high(offset)) + : this.module.createI32(i64_low(offset)); } + /** 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; + + // var buf: Uint8Array; + // var pos: u32; + + // // 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 { + // buf = new Uint8Array(bufferTotalSize); + // pos = 0; + // } + // writeI32(byteLength, buf, pos + bufferInstance.offsetof(LibrarySymbols.byteLength)); + // pos += bufferHeaderSize; + // var nativeType = elementType.toNativeType(); + // switch (nativeType) { + // case NativeType.I32: { + // switch (byteSize) { + // case 1: { + // for (let i = 0; i < length; ++i) { + // let value = values[i]; + // assert(getExpressionType(value) == nativeType); + // assert(getExpressionId(value) == ExpressionId.Const); + // writeI8(getConstValueI32(value), buf, pos); + // pos += 1; + // } + // break; + // } + // case 2: { + // for (let i = 0; i < length; ++i) { + // let value = values[i]; + // assert(getExpressionType(value) == nativeType); + // assert(getExpressionId(value) == ExpressionId.Const); + // writeI16(getConstValueI32(value), buf, pos); + // pos += 2; + // } + // break; + // } + // case 4: { + // for (let i = 0; i < length; ++i) { + // let value = values[i]; + // assert(getExpressionType(value) == nativeType); + // assert(getExpressionId(value) == ExpressionId.Const); + // writeI32(getConstValueI32(value), buf, pos); + // pos += 4; + // } + // break; + // } + // default: assert(false); + // } + // break; + // } + // case NativeType.I64: { + // for (let i = 0; i < length; ++i) { + // let value = values[i]; + // assert(getExpressionType(value) == nativeType); + // assert(getExpressionId(value) == ExpressionId.Const); + // writeI64(i64_new(getConstValueI64Low(value), getConstValueI64High(value)), buf, pos); + // pos += 8; + // } + // break; + // } + // case NativeType.F32: { + // for (let i = 0; i < length; ++i) { + // let value = values[i]; + // assert(getExpressionType(value) == nativeType); + // assert(getExpressionId(value) == ExpressionId.Const); + // writeF32(getConstValueF32(value), buf, pos); + // pos += 4; + // } + // break; + // } + // case NativeType.F64: { + // for (let i = 0; i < length; ++i) { + // let value = values[i]; + // assert(getExpressionType(value) == nativeType); + // assert(getExpressionId(value) == ExpressionId.Const); + // writeF64(getConstValueF64(value), buf, pos); + // pos += 8; + // } + // break; + // } + // default: assert(false); + // } + // var bufferSegment = this.addMemorySegment(buf); + // var bufferOffset = bufferSegment.offset; + // if (hasGC) bufferOffset = i64_add(bufferOffset, i64_new(gcHeaderSize)); + + // // create the Array segment and return a pointer to it + // var arrayPrototype = assert(program.arrayPrototype); + // var arrayInstance = assert(this.resolver.resolveClass( + // arrayPrototype, + // [ elementType ], + // makeMap() + // )); + // var arrayHeaderSize = (arrayInstance.currentMemoryOffset + 7) & ~7; + // if (hasGC) { + // buf = new Uint8Array(gcHeaderSize + arrayHeaderSize); + // pos = gcHeaderSize; + // writeI32(ensureGCHook(this, arrayInstance), buf, program.gcHookOffset); + // } else { + // buf = new Uint8Array(arrayHeaderSize); + // pos = 0; + // } + // var arraySegment = this.addMemorySegment(buf); + // var arrayOffset = arraySegment.offset; + // if (hasGC) arrayOffset = i64_add(arrayOffset, i64_new(gcHeaderSize)); + // this.currentType = arrayInstance.type; + // var buffer_offset = pos + arrayInstance.offsetof("buffer_"); + // var length_offset = pos + arrayInstance.offsetof("length_"); + // if (usizeTypeSize == 8) { + // writeI64(bufferOffset, buf, buffer_offset); + // writeI32(length, buf, length_offset); + // return this.module.createI64(i64_low(arrayOffset), i64_high(arrayOffset)); + // } else { + // assert(i64_is_u32(bufferOffset)); + // writeI32(i64_low(bufferOffset), buf, buffer_offset); + // writeI32(length, buf, length_offset); + // assert(i64_is_u32(arrayOffset)); + // return this.module.createI32(i64_low(arrayOffset)); + // } + // } + compileArrayLiteral( elementType: Type, expressions: (Expression | null)[], @@ -6601,18 +6692,20 @@ export class Compiler extends DiagnosticEmitter { } } - // make a static array if possible - // if (isStatic) return this.ensureStaticArray(elementType, constantValues); // TODO - - // otherwise obtain the array type var arrayPrototype = assert(this.program.arrayPrototype); var arrayInstance = assert(this.resolver.resolveClass( arrayPrototype, [ elementType ], - makeMap() + makeMap() )); var arrayType = arrayInstance.type; + // make a static buffer if possible + if (isStatic) { + // let bufferPtr = this.ensureStaticArrayBuffer(elementType, constantValues); + // TODO: runtime.alloc the array header and make it use a copy of the static buffer + } + // and compile an explicit instantiation this.currentType = arrayType; var setter = arrayInstance.lookupOverload(OperatorKind.INDEXED_SET, true); @@ -7851,38 +7944,42 @@ export class Compiler extends DiagnosticEmitter { assert(classInstance.program == program); var module = this.module; var options = this.options; + var nativeSizeType = options.nativeSizeType; - // __gc_allocate(size, markFn) - if (program.hasGC && classInstance.type.isManaged(program)) { - let allocateInstance = assert(program.gcAllocateInstance); - if (!this.compileFunction(allocateInstance)) return module.createUnreachable(); - this.currentType = classInstance.type; - return module.createCall( - allocateInstance.internalName, [ - options.isWasm64 - ? module.createI64(classInstance.currentMemoryOffset) - : module.createI32(classInstance.currentMemoryOffset), - module.createI32( - ensureGCHook(this, classInstance) - ) - ], - options.nativeSizeType - ); + // ALLOCATE(payloadSize) + var allocateInstance = assert(program.allocateInstance); + if (!this.compileFunction(allocateInstance)) return module.createUnreachable(); + var alloc = module.createCall( + allocateInstance.internalName, [ + options.isWasm64 + ? module.createI64(classInstance.currentMemoryOffset) + : module.createI32(classInstance.currentMemoryOffset) + // module.createI32( + // ensureGCHook(this, classInstance) + // ) + ], + nativeSizeType + ); - // memory.allocate(size) - } else { - let allocateInstance = program.memoryAllocateInstance; - if (!allocateInstance || !this.compileFunction(allocateInstance)) return module.createUnreachable(); - this.currentType = classInstance.type; - return module.createCall( - allocateInstance.internalName, [ - options.isWasm64 - ? module.createI64(classInstance.currentMemoryOffset) - : module.createI32(classInstance.currentMemoryOffset) - ], - options.nativeSizeType - ); + // REGISTER(ref, classId) + if (program.gcImplemented) { + let registerInstance = assert(program.gcRegisterInstance); + if (!this.compileFunction(registerInstance)) return module.createUnreachable(); + let tempLocal = this.currentFlow.getAndFreeTempLocal(classInstance.type, false); + alloc = module.createBlock(null, [ + module.createCall( + registerInstance.internalName, [ + module.createTeeLocal(tempLocal.index, alloc), + module.createI32(classInstance.id) + ], + NativeType.None + ), + module.createGetLocal(tempLocal.index, nativeSizeType) + ], nativeSizeType); } + + this.currentType = classInstance.type; + return alloc; } /** Makes the initializers for a class's fields. */ diff --git a/src/program.ts b/src/program.ts index 7db97ef6..1d8f9ef5 100644 --- a/src/program.ts +++ b/src/program.ts @@ -334,7 +334,7 @@ export class Program extends DiagnosticEmitter { // runtime references /** ArrayBufferView reference. */ - arrayBufferView: Class | null = null; + arrayBufferViewInstance: Class | null = null; /** ArrayBuffer instance reference. */ arrayBufferInstance: Class | null = null; /** Array prototype reference. */ @@ -343,8 +343,12 @@ export class Program extends DiagnosticEmitter { stringInstance: Class | null = null; /** Abort function reference, if present. */ abortInstance: Function | null = null; - /** Memory allocation function. */ - memoryAllocateInstance: Function | null = null; + /** Runtime allocation function. */ + allocateInstance: Function | null = null; + /** Runtime reallocation function. */ + reallocateInstance: Function | null = null; + /** Runtime discard function. */ + discardInstance: Function | null = null; /** Next class id. */ nextClassId: u32 = 1; @@ -352,9 +356,11 @@ export class Program extends DiagnosticEmitter { // gc integration /** Whether a garbage collector is present or not. */ - hasGC: bool = false; - /** Garbage collector allocation function. */ - gcAllocateInstance: Function | null = null; + get gcImplemented(): bool { + return this.gcRegisterInstance !== null; + } + /** Garbage collector register function called when an object becomes managed. */ + gcRegisterInstance: Function | null = null; /** Garbage collector link function called when a managed object is referenced from a parent. */ gcLinkInstance: Function | null = null; /** Garbage collector mark function called to on reachable managed objects. */ @@ -380,7 +386,7 @@ export class Program extends DiagnosticEmitter { /** Gets the size of a common runtime header. */ get runtimeHeaderSize(): i32 { - return this.hasGC ? 16 : 8; + return this.gcImplemented ? 16 : 8; } /** Writes a common runtime header to the specified buffer. */ @@ -767,18 +773,18 @@ export class Program extends DiagnosticEmitter { // register global library elements { let element: Element | null; - if (element = this.lookupGlobal(LibrarySymbols.String)) { - assert(element.kind == ElementKind.CLASS_PROTOTYPE); - this.stringInstance = resolver.resolveClass(element, null); - } if (element = this.lookupGlobal(LibrarySymbols.ArrayBufferView)) { assert(element.kind == ElementKind.CLASS_PROTOTYPE); - this.arrayBufferView = resolver.resolveClass(element, null); + this.arrayBufferViewInstance = resolver.resolveClass(element, null); } if (element = this.lookupGlobal(LibrarySymbols.ArrayBuffer)) { assert(element.kind == ElementKind.CLASS_PROTOTYPE); this.arrayBufferInstance = resolver.resolveClass(element, null); } + if (element = this.lookupGlobal(LibrarySymbols.String)) { + assert(element.kind == ElementKind.CLASS_PROTOTYPE); + this.stringInstance = resolver.resolveClass(element, null); + } if (element = this.lookupGlobal(LibrarySymbols.Array)) { assert(element.kind == ElementKind.CLASS_PROTOTYPE); this.arrayPrototype = element; @@ -787,59 +793,18 @@ export class Program extends DiagnosticEmitter { assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); this.abortInstance = this.resolver.resolveFunction(element, null); } - if (element = this.lookupGlobal(LibrarySymbols.memory)) { - if (element = element.lookupInSelf(LibrarySymbols.allocate)) { - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - this.memoryAllocateInstance = this.resolver.resolveFunction(element, null); - } + if (element = this.lookupGlobal(LibrarySymbols.ALLOCATE)) { + assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); + this.allocateInstance = this.resolver.resolveFunction(element, null); + } + if (element = this.lookupGlobal(LibrarySymbols.REALLOCATE)) { + assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); + this.reallocateInstance = this.resolver.resolveFunction(element, null); + } + if (element = this.lookupGlobal(LibrarySymbols.DISCARD)) { + assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); + this.discardInstance = this.resolver.resolveFunction(element, null); } - } - - // register GC hooks if present - // FIXME: think about a better way than globals to model this, maybe a GC namespace that can be - // dynamically extended by a concrete implementation but then has `@unsafe` methods that normal - // code cannot call without explicitly enabling it with a flag. - if ( - this.elementsByName.has("__gc_allocate") && - this.elementsByName.has("__gc_link") && - this.elementsByName.has("__gc_mark") - ) { - // __gc_allocate(usize, (ref: usize) => void): usize - let element = this.elementsByName.get("__gc_allocate"); - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - let gcAllocateInstance = assert(this.resolver.resolveFunction(element, null)); - let signature = gcAllocateInstance.signature; - assert(signature.parameterTypes.length == 2); - assert(signature.parameterTypes[0] == this.options.usizeType); - assert(signature.parameterTypes[1].signatureReference); - assert(signature.returnType == this.options.usizeType); - - // __gc_link(usize, usize): void - element = this.elementsByName.get("__gc_link"); - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - let gcLinkInstance = assert(this.resolver.resolveFunction(element, null)); - signature = gcLinkInstance.signature; - assert(signature.parameterTypes.length == 2); - assert(signature.parameterTypes[0] == this.options.usizeType); - assert(signature.parameterTypes[1] == this.options.usizeType); - assert(signature.returnType == Type.void); - - // __gc_mark(usize): void - element = this.elementsByName.get("__gc_mark"); - assert(element.kind == ElementKind.FUNCTION_PROTOTYPE); - let gcMarkInstance = assert(this.resolver.resolveFunction(element, null)); - signature = gcMarkInstance.signature; - assert(signature.parameterTypes.length == 1); - assert(signature.parameterTypes[0] == this.options.usizeType); - assert(signature.returnType == Type.void); - - this.gcAllocateInstance = gcAllocateInstance; - this.gcLinkInstance = gcLinkInstance; - this.gcMarkInstance = gcMarkInstance; - let gcHookOffset = 2 * options.usizeType.byteSize; // .next + .prev - this.gcHookOffset = gcHookOffset; - this.gcHeaderSize = (gcHookOffset + 4 + 7) & ~7; // + .hook index + alignment - this.hasGC = true; } // mark module exports, i.e. to apply proper wrapping behavior on the boundaries diff --git a/src/types.ts b/src/types.ts index b1682f97..0f7d6b11 100644 --- a/src/types.ts +++ b/src/types.ts @@ -147,7 +147,7 @@ export class Type { /** Tests if this is a managed type that needs GC hooks. */ isManaged(program: Program): bool { - if (program.hasGC) { + if (program.gcImplemented) { let classReference = this.classReference; return classReference !== null && !classReference.hasDecorator(DecoratorFlags.UNMANAGED); } diff --git a/std/assembly/array.ts b/std/assembly/array.ts index 4980c270..3d465a1b 100644 --- a/std/assembly/array.ts +++ b/std/assembly/array.ts @@ -1,5 +1,4 @@ -import { runtime, ArrayBufferView } from "./runtime"; -import { gc } from "./gc"; +import { ALLOCATE, REALLOCATE, DISCARD, LINK, REGISTER, MAX_BYTELENGTH, ArrayBufferView } from "./runtime"; import { ArrayBuffer } from "./arraybuffer"; import { COMPARATOR, SORT } from "./util/sort"; import { itoa, dtoa, itoa_stream, dtoa_stream, MAX_DOUBLE_LENGTH } from "./util/number"; @@ -8,7 +7,7 @@ import { isArray as builtin_isArray } from "./builtins"; export class Array extends ArrayBufferView { private length_: i32; - @inline static isArray(value: U): bool { + static isArray(value: U): bool { return builtin_isArray(value) && value !== null; } @@ -34,10 +33,10 @@ export class Array extends ArrayBufferView { var oldData = this.data; var oldCapacity = oldData.byteLength >>> alignof(); if (length > oldCapacity) { - const MAX_LENGTH = ArrayBufferView.MAX_BYTELENGTH >>> alignof(); + const MAX_LENGTH = MAX_BYTELENGTH >>> alignof(); if (length > MAX_LENGTH) throw new RangeError("Invalid array length"); let newCapacity = length << alignof(); - let newData = runtime.realloc(changetype(oldData), newCapacity); // registers on move + let newData = REALLOCATE(changetype(oldData), newCapacity); // registers on move if (newData !== changetype(oldData)) { this.data = changetype(newData); // links this.dataStart = newData; @@ -77,7 +76,7 @@ export class Array extends ArrayBufferView { private __set(index: i32, value: T): void { this.resize(index + 1); store(this.dataStart + (index << alignof()), value); - if (isManaged()) gc.link(value, this); + if (isManaged()) LINK(value, this); if (index >= this.length_) this.length_ = index + 1; } @@ -142,7 +141,7 @@ export class Array extends ArrayBufferView { this.resize(newLength); this.length_ = newLength; store(this.dataStart + ((newLength - 1) << alignof()), element); - if (isManaged()) gc.link(element, this); + if (isManaged()) LINK(element, this); return newLength; } @@ -157,14 +156,14 @@ export class Array extends ArrayBufferView { for (let offset: usize = 0; offset < thisSize; offset += sizeof()) { let element = load(thisStart + offset); store(outStart + offset, element); - gc.link(element, out); + LINK(element, out); } let otherStart = other.dataStart; let otherSize = otherLen << alignof(); for (let offset: usize = 0; offset < otherSize; offset += sizeof()) { let element = load(otherStart + offset); store(outStart + thisSize + offset, element); - gc.link(element, out); + LINK(element, out); } } else { memory.copy(outStart, this.dataStart, thisSize); @@ -222,7 +221,7 @@ export class Array extends ArrayBufferView { let value = load(this.dataStart + (index << alignof())); let result = callbackfn(value, index, this); store(outStart + (index << alignof()), result); - if (isManaged()) gc.link(result, out); + if (isManaged()) LINK(result, out); } return out; } @@ -294,7 +293,7 @@ export class Array extends ArrayBufferView { (newLength - 1) << alignof() ); store(base, element); - if (isManaged()) gc.link(element, this); + if (isManaged()) LINK(element, this); this.length_ = newLength; return newLength; } @@ -311,7 +310,7 @@ export class Array extends ArrayBufferView { let offset = i << alignof(); let element = load(thisBase + offset); store(sliceBase + offset, element); - if (isManaged()) gc.link(element, slice); + if (isManaged()) LINK(element, slice); } return slice; } @@ -327,7 +326,7 @@ export class Array extends ArrayBufferView { for (let i = 0; i < deleteCount; ++i) { let element = load(thisBase + (i << alignof())); store(spliceStart + (i << alignof()), element); - if (isManaged()) gc.link(element, splice); + if (isManaged()) LINK(element, splice); } memory.copy( splice.dataStart, @@ -397,7 +396,7 @@ export class Array extends ArrayBufferView { var sepLen = separator.length; var valueLen = 5; // max possible length of element len("false") var estLen = (valueLen + sepLen) * lastIndex + valueLen; - var result = runtime.alloc(estLen << 1); + var result = ALLOCATE(estLen << 1); var offset = 0; var value: bool; for (let i = 0; i < lastIndex; ++i) { @@ -429,10 +428,10 @@ export class Array extends ArrayBufferView { if (estLen > offset) { let trimmed = changetype(result).substring(0, offset); - runtime.freeUnregistered(result); + DISCARD(result); return trimmed; // registered in .substring } - return gc.register(result); + return REGISTER(result); } private join_int(separator: string = ","): string { @@ -445,7 +444,7 @@ export class Array extends ArrayBufferView { var sepLen = separator.length; const valueLen = (sizeof() <= 4 ? 10 : 20) + i32(isSigned()); var estLen = (valueLen + sepLen) * lastIndex + valueLen; - var result = runtime.alloc(estLen << 1); + var result = ALLOCATE(estLen << 1); var offset = 0; var value: T; for (let i = 0; i < lastIndex; ++i) { @@ -466,10 +465,10 @@ export class Array extends ArrayBufferView { offset += itoa_stream(result, offset, value); if (estLen > offset) { let trimmed = changetype(result).substring(0, offset); - runtime.freeUnregistered(result); + DISCARD(result); return trimmed; // registered in .substring } - return gc.register(result); + return REGISTER(result); } private join_flt(separator: string = ","): string { @@ -486,7 +485,7 @@ export class Array extends ArrayBufferView { const valueLen = MAX_DOUBLE_LENGTH; var sepLen = separator.length; var estLen = (valueLen + sepLen) * lastIndex + valueLen; - var result = runtime.alloc(estLen << 1); + var result = ALLOCATE(estLen << 1); var offset = 0; var value: T; for (let i = 0; i < lastIndex; ++i) { @@ -511,10 +510,10 @@ export class Array extends ArrayBufferView { ); if (estLen > offset) { let trimmed = changetype(result).substring(0, offset); - runtime.freeUnregistered(result); + DISCARD(result); return trimmed; // registered in .substring } - return gc.register(result); + return REGISTER(result); } private join_str(separator: string = ","): string { @@ -529,7 +528,7 @@ export class Array extends ArrayBufferView { estLen += load(dataStart + (i << alignof())).length; } var offset = 0; - var result = runtime.alloc((estLen + sepLen * lastIndex) << 1); + var result = ALLOCATE((estLen + sepLen * lastIndex) << 1); var value: String; for (let i = 0; i < lastIndex; ++i) { value = load(dataStart + (i << alignof())); @@ -560,7 +559,7 @@ export class Array extends ArrayBufferView { valueLen << 1 ); } - return gc.register(result); + return REGISTER(result); } private join_arr(separator: string = ","): string { @@ -597,7 +596,7 @@ export class Array extends ArrayBufferView { const valueLen = 15; // max possible length of element len("[object Object]") var sepLen = separator.length; var estLen = (valueLen + sepLen) * lastIndex + valueLen; - var result = runtime.alloc(estLen << 1); + var result = ALLOCATE(estLen << 1); var offset = 0; var value: T; for (let i = 0; i < lastIndex; ++i) { @@ -629,13 +628,12 @@ export class Array extends ArrayBufferView { } if (estLen > offset) { let out = changetype(result).substring(0, offset); - runtime.freeUnregistered(result); + DISCARD(result); return out; // registered in .substring } - return gc.register(result); + return REGISTER(result); } - @inline toString(): string { return this.join(); } diff --git a/std/assembly/arraybuffer.ts b/std/assembly/arraybuffer.ts index a7feab95..a9c3e1e9 100644 --- a/std/assembly/arraybuffer.ts +++ b/std/assembly/arraybuffer.ts @@ -1,5 +1,4 @@ -import { runtime, ArrayBufferView } from "./runtime"; -import { gc } from "./gc"; +import { ALLOCATE, REGISTER, HEADER, HEADER_SIZE, MAX_BYTELENGTH } from "./runtime"; @sealed export class ArrayBuffer { @@ -22,24 +21,24 @@ import { gc } from "./gc"; } constructor(length: i32) { - if (length > ArrayBufferView.MAX_BYTELENGTH) throw new RangeError("Invalid array buffer length"); - var buffer = runtime.alloc(length); + if (length > MAX_BYTELENGTH) throw new RangeError("Invalid array buffer length"); + var buffer = ALLOCATE(length); memory.fill(changetype(buffer), 0, length); - return gc.register(buffer); + return REGISTER(buffer); } get byteLength(): i32 { - return changetype(changetype(this) - runtime.Header.SIZE).payloadSize; + return changetype
(changetype(this) - HEADER_SIZE).payloadSize; } - slice(begin: i32 = 0, end: i32 = ArrayBufferView.MAX_BYTELENGTH): ArrayBuffer { + slice(begin: i32 = 0, end: i32 = MAX_BYTELENGTH): ArrayBuffer { var length = this.byteLength; begin = begin < 0 ? max(length + begin, 0) : min(begin, length); end = end < 0 ? max(length + end , 0) : min(end , length); var outSize = max(end - begin, 0); - var out = runtime.alloc(outSize); + var out = ALLOCATE(outSize); memory.copy(out, changetype(this) + begin, outSize); - return gc.register(out); + return REGISTER(out); } toString(): string { diff --git a/std/assembly/collector/itcm.ts b/std/assembly/collector/itcm.ts index 17d7a292..3c65ed5b 100644 --- a/std/assembly/collector/itcm.ts +++ b/std/assembly/collector/itcm.ts @@ -9,8 +9,8 @@ const TRACE = false; @inline export const HEADER_SIZE: usize = (offsetof() + AL_MASK) & ~AL_MASK; +import { ITERATEROOTS } from "../runtime"; import { AL_MASK, MAX_SIZE_32 } from "../util/allocator"; -import { gc } from "../gc"; /** Collector states. */ const enum State { @@ -140,7 +140,7 @@ function step(): void { } case State.IDLE: { if (TRACE) trace("gc~step/IDLE"); - gc.iterateRoots(__gc_mark); + ITERATEROOTS(__gc_mark); state = State.MARK; if (TRACE) trace("gc~state = MARK"); break; @@ -161,7 +161,7 @@ function step(): void { obj.hookFn(objToRef(obj)); } else { if (TRACE) trace("gc~step/MARK finish"); - gc.iterateRoots(__gc_mark); + ITERATEROOTS(__gc_mark); obj = iter.next; if (obj === toSpace) { let from = fromSpace; diff --git a/std/assembly/dataview.ts b/std/assembly/dataview.ts index 92fe52b8..bf44d415 100644 --- a/std/assembly/dataview.ts +++ b/std/assembly/dataview.ts @@ -1,5 +1,5 @@ +import { MAX_BYTELENGTH } from "./runtime"; import { ArrayBuffer } from "./arraybuffer"; -import { ArrayBufferView } from "./runtime"; export class DataView { @@ -13,7 +13,7 @@ export class DataView { byteLength: i32 = i32.MIN_VALUE // FIXME ) { if (byteLength === i32.MIN_VALUE) byteLength = buffer.byteLength - byteOffset; // FIXME - if (byteLength > ArrayBufferView.MAX_BYTELENGTH) throw new RangeError("Invalid byteLength"); + if (byteLength > MAX_BYTELENGTH) throw new RangeError("Invalid byteLength"); if (byteOffset + byteLength > buffer.byteLength) throw new RangeError("Invalid length"); this.data = buffer; // links var dataStart = changetype(buffer) + byteOffset; diff --git a/std/assembly/gc.ts b/std/assembly/gc.ts index 3749b84f..7dbc069d 100644 --- a/std/assembly/gc.ts +++ b/std/assembly/gc.ts @@ -1,45 +1,30 @@ -import { runtime } from "./runtime"; - /** Garbage collector interface. */ export namespace gc { /** Whether the garbage collector interface is implemented. */ // @ts-ignore: decorator @lazy - export const implemented: bool = isDefined( + export const IMPLEMENTED: bool = isDefined( // @ts-ignore: stub __gc_register ); - /** Gets the computed unique class id of a class type. */ - // @ts-ignore: decorator - @unsafe @builtin - export declare function classId(): u32; - - /** Iterates reference root objects. */ - // @ts-ignore: decorator - @unsafe @builtin - export declare function iterateRoots(fn: (ref: usize) => void): void; - /** Registers a managed object to be tracked by the garbage collector. */ // @ts-ignore: decorator @unsafe @inline - export function register(ref: usize): T { - runtime.unrefUnregistered(ref).classId = classId(); + export function register(ref: usize): void { // @ts-ignore: stub if (isDefined(__gc_register)) __gc_register(ref); - return changetype(ref); + else ERROR("missing implementation: gc.register"); } /** Links a registered object with the registered object now referencing it. */ // @ts-ignore: decorator @unsafe @inline - export function link(ref: T, parentRef: TParent): void { - assert(changetype(ref) >= HEAP_BASE + runtime.Header.SIZE); // must be a heap object - var header = changetype(changetype(ref) - runtime.Header.SIZE); - assert(header.classId != runtime.Header.MAGIC && header.gc1 != 0 && header.gc2 != 0); // must be registered + export function link(ref: usize, parentRef: usize): void { // @ts-ignore: stub if (isDefined(__gc_link)) __gc_link(ref, parentRef); + else ERROR("missing implementation: gc.link"); } /** Marks an object as being reachable. */ diff --git a/std/assembly/index.d.ts b/std/assembly/index.d.ts index 3a373a28..4b43f4ed 100644 --- a/std/assembly/index.d.ts +++ b/std/assembly/index.d.ts @@ -1515,36 +1515,16 @@ declare function unmanaged(constructor: Function): void; declare function sealed(constructor: Function): void; /** Annotates a method, function or constant global as always inlined. */ -declare function inline( - target: any, - propertyKey: string, - descriptor: TypedPropertyDescriptor -): TypedPropertyDescriptor | void; +declare function inline(...args: any[]): any; /** Annotates a method, function or constant global as unsafe. */ -declare function unsafe( - target: any, - propertyKey: string, - descriptor: TypedPropertyDescriptor -): TypedPropertyDescriptor | void; +declare function unsafe(...args: any[]): any; /** Annotates an explicit external name of a function or global. */ -declare function external(namespace: string, name: string): ( - target: any, - propertyKey: string, - descriptor: TypedPropertyDescriptor -) => TypedPropertyDescriptor | void; +declare function external(...args: any[]): any; /** Annotates a global for lazy compilation. */ -declare function lazy( - target: any, - propertyKey: string, - descriptor: TypedPropertyDescriptor -): TypedPropertyDescriptor | void; +declare function lazy(...args: any[]): any; /** Annotates a function as the explicit start function. */ -declare function start( - target: any, - propertyKey: string, - descriptor: TypedPropertyDescriptor -): TypedPropertyDescriptor | void; +declare function start(...args: any[]): any; diff --git a/std/assembly/map.ts b/std/assembly/map.ts index a389c700..5005ce33 100644 --- a/std/assembly/map.ts +++ b/std/assembly/map.ts @@ -1,4 +1,4 @@ -import { gc } from "./gc"; +import { LINK } from "./runtime"; import { HASH } from "./util/hash"; // A deterministic hash map based on CloseTable from https://github.com/jorendorff/dht @@ -8,12 +8,12 @@ import { HASH } from "./util/hash"; const INITIAL_CAPACITY = 4; // @ts-ignore: decorator -@inline const -FILL_FACTOR: f64 = 8 / 3; +@inline +const FILL_FACTOR: f64 = 8 / 3; // @ts-ignore: decorator -@inline const -FREE_FACTOR: f64 = 3 / 4; +@inline +const FREE_FACTOR: f64 = 3 / 4; /** Structure of a map entry. */ @unmanaged class MapEntry { @@ -124,8 +124,8 @@ export class Map { let bucketPtrBase = changetype(this.buckets) + (hashCode & this.bucketsMask) * BUCKET_SIZE; entry.taggedNext = load(bucketPtrBase); store(bucketPtrBase, changetype(entry)); - if (isManaged()) gc.link(key, this); - if (isManaged()) gc.link(value, this); + if (isManaged()) LINK(key, this); + if (isManaged()) LINK(value, this); } } diff --git a/std/assembly/runtime.ts b/std/assembly/runtime.ts index 55126e32..c943ccf8 100644 --- a/std/assembly/runtime.ts +++ b/std/assembly/runtime.ts @@ -1,135 +1,166 @@ import { AL_MASK, MAX_SIZE_32 } from "./util/allocator"; import { HEAP_BASE, memory } from "./memory"; -import { gc } from "./gc"; -/** Common runtime. */ -export namespace runtime { +/** Whether the memory manager interface is implemented. */ +// @ts-ignore: decorator, stub +@lazy export const MM_IMPLEMENTED: bool = isDefined(__memory_allocate); - /** Common runtime header of all objects. */ - @unmanaged export class Header { +/** Whether the garbage collector interface is implemented. */ +// @ts-ignore: decorator, stub +@lazy export const GC_IMPLEMENTED: bool = isDefined(__gc_register); - /** Size of a runtime header. */ - // @ts-ignore: decorator - @lazy @inline - static readonly SIZE: usize = gc.implemented - ? (offsetof( ) + AL_MASK) & ~AL_MASK // full header if GC is present - : (offsetof("gc1") + AL_MASK) & ~AL_MASK; // half header if GC is absent +/** Common runtime header. Each managed object has one. */ +@unmanaged export class HEADER { + /** Unique id of the respective class or a magic value if not yet registered.*/ + classId: u32; + /** Size of the allocated payload. */ + payloadSize: u32; + /** Reserved field for use by GC. Only present if GC is. */ + gc1: usize; // itcm: tagged next + /** Reserved field for use by GC. Only present if GC is. */ + gc2: usize; // itcm: prev +} - /** Magic value used to validate runtime headers. */ - // @ts-ignore: decorator - @lazy @inline - static readonly MAGIC: u32 = 0xA55E4B17; +/** Common runtime header size. */ +export const HEADER_SIZE: usize = GC_IMPLEMENTED + ? (offsetof
( ) + AL_MASK) & ~AL_MASK // full header if GC is present + : (offsetof
("gc1") + AL_MASK) & ~AL_MASK; // half header if GC is absent - /** Unique id of the respective class or a magic value if not yet registered.*/ - classId: u32; - /** Size of the allocated payload. */ - payloadSize: u32; - /** Reserved field for use by GC. Only present if GC is. */ - gc1: usize; // itcm: tagged next - /** Reserved field for use by GC. Only present if GC is. */ - gc2: usize; // itcm: prev +/** Common runtime header magic. Used to assert registered/unregistered status. */ +export const HEADER_MAGIC: u32 = 0xA55E4B17; + +/** Gets the computed unique class id of a class type. */ +// @ts-ignore: decorator +@unsafe @builtin +export declare function CLASSID(): u32; + +/** Iterates over all root objects of a reference type. */ +// @ts-ignore: decorator +@unsafe @builtin +export declare function ITERATEROOTS(fn: (ref: usize) => void): void; + +/** Adjusts an allocation to actual block size. Primarily targets TLSF. */ +export function ADJUST(payloadSize: usize): usize { + // round up to power of 2, e.g. with HEADER_SIZE=8: + // 0 -> 2^3 = 8 + // 1..8 -> 2^4 = 16 + // 9..24 -> 2^5 = 32 + // ... + // MAX_LENGTH -> 2^30 = 0x40000000 (MAX_SIZE_32) + return 1 << (32 - clz(payloadSize + HEADER_SIZE - 1)); +} + +/** Allocates a new object and returns a pointer to its payload. Does not fill. */ +// @ts-ignore: decorator +@unsafe +export function ALLOCATE(payloadSize: usize): usize { + var header = changetype
(memory.allocate(ADJUST(payloadSize))); + header.classId = HEADER_MAGIC; + header.payloadSize = payloadSize; + if (GC_IMPLEMENTED) { + header.gc1 = 0; + header.gc2 = 0; } + return changetype(header) + HEADER_SIZE; +} - // Note that header data and layout isn't quite optimal depending on which allocator one - // decides to use, but it's done this way for maximum flexibility. Also remember that the - // runtime will most likely change significantly once reftypes and WASM GC are a thing. - - /** Adjusts an allocation to actual block size. Primarily targets TLSF. */ - function adjust(payloadSize: usize): usize { - // round up to power of 2, e.g. with HEADER_SIZE=8: - // 0 -> 2^3 = 8 - // 1..8 -> 2^4 = 16 - // 9..24 -> 2^5 = 32 - // ... - // MAX_LENGTH -> 2^30 = 0x40000000 (MAX_SIZE_32) - return 1 << (32 - clz(payloadSize + Header.SIZE - 1)); - } - - /** Allocates a new object and returns a pointer to its payload. Does not fill. */ - // @ts-ignore: decorator - @unsafe - export function alloc(payloadSize: u32): usize { - var header = changetype
(memory.allocate(adjust(payloadSize))); - header.classId = Header.MAGIC; - header.payloadSize = payloadSize; - if (gc.implemented) { - header.gc1 = 0; - header.gc2 = 0; - } - return changetype(header) + Header.SIZE; - } - - /** Reallocates an object if necessary. Returns a pointer to its (moved) payload. */ - // @ts-ignore: decorator - @unsafe - export function realloc(ref: usize, newPayloadSize: u32): usize { - // Background: When managed objects are allocated these aren't immediately registered with GC - // but can be used as scratch objects while unregistered. This is useful in situations where - // the object must be reallocated multiple times because its final size isn't known beforehand, - // e.g. in Array#filter, with only the final object making it into GC'ed userland. - var header = changetype
(ref - Header.SIZE); - var payloadSize = header.payloadSize; - if (payloadSize < newPayloadSize) { - let newAdjustedSize = adjust(newPayloadSize); - if (select(adjust(payloadSize), 0, ref > HEAP_BASE) < newAdjustedSize) { - // move if the allocation isn't large enough or not a heap object - let newHeader = changetype
(memory.allocate(newAdjustedSize)); - newHeader.classId = header.classId; - if (gc.implemented) { - newHeader.gc1 = 0; - newHeader.gc2 = 0; - } - let newRef = changetype(newHeader) + Header.SIZE; - memory.copy(newRef, ref, payloadSize); - memory.fill(newRef + payloadSize, 0, newPayloadSize - payloadSize); - if (header.classId == Header.MAGIC) { - // free right away if not registered yet - assert(ref > HEAP_BASE); // static objects aren't scratch objects - memory.free(changetype(header)); - } else if (gc.implemented) { - // if previously registered, register again - // @ts-ignore: stub - __gc_register(ref); - } - header = newHeader; - ref = newRef; - } else { - // otherwise just clear additional memory within this block - memory.fill(ref + payloadSize, 0, newPayloadSize - payloadSize); +/** Reallocates an object if necessary. Returns a pointer to its (moved) payload. */ +// @ts-ignore: decorator +@unsafe +export function REALLOCATE(ref: usize, newPayloadSize: usize): usize { + // Background: When managed objects are allocated these aren't immediately registered with GC + // but can be used as scratch objects while unregistered. This is useful in situations where + // the object must be reallocated multiple times because its final size isn't known beforehand, + // e.g. in Array#filter, with only the final object making it into GC'ed userland. + var header = changetype
(ref - HEADER_SIZE); + var payloadSize = header.payloadSize; + if (payloadSize < newPayloadSize) { + let newAdjustedSize = ADJUST(newPayloadSize); + if (select(ADJUST(payloadSize), 0, ref > HEAP_BASE) < newAdjustedSize) { + // move if the allocation isn't large enough or not a heap object + let newHeader = changetype
(memory.allocate(newAdjustedSize)); + newHeader.classId = header.classId; + if (GC_IMPLEMENTED) { + newHeader.gc1 = 0; + newHeader.gc2 = 0; } + let newRef = changetype(newHeader) + HEADER_SIZE; + memory.copy(newRef, ref, payloadSize); + memory.fill(newRef + payloadSize, 0, newPayloadSize - payloadSize); + if (header.classId == HEADER_MAGIC) { + // free right away if not registered yet + assert(ref > HEAP_BASE); // static objects aren't scratch objects + memory.free(changetype(header)); + } else if (GC_IMPLEMENTED) { + // if previously registered, register again + // @ts-ignore: stub + __gc_register(ref); + } + header = newHeader; + ref = newRef; } else { - // if the size is the same or less, just update the header accordingly. - // unused space is cleared when grown, so no need to do this here. + // otherwise just clear additional memory within this block + memory.fill(ref + payloadSize, 0, newPayloadSize - payloadSize); } - header.payloadSize = newPayloadSize; - return ref; + } else { + // if the size is the same or less, just update the header accordingly. + // unused space is cleared when grown, so no need to do this here. } + header.payloadSize = newPayloadSize; + return ref; +} - // @ts-ignore: decorator - @unsafe - export function unrefUnregistered(ref: usize): Header { - assert(ref >= HEAP_BASE + Header.SIZE); // must be a heap object - var header = changetype
(ref - Header.SIZE); - assert(header.classId == Header.MAGIC); // must be unregistered - return header; - } +/** Registers a managed object to be tracked by the garbage collector, if present. */ +// @ts-ignore: decorator +@unsafe @inline +export function REGISTER(ref: usize): T { + ASSERT_UNREGISTERED(ref); + changetype
(ref - HEADER_SIZE).classId = CLASSID(); + // @ts-ignore: stub + if (GC_IMPLEMENTED) __gc_register(ref); + return changetype(ref); +} - /** Frees an unregistered object that turned out to be unnecessary. */ - // @ts-ignore: decorator - @unsafe @inline - export function freeUnregistered(ref: T): void { - memory.free(changetype(unrefUnregistered(changetype(ref)))); - } +/** Links a registered object with the (registered) object now referencing it. */ +// @ts-ignore: decorator +@unsafe @inline +export function LINK(ref: T, parentRef: TParent): void { + ASSERT_REGISTERED(changetype(ref)); + ASSERT_REGISTERED(changetype(parentRef)); + // @ts-ignore: stub + if (GC_IMPLEMENTED) __gc_link(changetype(ref), changetype(parentRef)); +} + +/** Discards an unregistered object that turned out to be unnecessary. */ +// @ts-ignore: decorator +export function DISCARD(ref: usize): void { + ASSERT_UNREGISTERED(ref); + memory.free(changetype(ref - HEADER_SIZE)); +} + +// Helpers + +/** Asserts that a managed object is still unregistered. */ +function ASSERT_UNREGISTERED(ref: usize): void { + assert(ref > HEAP_BASE); // must be a heap object + assert(changetype
(ref - HEADER_SIZE).classId == HEADER_MAGIC); +} + +/** Asserts that a managed object has already been registered. */ +function ASSERT_REGISTERED(ref: usize): void { + assert(ref > HEAP_BASE); // must be a heap object + assert(changetype
(ref - HEADER_SIZE).classId != HEADER_MAGIC); } import { ArrayBuffer } from "./arraybuffer"; +/** Maximum byte length of any buffer. */ +// @ts-ignore: decorator +@lazy +export const MAX_BYTELENGTH: i32 = MAX_SIZE_32 - HEADER_SIZE; + +/** Hard wired ArrayBufferView interface. */ export abstract class ArrayBufferView { - - // @ts-ignore: decorator - @lazy - static readonly MAX_BYTELENGTH: i32 = MAX_SIZE_32 - runtime.Header.SIZE; - [key: number]: number; // @ts-ignore: decorator @@ -145,7 +176,7 @@ export abstract class ArrayBufferView { dataEnd: usize; constructor(length: i32, alignLog2: i32) { - if (length > ArrayBufferView.MAX_BYTELENGTH >>> alignLog2) throw new RangeError("Invalid length"); + if (length > MAX_BYTELENGTH >>> alignLog2) throw new RangeError("Invalid length"); var buffer = new ArrayBuffer(length << alignLog2); this.data = buffer; this.dataStart = changetype(buffer); @@ -161,7 +192,7 @@ export abstract class ArrayBufferView { } get length(): i32 { - ERROR("missing implementation: [T extends ArrayBufferView]#length"); + ERROR("missing implementation: subclasses must implement ArrayBufferView#length"); return unreachable(); } } diff --git a/std/assembly/set.ts b/std/assembly/set.ts index 8d0b8963..804fa16e 100644 --- a/std/assembly/set.ts +++ b/std/assembly/set.ts @@ -1,4 +1,4 @@ -import { gc } from "./gc"; +import { LINK } from "./runtime"; import { HASH } from "./util/hash"; // A deterministic hash set based on CloseTable from https://github.com/jorendorff/dht @@ -114,7 +114,7 @@ export class Set { let bucketPtrBase = changetype(this.buckets) + (hashCode & this.bucketsMask) * BUCKET_SIZE; entry.taggedNext = load(bucketPtrBase); store(bucketPtrBase, changetype(entry)); - if (isManaged()) gc.link(key, this); + if (isManaged()) LINK(key, this); } } diff --git a/std/assembly/string.ts b/std/assembly/string.ts index c9ead2fb..64c02c1e 100644 --- a/std/assembly/string.ts +++ b/std/assembly/string.ts @@ -1,29 +1,26 @@ -import { runtime } from "./runtime"; -import { gc } from "./gc"; +import { ALLOCATE, REGISTER, HEADER, HEADER_SIZE } from "./runtime"; import { MAX_SIZE_32 } from "./util/allocator"; import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./util/string"; @sealed export abstract class String { - // @ts-ignore: decorator - @lazy - static readonly MAX_LENGTH: i32 = (MAX_SIZE_32 - runtime.Header.SIZE) >> alignof(); + @lazy static readonly MAX_LENGTH: i32 = (MAX_SIZE_32 - HEADER_SIZE) >> alignof(); get length(): i32 { - return changetype(changetype(this) - runtime.Header.SIZE).payloadSize >> 1; + return changetype
(changetype(this) - HEADER_SIZE).payloadSize >> 1; } // TODO Add and handle second argument static fromCharCode(code: i32): String { - var out = runtime.alloc(2); + var out = ALLOCATE(2); store(out, code); - return gc.register(out); + return REGISTER(out); } static fromCodePoint(code: i32): String { assert(code <= 0x10FFFF); var sur = code > 0xFFFF; - var out = runtime.alloc((i32(sur) + 1) << 1); + var out = ALLOCATE((i32(sur) + 1) << 1); if (!sur) { store(out, code); } else { @@ -32,15 +29,15 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut let lo: u32 = (code & 0x3FF) + 0xDC00; store(out, (hi << 16) | lo); } - return gc.register(out); + return REGISTER(out); } @operator("[]") charAt(pos: i32): String { assert(this !== null); if (pos >= this.length) return changetype(""); - var out = runtime.alloc(2); + var out = ALLOCATE(2); store(out, load(changetype(this) + (pos << 1))); - return gc.register(out); + return REGISTER(out); } charCodeAt(pos: i32): i32 { @@ -71,10 +68,10 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut var otherSize: isize = other.length << 1; var outSize: usize = thisSize + otherSize; if (outSize == 0) return changetype(""); - var out = runtime.alloc(outSize); + var out = ALLOCATE(outSize); memory.copy(out, changetype(this), thisSize); memory.copy(out + thisSize, changetype(other), otherSize); - return gc.register(out); + return REGISTER(out); } endsWith(searchString: String, endPosition: i32 = String.MAX_LENGTH): bool { @@ -184,9 +181,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut if (intStart < 0) intStart = max(size + intStart, 0); var resultLength = min(max(end, 0), size - intStart); if (resultLength <= 0) return changetype(""); - var out = runtime.alloc(resultLength << 1); + var out = ALLOCATE(resultLength << 1); memory.copy(out, changetype(this) + intStart, resultLength); - return gc.register(out); + return REGISTER(out); } substring(start: i32, end: i32 = i32.MAX_VALUE): String { @@ -199,9 +196,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut len = toPos - fromPos; if (!len) return changetype(""); if (!fromPos && toPos == this.length << 1) return this; - var out = runtime.alloc(len); + var out = ALLOCATE(len); memory.copy(out, changetype(this) + fromPos, len); - return gc.register(out); + return REGISTER(out); } trim(): String { @@ -227,9 +224,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut } if (!size) return changetype(""); if (!start && size == length << 1) return this; - var out = runtime.alloc(size); + var out = ALLOCATE(size); memory.copy(out, changetype(this) + offset, size); - return gc.register(out); + return REGISTER(out); } @inline @@ -257,9 +254,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut if (!offset) return this; size -= offset; if (!size) return changetype(""); - var out = runtime.alloc(size); + var out = ALLOCATE(size); memory.copy(out, changetype(this) + offset, size); - return gc.register(out); + return REGISTER(out); } trimEnd(): String { @@ -276,9 +273,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut } if (!size) return changetype(""); if (size == originalSize) return this; - var out = runtime.alloc(size); + var out = ALLOCATE(size); memory.copy(out, changetype(this), size); - return gc.register(out); + return REGISTER(out); } padStart(targetLength: i32, padString: string = " "): String { @@ -288,7 +285,7 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut var padSize = padString.length << 1; if (targetSize < thisSize || !padSize) return this; var prependSize = targetSize - thisSize; - var out = runtime.alloc(targetSize); + var out = ALLOCATE(targetSize); if (prependSize > padSize) { let repeatCount = (prependSize - 2) / padSize; let restBase = repeatCount * padSize; @@ -299,7 +296,7 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut memory.copy(out, changetype(padString), prependSize); } memory.copy(out + prependSize, changetype(this), thisSize); - return gc.register(out); + return REGISTER(out); } padEnd(targetLength: i32, padString: string = " "): String { @@ -309,7 +306,7 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut var padSize = padString.length << 1; if (targetSize < thisSize || !padSize) return this; var appendSize = targetSize - thisSize; - var out = runtime.alloc(targetSize); + var out = ALLOCATE(targetSize); memory.copy(out, changetype(this), thisSize); if (appendSize > padSize) { let repeatCount = (appendSize - 2) / padSize; @@ -320,7 +317,7 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut } else { memory.copy(out + thisSize, changetype(padString), appendSize); } - return gc.register(out); + return REGISTER(out); } repeat(count: i32 = 0): String { @@ -334,9 +331,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut if (count == 0 || !length) return changetype(""); if (count == 1) return this; - var out = runtime.alloc((length * count) << 1); + var out = ALLOCATE((length * count) << 1); memory.repeat(out, changetype(this), length << 1, count); - return gc.register(out); + return REGISTER(out); } slice(beginIndex: i32, endIndex: i32 = i32.MAX_VALUE): String { @@ -345,9 +342,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut var end = endIndex < 0 ? max(endIndex + len, 0) : min(endIndex, len); len = end - begin; if (len <= 0) return changetype(""); - var out = runtime.alloc(len << 1); + var out = ALLOCATE(len << 1); memory.copy(out, changetype(this) + (begin << 1), len << 1); - return gc.register(out); + return REGISTER(out); } split(separator: String | null = null, limit: i32 = i32.MAX_VALUE): String[] { @@ -365,7 +362,7 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut let buffer = unreachable(); // TODO // let buffer = result.buffer_; for (let i: isize = 0; i < length; ++i) { - let char = runtime.alloc(2); + let char = ALLOCATE(2); store( changetype(char), load( @@ -385,9 +382,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut while ((end = this.indexOf(separator!, start)) != -1) { let len = end - start; if (len > 0) { - let out = runtime.alloc(len << 1); + let out = ALLOCATE(len << 1); memory.copy(out, changetype(this) + (start << 1), len << 1); - result.push(gc.register(out)); + result.push(REGISTER(out)); } else { result.push(changetype("")); } @@ -401,9 +398,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut } var len = length - start; if (len > 0) { - let out = runtime.alloc(len << 1); + let out = ALLOCATE(len << 1); memory.copy(out, changetype(this) + (start << 1), len << 1); - result.push(gc.register(out)); + result.push(REGISTER(out)); } else { result.push(changetype("")); } @@ -475,10 +472,10 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut } } assert(ptrPos == len); - var out = runtime.alloc(bufPos); + var out = ALLOCATE(bufPos); memory.copy(changetype(out), buf, bufPos); memory.free(buf); - return gc.register(out); + return REGISTER(out); } toUTF8(): usize { diff --git a/std/assembly/typedarray.ts b/std/assembly/typedarray.ts index f7b653b8..b830eebb 100644 --- a/std/assembly/typedarray.ts +++ b/std/assembly/typedarray.ts @@ -1,5 +1,4 @@ -import { runtime, ArrayBufferView } from "./runtime"; -import { gc } from "./gc"; +import { ALLOCATE, REGISTER, LINK, ArrayBufferView } from "./runtime"; import { COMPARATOR, SORT as SORT_IMPL } from "./util/sort"; function clampToByte(value: i32): i32 { @@ -804,11 +803,11 @@ function SUBARRAY( else begin = min(begin, length); if (end < 0) end = max(length + end, begin); else end = max(min(end, length), begin); - var out = runtime.alloc(offsetof()); + var out = ALLOCATE(offsetof()); store(out, buffer, offsetof("buffer")); store(out, array.dataStart + (begin << alignof()) , offsetof("dataStart")); store(out, array.dataEnd + ((end - begin) << alignof()), offsetof("dataEnd")); - gc.link(buffer, gc.register(out)); // register first, then link + LINK(buffer, REGISTER(out)); // register first, then link return changetype(out); } diff --git a/std/assembly/util/number.ts b/std/assembly/util/number.ts index e62a40bc..1156f8a1 100644 --- a/std/assembly/util/number.ts +++ b/std/assembly/util/number.ts @@ -1,5 +1,4 @@ -import { runtime, ArrayBufferView } from "../runtime"; -import { gc } from "../gc"; +import { ALLOCATE, REGISTER, DISCARD, ArrayBufferView } from "../runtime"; import { CharCode } from "./string"; // @ts-ignore: decorator @@ -263,10 +262,10 @@ export function utoa32(value: u32): String { if (!value) return "0"; var decimals = decimalCount32(value); - var out = runtime.alloc(decimals << 1); + var out = ALLOCATE(decimals << 1); utoa32_core(changetype(out), value, decimals); - return gc.register(out); + return REGISTER(out); } export function itoa32(value: i32): String { @@ -276,12 +275,12 @@ export function itoa32(value: i32): String { if (sign) value = -value; var decimals = decimalCount32(value) + u32(sign); - var out = runtime.alloc(decimals << 1); + var out = ALLOCATE(decimals << 1); utoa32_core(changetype(out), value, decimals); if (sign) store(changetype(out), CharCode.MINUS); - return gc.register(out); + return REGISTER(out); } export function utoa64(value: u64): String { @@ -291,14 +290,14 @@ export function utoa64(value: u64): String { if (value <= u32.MAX_VALUE) { let val32 = value; let decimals = decimalCount32(val32); - out = runtime.alloc(decimals << 1); + out = ALLOCATE(decimals << 1); utoa32_core(out, val32, decimals); } else { let decimals = decimalCount64(value); - out = runtime.alloc(decimals << 1); + out = ALLOCATE(decimals << 1); utoa64_core(changetype(out), value, decimals); } - return gc.register(out); + return REGISTER(out); } export function itoa64(value: i64): String { @@ -311,16 +310,16 @@ export function itoa64(value: i64): String { if (value <= u32.MAX_VALUE) { let val32 = value; let decimals = decimalCount32(val32) + u32(sign); - out = runtime.alloc(decimals << 1); + out = ALLOCATE(decimals << 1); utoa32_core(changetype(out), val32, decimals); } else { let decimals = decimalCount64(value) + u32(sign); - out = runtime.alloc(decimals << 1); + out = ALLOCATE(decimals << 1); utoa64_core(changetype(out), value, decimals); } if (sign) store(changetype(out), CharCode.MINUS); - return gc.register(out); + return REGISTER(out); } export function itoa(value: T): String { @@ -625,10 +624,10 @@ export function dtoa(value: f64): String { if (isNaN(value)) return "NaN"; return select("-Infinity", "Infinity", value < 0); } - var temp = runtime.alloc(MAX_DOUBLE_LENGTH << 1); + var temp = ALLOCATE(MAX_DOUBLE_LENGTH << 1); var length = dtoa_core(temp, value); var result = changetype(temp).substring(0, length); - runtime.freeUnregistered(temp); + DISCARD(temp); return result; } diff --git a/tests/compiler/std/runtime.optimized.wat b/tests/compiler/std/runtime.optimized.wat index eac3b40a..352c2ced 100644 --- a/tests/compiler/std/runtime.optimized.wat +++ b/tests/compiler/std/runtime.optimized.wat @@ -1166,7 +1166,7 @@ local.get $2 call $~lib/allocator/tlsf/Root#use ) - (func $~lib/runtime/runtime.alloc (; 17 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/runtime/ALLOCATE (; 17 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) (local $1 i32) i32.const 1 i32.const 32 @@ -2536,7 +2536,7 @@ end end ) - (func $~lib/runtime/runtime.realloc (; 22 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) + (func $~lib/runtime/REALLOCATE (; 22 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) (local $2 i32) (local $3 i32) (local $4 i32) @@ -2611,8 +2611,8 @@ if i32.const 0 i32.const 184 - i32.const 87 - i32.const 10 + i32.const 92 + i32.const 8 call $~lib/env/abort unreachable end @@ -2641,34 +2641,32 @@ i32.store offset=4 local.get $0 ) - (func $~lib/runtime/runtime.unrefUnregistered (; 23 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/runtime/ASSERT_UNREGISTERED (; 23 ;) (type $FUNCSIG$vi) (param $0 i32) local.get $0 - i32.const 232 - i32.lt_u + i32.const 216 + i32.le_u if i32.const 0 i32.const 184 - i32.const 111 - i32.const 4 + i32.const 145 + i32.const 2 call $~lib/env/abort unreachable end local.get $0 i32.const 16 i32.sub - local.tee $0 i32.load i32.const -1520547049 i32.ne if i32.const 0 i32.const 184 - i32.const 113 - i32.const 4 + i32.const 146 + i32.const 2 call $~lib/env/abort unreachable end - local.get $0 ) (func $start:std/runtime (; 24 ;) (type $FUNCSIG$v) (local $0 i32) @@ -2710,7 +2708,7 @@ else i32.const 0 i32.const 72 - i32.const 30 + i32.const 31 i32.const 2 call $~lib/env/abort unreachable @@ -2808,7 +2806,7 @@ f64.const 0 call $~lib/env/trace i32.const 1 - call $~lib/runtime/runtime.alloc + call $~lib/runtime/ALLOCATE global.set $std/runtime/ref1 global.get $std/runtime/ref1 i32.const 16 @@ -2821,7 +2819,7 @@ if i32.const 0 i32.const 72 - i32.const 45 + i32.const 46 i32.const 0 call $~lib/env/abort unreachable @@ -2833,7 +2831,7 @@ if i32.const 0 i32.const 72 - i32.const 46 + i32.const 47 i32.const 0 call $~lib/env/abort unreachable @@ -2842,12 +2840,12 @@ local.tee $0 local.get $0 global.get $std/runtime/barrier1 - call $~lib/runtime/runtime.realloc + call $~lib/runtime/REALLOCATE i32.ne if i32.const 0 i32.const 72 - i32.const 47 + i32.const 48 i32.const 0 call $~lib/env/abort unreachable @@ -2859,14 +2857,14 @@ if i32.const 0 i32.const 72 - i32.const 48 + i32.const 49 i32.const 0 call $~lib/env/abort unreachable end global.get $std/runtime/ref1 global.get $std/runtime/barrier2 - call $~lib/runtime/runtime.realloc + call $~lib/runtime/REALLOCATE global.set $std/runtime/ref2 global.get $std/runtime/ref1 global.get $std/runtime/ref2 @@ -2874,7 +2872,7 @@ if i32.const 0 i32.const 72 - i32.const 50 + i32.const 51 i32.const 0 call $~lib/env/abort unreachable @@ -2890,16 +2888,20 @@ if i32.const 0 i32.const 72 - i32.const 52 + i32.const 53 i32.const 0 call $~lib/env/abort unreachable end global.get $std/runtime/ref2 - call $~lib/runtime/runtime.unrefUnregistered + local.tee $0 + call $~lib/runtime/ASSERT_UNREGISTERED + local.get $0 + i32.const 16 + i32.sub call $~lib/memory/memory.free global.get $std/runtime/barrier2 - call $~lib/runtime/runtime.alloc + call $~lib/runtime/ALLOCATE global.set $std/runtime/ref3 global.get $std/runtime/ref1 global.get $std/runtime/ref3 @@ -2907,17 +2909,20 @@ if i32.const 0 i32.const 72 - i32.const 55 + i32.const 56 i32.const 0 call $~lib/env/abort unreachable end global.get $std/runtime/barrier1 - call $~lib/runtime/runtime.alloc + call $~lib/runtime/ALLOCATE global.set $std/runtime/ref4 global.get $std/runtime/ref4 local.tee $0 - call $~lib/runtime/runtime.unrefUnregistered + call $~lib/runtime/ASSERT_UNREGISTERED + local.get $0 + i32.const 16 + i32.sub i32.const 2 i32.store local.get $0 @@ -2928,7 +2933,7 @@ if i32.const 0 i32.const 72 - i32.const 59 + i32.const 60 i32.const 0 call $~lib/env/abort unreachable @@ -2944,7 +2949,7 @@ if i32.const 0 i32.const 72 - i32.const 61 + i32.const 62 i32.const 0 call $~lib/env/abort unreachable @@ -2956,13 +2961,13 @@ if i32.const 0 i32.const 72 - i32.const 62 + i32.const 63 i32.const 0 call $~lib/env/abort unreachable end i32.const 10 - call $~lib/runtime/runtime.alloc + call $~lib/runtime/ALLOCATE global.set $std/runtime/ref5 global.get $std/runtime/ref5 i32.const 16 @@ -2973,7 +2978,7 @@ if i32.const 0 i32.const 72 - i32.const 65 + i32.const 66 i32.const 0 call $~lib/env/abort unreachable @@ -2989,7 +2994,7 @@ if i32.const 0 i32.const 72 - i32.const 66 + i32.const 67 i32.const 0 call $~lib/env/abort unreachable diff --git a/tests/compiler/std/runtime.ts b/tests/compiler/std/runtime.ts index 4c30e3f1..680eac88 100644 --- a/tests/compiler/std/runtime.ts +++ b/tests/compiler/std/runtime.ts @@ -1,4 +1,5 @@ import "allocator/tlsf"; +import { CLASSID, ADJUST, ALLOCATE, REALLOCATE, REGISTER, DISCARD, HEADER, HEADER_SIZE, HEADER_MAGIC } from "runtime"; var register_ref: usize = 0; @@ -19,48 +20,48 @@ var link_parentRef: usize = 0; class A {} class B {} -assert(gc.classId() != gc.classId()); +assert(CLASSID() != CLASSID()); function isPowerOf2(x: i32): bool { return x != 0 && (x & (x - 1)) == 0; } -assert(runtime.adjust(0) > 0); +assert(ADJUST(0) > 0); for (let i = 0; i < 9000; ++i) { - assert(isPowerOf2(runtime.adjust(i))); + assert(isPowerOf2(ADJUST(i))); } -var barrier1 = runtime.adjust(0); +var barrier1 = ADJUST(0); var barrier2 = barrier1 + 1; -while (runtime.adjust(barrier2 + 1) == runtime.adjust(barrier2)) ++barrier2; +while (ADJUST(barrier2 + 1) == ADJUST(barrier2)) ++barrier2; var barrier3 = barrier2 + 1; -while (runtime.adjust(barrier3 + 1) == runtime.adjust(barrier3)) ++barrier3; +while (ADJUST(barrier3 + 1) == ADJUST(barrier3)) ++barrier3; trace("barrier1", 1, barrier1); trace("barrier2", 1, barrier2); trace("barrier3", 1, barrier3); -var ref1 = runtime.alloc(1); -var header1 = changetype(ref1 - runtime.Header.SIZE); -assert(header1.classId == runtime.Header.MAGIC); +var ref1 = ALLOCATE(1); +var header1 = changetype
(ref1 - HEADER_SIZE); +assert(header1.classId == HEADER_MAGIC); assert(header1.payloadSize == 1); -assert(ref1 == runtime.realloc(ref1, barrier1)); // same segment +assert(ref1 == REALLOCATE(ref1, barrier1)); // same segment assert(header1.payloadSize == barrier1); -var ref2 = runtime.realloc(ref1, barrier2); +var ref2 = REALLOCATE(ref1, barrier2); assert(ref1 != ref2); // moves -var header2 = changetype(ref2 - runtime.Header.SIZE); +var header2 = changetype
(ref2 - HEADER_SIZE); assert(header2.payloadSize == barrier2); -runtime.freeUnregistered(ref2); -var ref3 = runtime.alloc(barrier2); +DISCARD(ref2); +var ref3 = ALLOCATE(barrier2); assert(ref1 == ref3); // reuses space of ref1 (free'd in realloc), ref2 (explicitly free'd) -var ref4 = runtime.alloc(barrier1); -gc.register(ref4); // should call __gc_register +var ref4 = ALLOCATE(barrier1); +REGISTER(ref4); // should call __gc_register assert(register_ref == ref4); -var header4 = changetype(register_ref - runtime.Header.SIZE); -assert(header4.classId == gc.classId()); +var header4 = changetype
(register_ref - HEADER_SIZE); +assert(header4.classId == CLASSID()); assert(header4.payloadSize == barrier1); -var ref5 = runtime.alloc(10); +var ref5 = ALLOCATE(10); assert(changetype(ref5).byteLength == 10); assert(changetype(ref5).length == 5); diff --git a/tests/compiler/std/runtime.untouched.wat b/tests/compiler/std/runtime.untouched.wat index 491839db..2409d5b2 100644 --- a/tests/compiler/std/runtime.untouched.wat +++ b/tests/compiler/std/runtime.untouched.wat @@ -36,10 +36,12 @@ (global $~lib/allocator/tlsf/Root.HL_END i32 (i32.const 2912)) (global $~lib/allocator/tlsf/Root.SIZE i32 (i32.const 2916)) (global $~lib/allocator/tlsf/ROOT (mut i32) (i32.const 0)) + (global $~lib/runtime/GC_IMPLEMENTED i32 (i32.const 1)) + (global $~lib/runtime/HEADER_SIZE i32 (i32.const 16)) + (global $~lib/runtime/HEADER_MAGIC i32 (i32.const -1520547049)) (global $std/runtime/register_ref (mut i32) (i32.const 0)) (global $std/runtime/link_ref (mut i32) (i32.const 0)) (global $std/runtime/link_parentRef (mut i32) (i32.const 0)) - (global $~lib/gc/gc.implemented i32 (i32.const 1)) (global $std/runtime/barrier1 (mut i32) (i32.const 0)) (global $std/runtime/barrier2 (mut i32) (i32.const 0)) (global $std/runtime/barrier3 (mut i32) (i32.const 0)) @@ -71,11 +73,11 @@ unreachable end ) - (func $~lib/runtime/runtime.adjust (; 3 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/runtime/ADJUST (; 3 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) i32.const 1 i32.const 32 local.get $0 - i32.const 16 + global.get $~lib/runtime/HEADER_SIZE i32.add i32.const 1 i32.sub @@ -1467,14 +1469,14 @@ end return ) - (func $~lib/runtime/runtime.alloc (; 23 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (func $~lib/runtime/ALLOCATE (; 23 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) (local $1 i32) local.get $0 - call $~lib/runtime/runtime.adjust + call $~lib/runtime/ADJUST call $~lib/memory/memory.allocate local.set $1 local.get $1 - i32.const -1520547049 + global.get $~lib/runtime/HEADER_MAGIC i32.store local.get $1 local.get $0 @@ -1486,7 +1488,7 @@ i32.const 0 i32.store offset=12 local.get $1 - i32.const 16 + global.get $~lib/runtime/HEADER_SIZE i32.add ) (func $~lib/util/memory/memcpy (; 24 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32) @@ -3243,14 +3245,14 @@ local.get $0 global.set $std/runtime/register_ref ) - (func $~lib/runtime/runtime.realloc (; 29 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) + (func $~lib/runtime/REALLOCATE (; 29 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) (local $2 i32) (local $3 i32) (local $4 i32) (local $5 i32) (local $6 i32) local.get $0 - i32.const 16 + global.get $~lib/runtime/HEADER_SIZE i32.sub local.set $2 local.get $2 @@ -3261,10 +3263,10 @@ i32.lt_u if local.get $1 - call $~lib/runtime/runtime.adjust + call $~lib/runtime/ADJUST local.set $4 local.get $3 - call $~lib/runtime/runtime.adjust + call $~lib/runtime/ADJUST i32.const 0 local.get $0 global.get $~lib/memory/HEAP_BASE @@ -3287,7 +3289,7 @@ i32.const 0 i32.store offset=12 local.get $5 - i32.const 16 + global.get $~lib/runtime/HEADER_SIZE i32.add local.set $6 local.get $6 @@ -3304,7 +3306,7 @@ call $~lib/memory/memory.fill local.get $2 i32.load - i32.const -1520547049 + global.get $~lib/runtime/HEADER_MAGIC i32.eq if local.get $0 @@ -3314,8 +3316,8 @@ if i32.const 0 i32.const 184 - i32.const 87 - i32.const 10 + i32.const 92 + i32.const 8 call $~lib/env/abort unreachable end @@ -3347,55 +3349,52 @@ i32.store offset=4 local.get $0 ) - (func $~lib/runtime/runtime.unrefUnregistered (; 30 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) - (local $1 i32) + (func $~lib/runtime/ASSERT_UNREGISTERED (; 30 ;) (type $FUNCSIG$vi) (param $0 i32) local.get $0 global.get $~lib/memory/HEAP_BASE - i32.const 16 - i32.add - i32.ge_u + i32.gt_u i32.eqz if i32.const 0 i32.const 184 - i32.const 111 - i32.const 4 + i32.const 145 + i32.const 2 call $~lib/env/abort unreachable end local.get $0 - i32.const 16 + global.get $~lib/runtime/HEADER_SIZE i32.sub - local.set $1 - local.get $1 i32.load - i32.const -1520547049 + global.get $~lib/runtime/HEADER_MAGIC i32.eq i32.eqz if i32.const 0 i32.const 184 - i32.const 113 - i32.const 4 + i32.const 146 + i32.const 2 call $~lib/env/abort unreachable end - local.get $1 ) - (func $~lib/runtime/runtime.freeUnregistered (; 31 ;) (type $FUNCSIG$vi) (param $0 i32) + (func $~lib/runtime/DISCARD (; 31 ;) (type $FUNCSIG$vi) (param $0 i32) local.get $0 - call $~lib/runtime/runtime.unrefUnregistered + call $~lib/runtime/ASSERT_UNREGISTERED + local.get $0 + global.get $~lib/runtime/HEADER_SIZE + i32.sub call $~lib/memory/memory.free ) (func $~lib/arraybuffer/ArrayBuffer#get:byteLength (; 32 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) local.get $0 - i32.const 16 + global.get $~lib/runtime/HEADER_SIZE i32.sub i32.load offset=4 ) (func $~lib/string/String#get:length (; 33 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) local.get $0 - i32.const 16 + global.get $~lib/runtime/HEADER_SIZE i32.sub i32.load offset=4 i32.const 1 @@ -3411,20 +3410,20 @@ if i32.const 0 i32.const 72 - i32.const 22 + i32.const 23 i32.const 0 call $~lib/env/abort unreachable end i32.const 0 - call $~lib/runtime/runtime.adjust + call $~lib/runtime/ADJUST i32.const 0 i32.gt_u i32.eqz if i32.const 0 i32.const 72 - i32.const 28 + i32.const 29 i32.const 0 call $~lib/env/abort unreachable @@ -3439,13 +3438,13 @@ i32.eqz br_if $break|0 local.get $0 - call $~lib/runtime/runtime.adjust + call $~lib/runtime/ADJUST call $std/runtime/isPowerOf2 i32.eqz if i32.const 0 i32.const 72 - i32.const 30 + i32.const 31 i32.const 2 call $~lib/env/abort unreachable @@ -3460,7 +3459,7 @@ unreachable end i32.const 0 - call $~lib/runtime/runtime.adjust + call $~lib/runtime/ADJUST global.set $std/runtime/barrier1 global.get $std/runtime/barrier1 i32.const 1 @@ -3471,9 +3470,9 @@ global.get $std/runtime/barrier2 i32.const 1 i32.add - call $~lib/runtime/runtime.adjust + call $~lib/runtime/ADJUST global.get $std/runtime/barrier2 - call $~lib/runtime/runtime.adjust + call $~lib/runtime/ADJUST i32.eq if global.get $std/runtime/barrier2 @@ -3493,9 +3492,9 @@ global.get $std/runtime/barrier3 i32.const 1 i32.add - call $~lib/runtime/runtime.adjust + call $~lib/runtime/ADJUST global.get $std/runtime/barrier3 - call $~lib/runtime/runtime.adjust + call $~lib/runtime/ADJUST i32.eq if global.get $std/runtime/barrier3 @@ -3534,21 +3533,21 @@ f64.const 0 call $~lib/env/trace i32.const 1 - call $~lib/runtime/runtime.alloc + call $~lib/runtime/ALLOCATE global.set $std/runtime/ref1 global.get $std/runtime/ref1 - i32.const 16 + global.get $~lib/runtime/HEADER_SIZE i32.sub global.set $std/runtime/header1 global.get $std/runtime/header1 i32.load - i32.const -1520547049 + global.get $~lib/runtime/HEADER_MAGIC i32.eq i32.eqz if i32.const 0 i32.const 72 - i32.const 45 + i32.const 46 i32.const 0 call $~lib/env/abort unreachable @@ -3561,7 +3560,7 @@ if i32.const 0 i32.const 72 - i32.const 46 + i32.const 47 i32.const 0 call $~lib/env/abort unreachable @@ -3569,13 +3568,13 @@ global.get $std/runtime/ref1 global.get $std/runtime/ref1 global.get $std/runtime/barrier1 - call $~lib/runtime/runtime.realloc + call $~lib/runtime/REALLOCATE i32.eq i32.eqz if i32.const 0 i32.const 72 - i32.const 47 + i32.const 48 i32.const 0 call $~lib/env/abort unreachable @@ -3588,14 +3587,14 @@ if i32.const 0 i32.const 72 - i32.const 48 + i32.const 49 i32.const 0 call $~lib/env/abort unreachable end global.get $std/runtime/ref1 global.get $std/runtime/barrier2 - call $~lib/runtime/runtime.realloc + call $~lib/runtime/REALLOCATE global.set $std/runtime/ref2 global.get $std/runtime/ref1 global.get $std/runtime/ref2 @@ -3604,13 +3603,13 @@ if i32.const 0 i32.const 72 - i32.const 50 + i32.const 51 i32.const 0 call $~lib/env/abort unreachable end global.get $std/runtime/ref2 - i32.const 16 + global.get $~lib/runtime/HEADER_SIZE i32.sub global.set $std/runtime/header2 global.get $std/runtime/header2 @@ -3621,15 +3620,15 @@ if i32.const 0 i32.const 72 - i32.const 52 + i32.const 53 i32.const 0 call $~lib/env/abort unreachable end global.get $std/runtime/ref2 - call $~lib/runtime/runtime.freeUnregistered + call $~lib/runtime/DISCARD global.get $std/runtime/barrier2 - call $~lib/runtime/runtime.alloc + call $~lib/runtime/ALLOCATE global.set $std/runtime/ref3 global.get $std/runtime/ref1 global.get $std/runtime/ref3 @@ -3638,19 +3637,22 @@ if i32.const 0 i32.const 72 - i32.const 55 + i32.const 56 i32.const 0 call $~lib/env/abort unreachable end global.get $std/runtime/barrier1 - call $~lib/runtime/runtime.alloc + call $~lib/runtime/ALLOCATE global.set $std/runtime/ref4 - block $~lib/gc/gc.register|inlined.0 (result i32) + block $~lib/runtime/REGISTER|inlined.0 (result i32) global.get $std/runtime/ref4 local.set $0 local.get $0 - call $~lib/runtime/runtime.unrefUnregistered + call $~lib/runtime/ASSERT_UNREGISTERED + local.get $0 + global.get $~lib/runtime/HEADER_SIZE + i32.sub i32.const 2 i32.store local.get $0 @@ -3665,13 +3667,13 @@ if i32.const 0 i32.const 72 - i32.const 59 + i32.const 60 i32.const 0 call $~lib/env/abort unreachable end global.get $std/runtime/register_ref - i32.const 16 + global.get $~lib/runtime/HEADER_SIZE i32.sub global.set $std/runtime/header4 global.get $std/runtime/header4 @@ -3682,7 +3684,7 @@ if i32.const 0 i32.const 72 - i32.const 61 + i32.const 62 i32.const 0 call $~lib/env/abort unreachable @@ -3695,13 +3697,13 @@ if i32.const 0 i32.const 72 - i32.const 62 + i32.const 63 i32.const 0 call $~lib/env/abort unreachable end i32.const 10 - call $~lib/runtime/runtime.alloc + call $~lib/runtime/ALLOCATE global.set $std/runtime/ref5 global.get $std/runtime/ref5 call $~lib/arraybuffer/ArrayBuffer#get:byteLength @@ -3711,7 +3713,7 @@ if i32.const 0 i32.const 72 - i32.const 65 + i32.const 66 i32.const 0 call $~lib/env/abort unreachable @@ -3724,7 +3726,7 @@ if i32.const 0 i32.const 72 - i32.const 66 + i32.const 67 i32.const 0 call $~lib/env/abort unreachable