diff --git a/std/assembly/allocator/tlsf.ts b/std/assembly/allocator/tlsf.ts index c4bf9991..bb843b3d 100644 --- a/std/assembly/allocator/tlsf.ts +++ b/std/assembly/allocator/tlsf.ts @@ -1,3 +1,8 @@ +// Two-Level Segregate Fit Memory Allocator. +// +// A general purpose dynamic memory allocator specifically designed to meet real-time requirements. +// Always aligns to 8 bytes. + // ╒══════════════ Block size interpretation (32-bit) ═════════════╕ // 3 2 1 // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits diff --git a/std/assembly/collector/pure.ts b/std/assembly/collector/pure.ts new file mode 100644 index 00000000..d10e40fa --- /dev/null +++ b/std/assembly/collector/pure.ts @@ -0,0 +1,261 @@ +// A Pure Reference Counting Garbage Collector +// +// After the paper by DAVID F. BACON, CLEMENT R. ATTANASIO, V.T. RAJAN, STEPHEN E.SMITH +// D.Bacon, IBM T.J. Watson Research Center +// 2001 ACM 0164-0925/99/0100-0111 $00.75 + +import { HEADER, HEADER_SIZE } from "../runtime"; + +ERROR("not implemented"); + +/* tslint:disable */ + +// TODO: new builtin +declare function ITERATECHILDREN(s: Header, fn: (t: Header) => void): void; + +/** Object Colorings for Cycle Collection */ +const enum Color { + /** In use or free. */ + BLACK = 0, + /** Possible member of cycle. */ + GRAY = 1, + /** Member of garbage cycle. */ + WHITE = 2, + /** Possible root of cycle. */ + PURPLE = 3, + /** Acyclic. */ + GREEN = 4 +} + +// TODO: this is a placeholder -> map this to HEADER +class Header { + rc: u32; + color: Color; + buffered: bool; +} + +// When reference counts are decremented, we place potential roots of cyclic garbage into a buffer +// called Roots. Periodically, we process this buffer and look for cycles by subtracting internal +// reference counts. + +var rootsBuffer: usize = 0; +var rootsOffset: usize = 0; // insertion offset +var rootsLength: usize = 0; // insertion limit + +function appendRoot(header: Header): void { + if (rootsOffset >= rootsLength) { + // grow for now + let newLength = rootsLength ? 2 * rootsLength : 256 * sizeof(); + let newBuffer = memory.allocate(newLength); + memory.copy(newBuffer, rootsBuffer, rootsLength); + rootsBuffer = newBuffer; + rootsLength = newLength; + } + store(rootsBuffer + rootsOffset, header); + rootsOffset += sizeof(); +} + +function systemFree(s: Header): void { + memory.free(changetype(s)); +} + +// When a reference to a node S is created, the reference count of T is incremented and it is +// colored black, since any object whose reference count was just incremented can not be garbage. + +function increment(s: Header): void { + s.rc += 1; + s.color = Color.BLACK; +} + +// When a reference to a node S is deleted, the reference count is decremented. If the reference +// count reaches zero, the procedure Release is invoked to free the garbage node. If the reference +// count does not reach zero, the node is considered as a possible root of a cycle. + +function decrement(s: Header): void { + s.rc -= 1; + if (!s.rc) release(s); + else possibleRoot(s); +} + +// When the reference count of a node reaches zero, the contained pointers are deleted, the object +// is colored black, and unless it has been buffered, it is freed. If it has been buffered, it is +// in the Roots buffer and will be freed later (in the procedure MarkRoots). + +function release(s: Header): void { + ITERATECHILDREN(s, t => decrement(t)); + s.color = Color.BLACK; + if (!s.buffered) systemFree(s); +} + +// When the reference count of S is decremented but does not reach zero, it is considered as a +// possible root of a garbage cycle. If its color is already purple, then it is already a candidate +// root; if not, its color is set to purple. Then the buffered flag is checked to see if it has +// been purple since we last performed a cycle collection. If it is not buffered, it is added to +// the buffer of possible roots. + +function possibleRoot(s: Header): void { + if (s.color != Color.PURPLE) { + s.color = Color.PURPLE; + if (!s.buffered) { + s.buffered = true; + appendRoot(s); + } + } +} + +// When the root buffer is full, or when some other condition, such as low memory occurs, the +// actual cycle collection operation is invoked. This operation has three phases: MarkRoots, which +// removes internal reference counts; ScanRoots, which restores reference counts when they are +// non-zero; and finally CollectRoots, which actually collects the cyclic garbage. + +function collectCycles(): void { + markRoots(); + scanRoots(); + collectRoots(); +} + +// The marking phase looks at all the nodes S whose pointers have been stored in the Roots buffer +// since the last cycle collection. If the color of the node is purple (indicating a possible root +// of a garbage cycle) and the reference count has not become zero, then MarkGray(S) is invoked to +// perform a depth-first search in which the reached nodes are colored gray and internal reference +// counts are subtracted. Otherwise, the node is removed from the Roots buffer, the buffered flag +// is cleared, and if the reference count is zero the object is freed. + +function markRoots(): void { + var readOffset = rootsBuffer; + var writeOffset = readOffset; + var readLimit = readOffset + rootsOffset; + while (readOffset < readLimit) { + let s = load
(readOffset); + if (s.color == Color.PURPLE && s.rc > 0) { + markGray(s); + store
(writeOffset, s); + writeOffset += sizeof(); + } else { + s.buffered = false; + // remove from roots + if (s.color == Color.BLACK && !s.rc) systemFree(s); + } + readOffset += sizeof(); + } + rootsOffset = writeOffset - rootsBuffer; +} + +// For each node S that was considered by MarkGray(S), this procedure invokes Scan(S) to either +// color the garbage subgraph white or re-color the live subgraph black. + +function scanRoots(): void { + var readOffset = rootsBuffer; + var readLimit = readOffset + rootsOffset; + while (readOffset < readLimit) { + scan(load
(readOffset)); + readOffset += sizeof(); + } +} + +// After the ScanRoots phase of the CollectCycles procedure, any remaining white nodes will be +// cyclic garbage and will be reachable from the Roots buffer. This prodecure invokes CollectWhite +// for each node in the Roots buffer to collect the garbage; all nodes in the root buffer are +// removed and their buffered flag is cleared. + +function collectRoots(): void { + var readOffset = rootsBuffer; + var readLimit = readOffset + rootsOffset; + while (readOffset < readLimit) { + let s = load
(readOffset); + // remove from roots + s.buffered = false; + collectWhite(s); + } + rootsOffset = 0; +} + +// This procedure performs a simple depth-first traversal of the graph beginning at S, marking +// visited nodes gray and removing internal reference counts as it goes. + +function markGray(s: Header): void { + if (s.color != Color.GRAY) { + s.color = Color.GRAY; + ITERATECHILDREN(s, t => { + t.rc -= 1; + markGray(t); + }); + } +} + +// If this procedure finds a gray object whose reference count is greater than one, then that +// object and everything reachable from it are live data; it will therefore call ScanBlack(S) in +// order to re-color the reachable subgraph and restore the reference counts subtracted by +// MarkGray. However, if the color of an object is gray and its reference count is zero, then it is +// colored white, and Scan is invoked upon its chldren. Note that an object may be colored white +// and then re-colored black if it is reachable from some subsequently discovered live node. + +function scan(s: Header): void { + if (s.color == Color.GRAY) { + if (s.rc > 0) scanBlack(s); + else { + s.color = Color.WHITE; + ITERATECHILDREN(s, t => scan(t)); + } + } + +} + +// This procedure performs the inverse operation of MarkGray, visiting the nodes, changing the +// color of objects back to black, and restoring their reference counts. + +function scanBlack(s: Header): void { + s.color = Color.BLACK; + ITERATECHILDREN(s, t => { + t.rc += 1; + if (t.color == Color.BLACK) scanBlack(t); + }); +} + +// This procedure recursively frees all white objects, re-coloring them black as it goes. If a +// white object is buffered, it is not freed; it will be freed later when it is found in the Roots +// buffer. + +function collectWhite(s: Header): void { + if (s.color == Color.WHITE && !s.buffered) { + s.color = Color.BLACK; + ITERATECHILDREN(s, t => collectWhite(t)); + systemFree(s); + } +} + +// Garbage collector interface + +// @ts-ignore: decorator +@global +function __gc_link(ref: usize, parentRef: usize): void { + increment(changetype
(ref - HEADER_SIZE)); +} + +// @ts-ignore: decorator +@global +function __gc_unlink(ref: usize, parentRef: usize): void { + decrement(changetype
(ref - HEADER_SIZE)) +} + +// @ts-ignore: decorator +@global +function __gc_collect(): void { + collectCycles(); +} + +// TODO: + +// A significant constant-factor improvement can be obtained for cycle collection by observice that +// some objects are inherently acyclic. We speculate that they will comprise the majorits of +// objects in many applications. Therefore, if we can avoid cycle collection for inherently acyclic +// object, we will significantly reduce the overhead of cycle collection as a whole. [...] +// +// Acyclic classes may contain: +// - scalars; +// - references to classes that are both acyclic and final; and +// - arrays of either of the above. +// +// Our implementation marks objects whose class is acyclic with the special color green. Green +// objects are ignored by the cycle collection algorithm, except that when a dead cycle refers to +// green objects, they are collected along with the dead cycle.