From d4d5814fc281b77370a8ba76a45cb07eb6669a79 Mon Sep 17 00:00:00 2001 From: dcode Date: Thu, 21 Mar 2019 12:29:49 +0100 Subject: [PATCH] rt docs, initial itcm wiring --- src/builtins.ts | 66 +++++++++++++++++----------------- src/compiler.ts | 1 - std/assembly/collector/itcm.ts | 57 ++++++++++++++--------------- std/assembly/runtime.ts | 66 ++++++++++++++++++++++++++++++++-- 4 files changed, 124 insertions(+), 66 deletions(-) diff --git a/src/builtins.ts b/src/builtins.ts index 2ff4516d..d07292c8 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -4367,17 +4367,20 @@ export function ensureGCHook( // check if the class implements a custom GC function (only valid for library elements) var members = classInstance.members; if (classInstance.isDeclaredInLibrary) { - if (members !== null && members.has("__gc")) { - let gcPrototype = assert(members.get("__gc")); - assert(gcPrototype.kind == ElementKind.FUNCTION_PROTOTYPE); - let gcInstance = assert(program.resolver.resolveFunction(gcPrototype, null)); - assert(gcInstance.is(CommonFlags.PRIVATE | CommonFlags.INSTANCE)); - assert(!gcInstance.isAny(CommonFlags.AMBIENT | CommonFlags.VIRTUAL)); - assert(gcInstance.signature.parameterTypes.length == 0); - assert(gcInstance.signature.returnType == Type.void); - gcInstance.internalName = classInstance.internalName + "~gc"; - assert(compiler.compileFunction(gcInstance)); - let index = compiler.ensureFunctionTableEntry(gcInstance); + if (members !== null && members.has("__iter")) { + let iterPrototype = assert(members.get("__iter")); + assert(iterPrototype.kind == ElementKind.FUNCTION_PROTOTYPE); + let iterInstance = assert(program.resolver.resolveFunction(iterPrototype, null)); + assert(iterInstance.is(CommonFlags.PRIVATE | CommonFlags.INSTANCE)); + assert(!iterInstance.isAny(CommonFlags.AMBIENT | CommonFlags.VIRTUAL)); + let signature = iterInstance.signature; + let parameterTypes = signature.parameterTypes; + assert(parameterTypes.length == 1); + assert(parameterTypes[0].signatureReference); + assert(signature.returnType == Type.void); + iterInstance.internalName = classInstance.internalName + "~iter"; + assert(compiler.compileFunction(iterInstance)); + let index = compiler.ensureFunctionTableEntry(iterInstance); classInstance.gcHookIndex = index; return index; } @@ -4408,7 +4411,7 @@ export function ensureGCHook( functionTable.push(""); classInstance.gcHookIndex = gcHookIndex; - // if the class extends a base class, call its hook first (calls mark) + // if the class extends a base class, call its hook first var baseInstance = classInstance.base; if (baseInstance) { assert(baseInstance.type.isManaged(program)); @@ -4418,19 +4421,12 @@ export function ensureGCHook( ensureGCHook(compiler, baseInstance.type.classReference) ), [ - module.createGetLocal(0, nativeSizeType) + module.createGetLocal(0, nativeSizeType), // this + module.createGetLocal(1, NativeType.I32) // fn ], - "FUNCSIG$" + (nativeSizeType == NativeType.I64 ? "vj" : "vi") + "FUNCSIG$" + (nativeSizeType == NativeType.I64 ? "vji" : "vii") ) ); - - // if this class is the top-most base class, mark the instance - } else { - body.push( - module.createCall(assert(program.gcMarkInstance).internalName, [ - module.createGetLocal(0, nativeSizeType) - ], NativeType.None) - ); } // mark instances assigned to own fields that are again references @@ -4442,16 +4438,20 @@ export function ensureGCHook( if (type.isManaged(program)) { let offset = (member).memoryOffset; assert(offset >= 0); - body.push( - module.createCall(assert(program.gcMarkInstance).internalName, [ - module.createLoad( - nativeSizeSize, - false, - module.createGetLocal(0, nativeSizeType), - nativeSizeType, - offset - ) - ], NativeType.None) + body.push( // fn(fieldValue) + module.createCallIndirect( + module.createGetLocal(1, NativeType.I32), + [ + module.createLoad( + nativeSizeSize, + false, + module.createGetLocal(0, nativeSizeType), + nativeSizeType, + offset + ), + ], + "FUNCSIG$vi" + ) ); } } @@ -4460,7 +4460,7 @@ export function ensureGCHook( } // add the function to the module and return its table index - var funcName = classInstance.internalName + "~gc"; + var funcName = classInstance.internalName + "~iter"; module.addFunction( funcName, compiler.ensureFunctionType(null, Type.void, options.usizeType), diff --git a/src/compiler.ts b/src/compiler.ts index 3a8d8650..17ec7670 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -7,7 +7,6 @@ import { compileCall as compileBuiltinCall, compileAbort, compileIterateRoots, - ensureGCHook, BuiltinSymbols, compileBuiltinArrayGet, compileBuiltinArraySet, diff --git a/std/assembly/collector/itcm.ts b/std/assembly/collector/itcm.ts index 3c65ed5b..26dedc7e 100644 --- a/std/assembly/collector/itcm.ts +++ b/std/assembly/collector/itcm.ts @@ -4,13 +4,7 @@ @inline const TRACE = false; -/** Size of a managed object header. */ -// @ts-ignore: decorator -@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 { ITERATEROOTS, HEADER_SIZE } from "../runtime"; /** Collector states. */ const enum State { @@ -51,6 +45,11 @@ var iter: ManagedObject; /** Represents a managed object in memory, consisting of a header followed by the object's data. */ @unmanaged class ManagedObject { + //
+ classId: u32; + payloadSize: u32; + //
+ /** Pointer to the next object with color flags stored in the alignment bits. */ nextWithColor: usize; @@ -58,7 +57,9 @@ var iter: ManagedObject; prev: ManagedObject; /** Class-specific hook function called with the user-space reference. */ - hookFn: (ref: usize) => void; + get hookFn(): (ref: usize) => void { + return changetype<(ref: usize) => void>(this.classId); + } /** Gets the pointer to the next object. */ get next(): ManagedObject { @@ -128,10 +129,10 @@ function step(): void { case State.INIT: { if (TRACE) trace("gc~step/INIT"); fromSpace = changetype(memory.allocate(HEADER_SIZE)); - fromSpace.hookFn = changetype<(ref: usize) => void>(-1); // would error + fromSpace.classId = -1; // would error fromSpace.clear(); toSpace = changetype(memory.allocate(HEADER_SIZE)); - toSpace.hookFn = changetype<(ref: usize) => void>(-1); // would error + toSpace.classId = -1; // would error toSpace.clear(); iter = toSpace; state = State.IDLE; @@ -140,7 +141,10 @@ function step(): void { } case State.IDLE: { if (TRACE) trace("gc~step/IDLE"); - ITERATEROOTS(__gc_mark); + ITERATEROOTS((ref: usize): void => { + var obj = refToObj(ref); + if (obj.color == white) obj.makeGray(); + }); state = State.MARK; if (TRACE) trace("gc~state = MARK"); break; @@ -161,7 +165,10 @@ function step(): void { obj.hookFn(objToRef(obj)); } else { if (TRACE) trace("gc~step/MARK finish"); - ITERATEROOTS(__gc_mark); + ITERATEROOTS((ref: usize): void => { + var obj = refToObj(ref); + if (obj.color == white) obj.makeGray(); + }); obj = iter.next; if (obj === toSpace) { let from = fromSpace; @@ -206,36 +213,26 @@ function objToRef(obj: ManagedObject): usize { // @ts-ignore: decorator @global @unsafe -export function __gc_allocate( // TODO: make this register only / reuse header - size: usize, - markFn: (ref: usize) => void -): usize { - if (TRACE) trace("gc.allocate", 1, size); - if (size > MAX_SIZE_32 - HEADER_SIZE) unreachable(); +export function __gc_register(ref: usize): void { + if (TRACE) trace("gc.register", 2, ref); step(); // also makes sure it's initialized - var obj = changetype(memory.allocate(HEADER_SIZE + size)); - obj.hookFn = markFn; + var obj = refToObj(ref); obj.color = white; fromSpace.push(obj); - return objToRef(obj); } // @ts-ignore: decorator @global @unsafe -export function __gc_link(parentRef: usize, childRef: usize): void { - if (TRACE) trace("gc.link", 2, parentRef, childRef); +export function __gc_retain(ref: usize, parentRef: usize): void { + if (TRACE) trace("gc.retain", 2, ref, parentRef); var parent = refToObj(parentRef); - if (parent.color == i32(!white) && refToObj(childRef).color == white) parent.makeGray(); + if (parent.color == i32(!white) && refToObj(ref).color == white) parent.makeGray(); } // @ts-ignore: decorator @global @unsafe -export function __gc_mark(ref: usize): void { - if (TRACE) trace("gc.mark", 1, ref); - if (ref) { - let obj = refToObj(ref); - if (obj.color == white) obj.makeGray(); - } +export function __gc_release(ref: usize, parentRef: usize): void { + if (TRACE) trace("gc.release", 2, ref, parentRef); } // @ts-ignore: decorator diff --git a/std/assembly/runtime.ts b/std/assembly/runtime.ts index e92d3e80..10b42ab7 100644 --- a/std/assembly/runtime.ts +++ b/std/assembly/runtime.ts @@ -1,6 +1,68 @@ +// The runtime provides a set of macros for dealing with common AssemblyScript internals, like +// allocation, memory management in general, integration with a (potenial) garbage collector +// and interfaces to hard-wired data types like buffers and their views. Doing so ensures that +// no matter which underlying implementation of a memory allocator or garbage collector is used, +// as long as all runtime/managed objects adhere to the runtime conventions, it'll all play well +// together. The compiler assumes that it can itself use the macros with the signatures declared +// in this file, so changing anything here will most likely require changes to the compiler, too. + import { AL_MASK, MAX_SIZE_32 } from "./util/allocator"; import { HEAP_BASE, memory } from "./memory"; +// ALLOCATE(size) +// -------------- +// Allocates a runtime object that might eventually make its way into GC'ed userland as a +// managed object. Implicitly prepends the common runtime header to the allocation. +// +// REALLOCATE(ref, size) +// --------------------- +// Changes the size of a previously allocated, but not yet registered, runtime object, for +// example when a pre-allocated buffer turned out to be too small or too large. This works by +// aligning dynamic allocations to actual block size internally so in the best case REALLOCATE +// only changes a size while in the worst case moves the object to larger block. +// +// DISCARD(ref) +// ------------ +// Discards a runtime object that has not been registed and turned out to be unnecessary. +// Essentially undoes the forgoing ALLOCATE. Should be avoided where possible, of course. +// +// REGISTER(ref) +// ---------------- +// Registers a runtime object of kind T. Sets the internal class id within the runtime header +// and asserts that the object hasn't been registered yet. If a tracing garbage collector is +// present that requires initial insertion, the macro also forwards a call to it. Once a +// runtime object has been registed (makes it into userland), it cannot be DISCARD'ed anymore. +// +// RETAIN(ref, parentRef) +// --------------------------------- +// Introduces a new reference to ref hold by parentRef. A tracing garbage collector will most +// likely link the runtime object within its internal graph when RETAIN is called, while a +// reference counting collector will increment the reference count. +// +// RELEASE(ref, parentRef) +// ---------------------------------- +// Releases a reference to ref hold by parentRef. A tracing garbage collector will most likely +// ignore this by design, while a reference counting collector decrements the reference count +// and potentially frees the runtime object. +// +// ALLOCATE_UNMANAGED(size) +// ------------------------ +// Allocates an unmanaged struct-like object. This is used by the compiler as an abstraction +// to memory.allocate just in case, and is usually not used directly. +// +// WRAPARRAY(buffer) +// -------------------- +// Wraps a buffer's data as a standard array of element type T. Used by the compiler when +// creating an array from a static data segment, but is usually not used directly. +// +// HEADER +// ------ +// The common runtime object header prepended to all managed objects. Has a size of 16 bytes in +// WASM32 and contains a classId (e.g. for instanceof checks), the allocation size (e.g. for +// .byteLength and .length computation) and additional reserved fields to be used by GC. If no +// GC is present, the HEADER is cut into half excluding the reserved fields, as indicated by +// HEADER_SIZE. + /** Whether the memory manager interface is implemented. */ // @ts-ignore: decorator, stub @lazy export const MM_IMPLEMENTED: bool = isDefined(__memory_allocate); @@ -157,7 +219,7 @@ function doRetain(ref: usize, parentRef: usize): void { assertRegistered(parentRef); } // @ts-ignore: stub - if (GC_IMPLEMENTED) __gc_link(changetype(ref), changetype(parentRef)); + if (GC_IMPLEMENTED) __gc_retain(changetype(ref), changetype(parentRef)); } /** Releases a registered object. */ @@ -175,7 +237,7 @@ function doRelease(ref: usize, parentRef: usize): void { assertRegistered(parentRef); } // @ts-ignore: stub - if (GC_IMPLEMENTED) __gc_unlink(changetype(ref), changetype(parentRef)); + if (GC_IMPLEMENTED) __gc_release(changetype(ref), changetype(parentRef)); } /** Discards an unregistered object that turned out to be unnecessary. */