mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-04-25 15:12:12 +00:00
288 lines
7.7 KiB
TypeScript
288 lines
7.7 KiB
TypeScript
/////////////////////////// μgc Garbage Collector /////////////////////////////
|
|
// based on https://github.com/bullno1/ugc - BSD (see LICENSE file) //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// States
|
|
const IDLE: u8 = 0;
|
|
const MARK: u8 = 1;
|
|
const SWEEP: u8 = 2;
|
|
|
|
// Gray tag
|
|
const GRAY: u32 = 2;
|
|
|
|
/** Header for a managed object. */
|
|
@explicit
|
|
class ObjectHeader {
|
|
|
|
/////////////////////////////// Constants ///////////////////////////////////
|
|
|
|
static readonly SIZE: usize = 2 * sizeof<usize>();
|
|
|
|
///////////////////////////////// Fields ////////////////////////////////////
|
|
|
|
tagged_next: usize;
|
|
tagged_prev: usize;
|
|
|
|
get next(): ObjectHeader {
|
|
return changetype<ObjectHeader>(this.tagged_next & ~3);
|
|
}
|
|
|
|
set next(value: ObjectHeader) {
|
|
this.tagged_next = changetype<usize>(value) | (this.tagged_next & 3);
|
|
}
|
|
|
|
get prev(): ObjectHeader {
|
|
return changetype<ObjectHeader>(this.tagged_prev & ~3);
|
|
}
|
|
|
|
set prev(value: ObjectHeader) {
|
|
this.tagged_prev = changetype<usize>(value) | (this.tagged_prev & 3);
|
|
}
|
|
|
|
get color(): u32 {
|
|
return this.tagged_next & 3;
|
|
}
|
|
|
|
set color(value: u32) {
|
|
assert(value < 3);
|
|
this.tagged_next = this.tagged_next | value;
|
|
}
|
|
|
|
///////////////////////////////// Methods ///////////////////////////////////
|
|
|
|
push(element: ObjectHeader): void {
|
|
element.next = this;
|
|
element.prev = this.prev;
|
|
this.prev.next = element;
|
|
this.prev = element;
|
|
}
|
|
|
|
unlink(): void {
|
|
var next = this.next;
|
|
var prev = this.prev;
|
|
next.prev = prev;
|
|
prev.next = next;
|
|
}
|
|
|
|
clear(): void {
|
|
this.next = this;
|
|
this.prev = this;
|
|
}
|
|
}
|
|
|
|
/** Garbage collector data. */
|
|
@explicit
|
|
class Control {
|
|
|
|
/////////////////////////////// Constants ///////////////////////////////////
|
|
|
|
static readonly SIZE: usize = 7 * sizeof<usize>() + 2 * sizeof<u8>();
|
|
static readonly PAUSED_BIT: u8 = 1 << 7;
|
|
|
|
///////////////////////////////// Fields ////////////////////////////////////
|
|
|
|
// 'from' and 'to' point here
|
|
private __set1_tagged_next: usize;
|
|
private __set1_tagged_prev: usize;
|
|
private __set2_tagged_next: usize;
|
|
private __set2_tagged_prev: usize;
|
|
|
|
from: ObjectHeader;
|
|
to: ObjectHeader;
|
|
iterator: ObjectHeader;
|
|
state: u8; // MSB indicates paused
|
|
white: u8;
|
|
|
|
/** Tests whether the collector is currently paused. */
|
|
get paused(): bool { return (this.state & Control.PAUSED_BIT) != 0; }
|
|
/** Sets whether the collector is currently paused. */
|
|
set paused(paused: bool) { this.state = paused ? this.state |= Control.PAUSED_BIT : this.state &= ~Control.PAUSED_BIT; }
|
|
|
|
///////////////////////////////// Methods ///////////////////////////////////
|
|
|
|
/** Creates a new instance. */
|
|
static create(mem: usize): Control {
|
|
var control = changetype<Control>(mem);
|
|
var set1 = changetype<ObjectHeader>(mem);
|
|
var set2 = changetype<ObjectHeader>(mem + 2 * sizeof<usize>());
|
|
set1.clear();
|
|
set2.clear();
|
|
control.state = IDLE;
|
|
control.white = 0;
|
|
control.from = set1;
|
|
control.to = set2;
|
|
control.iterator = control.to;
|
|
return control;
|
|
}
|
|
|
|
/** Registers a new object to be managed. */
|
|
register(obj: ObjectHeader): void {
|
|
this.from.push(obj);
|
|
obj.color = this.white;
|
|
}
|
|
|
|
/**
|
|
* Registers a new reference from one object to another.
|
|
*
|
|
* Whenever an object stores a reference to another object, this function
|
|
* MUST be called to ensure that the GC works correctly.
|
|
*
|
|
* Root objects (stack, globals) are treated differently so there is no need
|
|
* to call this function when a store to them occurs.
|
|
*/
|
|
addRef(parent: ObjectHeader, child: ObjectHeader): void {
|
|
var parent_color = parent.color;
|
|
var child_color = child.color;
|
|
var white = this.white;
|
|
var black = white ^ 1;
|
|
if (parent_color == black && child_color == white) {
|
|
this.makeGray(parent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Make the GC perform one unit of work.
|
|
*
|
|
* What happens depends on the current GC's state.
|
|
*
|
|
* - In IDLE state, it will scan the root by calling the scan callback then
|
|
* switch to MARK state.
|
|
* - In MARK state, it will mark one object and discover its children using
|
|
* the scan callback. When there is no object left to mark, the GC will
|
|
* scan the root once more to account for changes during the mark phase.
|
|
* When all live objects are marked, it will switch to SWEEP state.
|
|
* - In SWEEP state, it will release one object. When all garbage are
|
|
* released, it wil switch to UGC_IDLE state.
|
|
*/
|
|
step(): void {
|
|
var obj: ObjectHeader;
|
|
switch (this.state) {
|
|
|
|
case IDLE:
|
|
gc_scan_fn(this, null);
|
|
this.state = MARK;
|
|
break;
|
|
|
|
case MARK:
|
|
obj = this.iterator.next;
|
|
var white = this.white;
|
|
|
|
if (obj != this.to) {
|
|
this.iterator = obj;
|
|
obj.color = white ^ 1;
|
|
gc_scan_fn(this, obj);
|
|
} else {
|
|
gc_scan_fn(this, null);
|
|
obj = this.iterator.next;
|
|
if (obj == this.to) {
|
|
var from = this.from;
|
|
this.from = this.to;
|
|
this.to = from;
|
|
this.white = white ^ 1;
|
|
this.iterator = from.next;
|
|
this.state = SWEEP;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SWEEP:
|
|
obj = this.iterator;
|
|
if (obj != this.to) {
|
|
this.iterator = obj.next;
|
|
gc_free_fn(this, obj);
|
|
} else {
|
|
this.to.clear();
|
|
this.state = IDLE;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Performs a collection cycle.
|
|
*
|
|
* Start the GC if it's not already running and only return once the GC has
|
|
* finished collecting all garbage identified at the point of calling.
|
|
*
|
|
* If the GC is already in the SWEEP state, it will leave newly created
|
|
* garbage for the next cycle.
|
|
*/
|
|
collect(): void {
|
|
if (this.state == IDLE)
|
|
this.step();
|
|
while (this.state != IDLE)
|
|
this.step();
|
|
}
|
|
|
|
/** Informs the GC of a referred object during the mark phase. */
|
|
visit(obj: ObjectHeader): void {
|
|
if (this.state == SWEEP)
|
|
return;
|
|
if (obj.color == this.white)
|
|
this.makeGray(obj);
|
|
}
|
|
|
|
makeGray(obj: ObjectHeader): void {
|
|
if (obj != this.iterator) {
|
|
obj.unlink();
|
|
this.to.push(obj);
|
|
} else {
|
|
this.iterator = this.iterator.prev;
|
|
}
|
|
obj.color = GRAY;
|
|
}
|
|
}
|
|
|
|
var GC = Control.create(HEAP_BASE);
|
|
var GC_BASE = HEAP_BASE + Control.SIZE;
|
|
|
|
GC.register(changetype<ObjectHeader>(GC_BASE));
|
|
|
|
// Exported interface
|
|
|
|
/** Pauses automatic garbage collection. */
|
|
export function gc_pause(): void {
|
|
GC.paused = true;
|
|
}
|
|
|
|
/** Resumes automatic garbage collection. */
|
|
export function gc_resume(): void {
|
|
GC.paused = false;
|
|
}
|
|
|
|
/** Performs a collection cycle. Ignores pauses. */
|
|
export function gc_collect(): void {
|
|
var paused = GC.paused;
|
|
GC.paused = false;
|
|
GC.collect();
|
|
GC.paused = paused;
|
|
}
|
|
|
|
// TODO: these functions must be generated by the compiler and combined by
|
|
// any potential linker. They live here for now to document their structure.
|
|
|
|
function gc_scan_fn(control: Control, header: ObjectHeader | null): void {
|
|
if (!header) {
|
|
// visit all global vars referencing managed objects
|
|
} else {
|
|
// visit all referenced objects using the compiler's knowledge of this
|
|
// object's layout
|
|
var classId = load<u32>(changetype<usize>(header) + ObjectHeader.SIZE);
|
|
// switch (classId) {
|
|
// arrays
|
|
// strings
|
|
// user-defined
|
|
// }
|
|
}
|
|
}
|
|
|
|
function gc_free_fn(control: Control, header: ObjectHeader): void {
|
|
// finalize the given object using the compiler's knowledge of its layout
|
|
var classId = load<u32>(changetype<usize>(header) + ObjectHeader.SIZE);
|
|
// switch (classId) {
|
|
// array, string: free their data segments
|
|
// TODO: might make sense to provide @finalize or similar
|
|
// }
|
|
free_memory(changetype<usize>(header));
|
|
}
|