mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-07-02 16:11:49 +00:00
Add a more convenient tracing utility for debugging; Fix basic GC test
This commit is contained in:
@ -4,122 +4,15 @@
|
||||
* @module std/assembly/collector/itcm
|
||||
*//***/
|
||||
|
||||
// Based on the concepts of Bach Le's μgc, see: https://github.com/bullno1/ugc
|
||||
// Largely based on the Bach Le's μgc, see: https://github.com/bullno1/ugc
|
||||
|
||||
const TRACE = true;
|
||||
|
||||
import {
|
||||
AL_MASK,
|
||||
MAX_SIZE_32
|
||||
} from "../internal/allocator";
|
||||
|
||||
// ╒═══════════════ Managed object layout (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
|
||||
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┼─┴─┴─┤ ┐
|
||||
// │ next │ F │ ◄─┐ = nextWithFlags
|
||||
// ├─────────────────────────────────────────────────────────┴─────┤ │ usize
|
||||
// │ prev │ ◄─┘
|
||||
// ╞═══════════════════════════════════════════════════════════════╡ SIZE ┘
|
||||
// │ ... data ... │
|
||||
// └───────────────────────────────────────────────────────────────┘
|
||||
// F: flags
|
||||
|
||||
/** Managed object flags. */
|
||||
namespace Flags {
|
||||
/** Object is unreachable (so far). */
|
||||
export var WHITE = 0;
|
||||
/** Object is reachable. */
|
||||
export var BLACK = 1;
|
||||
/** Object is reachable but its children have not yet been scanned. */
|
||||
export const GRAY = 2;
|
||||
/** Mask to obtain just the flag bits. */
|
||||
export const MASK = AL_MASK;
|
||||
}
|
||||
|
||||
/** Represents a managed object in memory, consisting of a header followed by the object's data. */
|
||||
@unmanaged
|
||||
class ManagedObject {
|
||||
|
||||
/** Pointer to the next object with additional flags stored in the alignment bits. */
|
||||
nextWithFlags: usize;
|
||||
|
||||
/** Pointer to the previous object. */
|
||||
prev: ManagedObject;
|
||||
|
||||
/** Visitor function called with the data pointer (excl. header). */
|
||||
visitFn: (obj: usize) => void;
|
||||
|
||||
/** Size of a managed object after alignment. */
|
||||
static readonly SIZE: usize = (offsetof<ManagedObject>() + AL_MASK) & ~AL_MASK;
|
||||
|
||||
/** Gets the pointer to the next object in the list. */
|
||||
get next(): ManagedObject {
|
||||
return changetype<ManagedObject>(this.nextWithFlags & ~Flags.MASK);
|
||||
}
|
||||
|
||||
/** Sets the pointer to the next object in the list. */
|
||||
set next(obj: ManagedObject) {
|
||||
this.nextWithFlags = changetype<usize>(obj) | (this.nextWithFlags & Flags.MASK);
|
||||
}
|
||||
|
||||
/** Inserts an object to this list. */
|
||||
insert(obj: ManagedObject): void {
|
||||
var prev = this.prev;
|
||||
obj.next = this;
|
||||
obj.prev = prev;
|
||||
prev.next = obj;
|
||||
this.prev = obj;
|
||||
}
|
||||
|
||||
/** Removes this object from its list. */
|
||||
remove(): void {
|
||||
var next = this.next;
|
||||
var prev = this.prev;
|
||||
next.prev = prev;
|
||||
prev.next = next;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.nextWithFlags = changetype<usize>(this);
|
||||
this.prev = this;
|
||||
}
|
||||
|
||||
/** Tests if this object is white, that is unreachable (so far). */
|
||||
get isWhite(): bool {
|
||||
return (this.nextWithFlags & Flags.MASK) == Flags.WHITE;
|
||||
}
|
||||
|
||||
/** Marks this object as white, that is unreachable (so far). */
|
||||
makeWhite(): void {
|
||||
this.nextWithFlags = (this.nextWithFlags & ~Flags.MASK) | Flags.WHITE;
|
||||
}
|
||||
|
||||
/** Tests if this object is black, that is reachable. Root objects are always reachable. */
|
||||
get isBlack(): bool {
|
||||
return (this.nextWithFlags & Flags.MASK) == Flags.BLACK;
|
||||
}
|
||||
|
||||
/** Marks this object as black, that is reachable. */
|
||||
makeBlack(): void {
|
||||
this.nextWithFlags = (this.nextWithFlags & ~Flags.MASK) | Flags.BLACK;
|
||||
}
|
||||
|
||||
/** Tests if this object is gray, that is reachable with unscanned children. */
|
||||
get isGray(): bool {
|
||||
return (this.nextWithFlags & Flags.MASK) == Flags.GRAY;
|
||||
}
|
||||
|
||||
/** Marks this object as gray, that is reachable with unscanned children. */
|
||||
makeGray(): void {
|
||||
if (this != iter) {
|
||||
this.remove();
|
||||
set2.insert(this);
|
||||
} else {
|
||||
iter = iter.prev;
|
||||
}
|
||||
this.nextWithFlags = (this.nextWithFlags & ~Flags.MASK) | Flags.GRAY;
|
||||
}
|
||||
}
|
||||
|
||||
/** Collector states. */
|
||||
const enum State {
|
||||
/** Not yet initialized. */
|
||||
@ -134,64 +27,164 @@ const enum State {
|
||||
|
||||
/** Current collector state. */
|
||||
var state = State.INIT;
|
||||
/** Current white color value. */
|
||||
var white = 0;
|
||||
|
||||
// From and to spaces
|
||||
var set1: ManagedObject;
|
||||
var set2: ManagedObject;
|
||||
var from: ManagedObject;
|
||||
var to: ManagedObject;
|
||||
var iter: ManagedObject;
|
||||
|
||||
// ╒═══════════════ Managed object layout (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
|
||||
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┼─┴─┴─┤ ┐
|
||||
// │ next │ F │ ◄─┐ = nextWithFlags
|
||||
// ├─────────────────────────────────────────────────────────┴─────┤ │ usize
|
||||
// │ prev │ ◄─┘
|
||||
// ╞═══════════════════════════════════════════════════════════════╡ SIZE ┘
|
||||
// │ ... data ... │
|
||||
// └───────────────────────────────────────────────────────────────┘
|
||||
// F: flags
|
||||
|
||||
/** Represents a managed object in memory, consisting of a header followed by the object's data. */
|
||||
@unmanaged
|
||||
class ManagedObject {
|
||||
|
||||
/** Pointer to the next object with color flags stored in the alignment bits. */
|
||||
nextWithColor: usize;
|
||||
|
||||
/** Pointer to the previous object. */
|
||||
prev: ManagedObject;
|
||||
|
||||
/** Visitor function called with the payload reference. */
|
||||
visitFn: (ref: usize) => void;
|
||||
|
||||
/** Size of a managed object after alignment. */
|
||||
static readonly SIZE: usize = (offsetof<ManagedObject>() + AL_MASK) & ~AL_MASK;
|
||||
|
||||
/** Gets the pointer to the next object in the list. */
|
||||
get next(): ManagedObject {
|
||||
return changetype<ManagedObject>(this.nextWithColor & ~3);
|
||||
}
|
||||
|
||||
/** Sets the pointer to the next object in the list. */
|
||||
set next(obj: ManagedObject) {
|
||||
this.nextWithColor = changetype<usize>(obj) | (this.nextWithColor & 3);
|
||||
}
|
||||
|
||||
/** Gets this object's color. */
|
||||
get color(): i32 {
|
||||
return this.nextWithColor & 3;
|
||||
}
|
||||
|
||||
/** Sets this object's color. */
|
||||
set color(color: i32) {
|
||||
this.nextWithColor = (this.nextWithColor & ~3) | color;
|
||||
}
|
||||
|
||||
/** Inserts an object to this list. */
|
||||
push(obj: ManagedObject): void {
|
||||
var prev = this.prev;
|
||||
trace(" push", 3, objToRef(prev), objToRef(obj), objToRef(this));
|
||||
obj.next = this;
|
||||
obj.prev = prev;
|
||||
prev.next = obj;
|
||||
this.prev = obj;
|
||||
}
|
||||
|
||||
/** Unlinks this object from its list. */
|
||||
unlink(): void {
|
||||
var next = this.next;
|
||||
var prev = this.prev;
|
||||
if (TRACE) trace(" unlink", 3, objToRef(prev), objToRef(this), objToRef(next));
|
||||
next.prev = prev;
|
||||
prev.next = next;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
if (TRACE) trace(" clear", 1, objToRef(this));
|
||||
this.nextWithColor = changetype<usize>(this);
|
||||
this.prev = this;
|
||||
}
|
||||
|
||||
/** Marks this object as gray, that is reachable with unscanned children. */
|
||||
makeGray(): void {
|
||||
if (TRACE) trace(" makeGray", 1, objToRef(this));
|
||||
const gray = 2;
|
||||
if (this == iter) iter = this.prev;
|
||||
this.unlink();
|
||||
to.push(this);
|
||||
this.nextWithColor = (this.nextWithColor & ~3) | gray;
|
||||
}
|
||||
}
|
||||
|
||||
function markRoots(): void {
|
||||
if (TRACE) trace(" markRoots");
|
||||
gc.iterateRoots(function markRoot(ref: usize): void {
|
||||
if (TRACE) trace(" markRoot", 1, ref);
|
||||
if (ref) __gc_mark(ref);
|
||||
});
|
||||
}
|
||||
|
||||
/** Performs a single step according to the current state. */
|
||||
function step(): void {
|
||||
var obj: ManagedObject;
|
||||
switch (state) {
|
||||
case State.INIT: {
|
||||
set1 = changetype<ManagedObject>(memory.allocate(ManagedObject.SIZE));
|
||||
set1.clear();
|
||||
set2 = changetype<ManagedObject>(memory.allocate(ManagedObject.SIZE));
|
||||
set2.clear();
|
||||
iter = set2;
|
||||
if (TRACE) trace("gc~step/INIT");
|
||||
from = changetype<ManagedObject>(memory.allocate(ManagedObject.SIZE));
|
||||
from.visitFn = changetype<(ref: usize) => void>(<u32>-1); // would error
|
||||
from.clear();
|
||||
to = changetype<ManagedObject>(memory.allocate(ManagedObject.SIZE));
|
||||
to.visitFn = changetype<(ref: usize) => void>(<u32>-1); // would error
|
||||
to.clear();
|
||||
iter = to;
|
||||
state = State.IDLE;
|
||||
if (TRACE) trace("gc~state = IDLE");
|
||||
// fall-through
|
||||
}
|
||||
case State.IDLE: {
|
||||
// start by marking roots
|
||||
gc.iterateRoots(function mark_root(ref: usize): void {
|
||||
if (ref) {
|
||||
let obj = changetype<ManagedObject>(ref - ManagedObject.SIZE);
|
||||
obj.makeBlack();
|
||||
obj.visitFn(ref);
|
||||
}
|
||||
});
|
||||
if (TRACE) trace("gc~step/IDLE");
|
||||
markRoots();
|
||||
state = State.MARK;
|
||||
if (TRACE) trace("gc~state = MARK");
|
||||
break;
|
||||
}
|
||||
case State.MARK: {
|
||||
obj = iter.next;
|
||||
if (obj != set2) {
|
||||
if (obj !== to) {
|
||||
if (TRACE) trace("gc~step/MARK iterate", 1, objToRef(obj));
|
||||
iter = obj;
|
||||
obj.makeBlack();
|
||||
obj.visitFn(changetype<usize>(obj) + ManagedObject.SIZE);
|
||||
obj.color = <i32>!white;
|
||||
obj.visitFn(objToRef(obj));
|
||||
} else {
|
||||
if (TRACE) trace("gc~step/MARK finish");
|
||||
markRoots();
|
||||
obj = iter.next;
|
||||
if (obj == set2) {
|
||||
let set1_ = set1;
|
||||
set1 = set2;
|
||||
set2 = set1_;
|
||||
Flags.WHITE ^= 1;
|
||||
Flags.BLACK ^= 1;
|
||||
iter = set1.next;
|
||||
if (obj === to) {
|
||||
let prevFrom = from;
|
||||
from = to;
|
||||
to = prevFrom;
|
||||
white = <i32>!white;
|
||||
iter = prevFrom.next;
|
||||
state = State.SWEEP;
|
||||
if (TRACE) trace("gc~state = SWEEP");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case State.SWEEP: {
|
||||
obj = iter;
|
||||
if (obj !== set2) {
|
||||
if (obj !== to) {
|
||||
if (TRACE) trace("gc~step/SWEEP free", 1, objToRef(obj));
|
||||
iter = obj.next;
|
||||
memory.free(changetype<usize>(obj));
|
||||
} else {
|
||||
set2.clear();
|
||||
if (TRACE) trace("gc~step/SWEEP finish");
|
||||
to.clear();
|
||||
state = State.IDLE;
|
||||
if (TRACE) trace("gc~state = IDLE");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -213,30 +206,33 @@ function step(): void {
|
||||
size: usize,
|
||||
visitFn: (ref: usize) => void
|
||||
): usize {
|
||||
assert(size <= MAX_SIZE_32 - ManagedObject.SIZE);
|
||||
if (TRACE) trace("gc.allocate", 1, size);
|
||||
if (size > MAX_SIZE_32 - ManagedObject.SIZE) unreachable();
|
||||
step(); // also makes sure it's initialized
|
||||
var obj = changetype<ManagedObject>(memory.allocate(ManagedObject.SIZE + size));
|
||||
obj.makeWhite();
|
||||
obj.visitFn = visitFn;
|
||||
set1.insert(obj);
|
||||
obj.color = white;
|
||||
from.push(obj);
|
||||
return objToRef(obj);
|
||||
}
|
||||
|
||||
/** Marks a reachable object. Called from the visitFn functions. */
|
||||
@global export function __gc_mark(ref: usize): void {
|
||||
if (TRACE) trace("gc.mark", 1, ref);
|
||||
var obj = refToObj(ref);
|
||||
if (state == State.SWEEP) return;
|
||||
if (obj.isWhite) obj.makeGray();
|
||||
if (obj.color == white) obj.makeGray();
|
||||
}
|
||||
|
||||
/** Links a managed child object to its parent object. */
|
||||
@global export function __gc_link(parentRef: usize, childRef: usize): void {
|
||||
if (TRACE) trace("gc.link", 2, parentRef, childRef);
|
||||
var parent = refToObj(parentRef);
|
||||
var child = refToObj(childRef);
|
||||
if (parent.isBlack && child.isWhite) parent.makeGray();
|
||||
if (parent.color == <i32>!white && refToObj(childRef).color == white) parent.makeGray();
|
||||
}
|
||||
|
||||
/** Performs a full garbage collection cycle. */
|
||||
@global export function __gc_collect(): void {
|
||||
if (TRACE) trace("gc.collect");
|
||||
// begin collecting if not yet collecting
|
||||
switch (state) {
|
||||
case State.INIT:
|
||||
|
@ -1,7 +1,16 @@
|
||||
/** Environment abort function called where assertions evaluate to false / on throw. */
|
||||
declare function abort(
|
||||
message?: string | null,
|
||||
fileName?: string | null,
|
||||
lineNumber?: u32,
|
||||
columnNumber?: u32
|
||||
): void;
|
||||
|
||||
declare function trace(
|
||||
message: string,
|
||||
n?: i32,
|
||||
a0?: f64,
|
||||
a1?: f64,
|
||||
a2?: f64,
|
||||
a3?: f64,
|
||||
a4?: f64
|
||||
): void;
|
||||
|
@ -9,19 +9,19 @@ export namespace gc {
|
||||
}
|
||||
|
||||
export function mark(ref: usize): void {
|
||||
if (isDefined(__gc_mark)) return __gc_mark(ref); // tslint:disable-line
|
||||
if (isDefined(__gc_mark)) { __gc_mark(ref); return; } // tslint:disable-line
|
||||
WARNING("Calling 'gc.mark' requires a garbage collector to be present.");
|
||||
unreachable();
|
||||
}
|
||||
|
||||
export function link(parentRef: usize, childRef: usize): void {
|
||||
if (isDefined(__gc_link)) return __gc_link(parentRef, childRef); // tslint:disable-line
|
||||
if (isDefined(__gc_link)) { __gc_link(parentRef, childRef); return; } // tslint:disable-line
|
||||
WARNING("Calling 'gc.link' requires a garbage collector to be present.");
|
||||
unreachable();
|
||||
}
|
||||
|
||||
export function collect(): void {
|
||||
if (isDefined(__gc_collect)) return __gc_collect(); // tslint:disable-line
|
||||
if (isDefined(__gc_collect)) { __gc_collect(); return; } // tslint:disable-line
|
||||
WARNING("Calling 'gc.collect' requires a garbage collector to be present.");
|
||||
unreachable();
|
||||
}
|
||||
|
3
std/assembly/index.d.ts
vendored
3
std/assembly/index.d.ts
vendored
@ -654,6 +654,9 @@ declare const Math: IMath<f64>;
|
||||
/** Alias of {@link NativeMathf} or {@link JSMath} respectively. Defaults to `NativeMathf`. */
|
||||
declare const Mathf: IMath<f32>;
|
||||
|
||||
/** Environmental tracing function for debugging purposes. */
|
||||
declare function trace(msg: string, n?: i32, a0?: f64, a1?: f64, a2?: f64, a3?: f64, a4?: f64): void;
|
||||
|
||||
// Decorators
|
||||
|
||||
/** Annotates an element as a program global. */
|
||||
|
Reference in New Issue
Block a user