mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-06-19 01:41:30 +00:00
Rename memory instructions; Rework constant handling
This commit is contained in:
@ -11,6 +11,10 @@ import {
|
||||
MAX_SIZE_32
|
||||
} from "../internal/allocator";
|
||||
|
||||
import {
|
||||
__gc_iterate_roots
|
||||
} from "../builtins";
|
||||
|
||||
// ╒═══════════════ 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
|
||||
@ -78,6 +82,11 @@ class ManagedObject {
|
||||
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;
|
||||
@ -107,7 +116,7 @@ class ManagedObject {
|
||||
makeGray(): void {
|
||||
if (this != iter) {
|
||||
this.remove();
|
||||
to.insert(this);
|
||||
set2.insert(this);
|
||||
} else {
|
||||
iter = iter.prev;
|
||||
}
|
||||
@ -131,8 +140,8 @@ const enum State {
|
||||
var state = State.INIT;
|
||||
|
||||
// From and to spaces
|
||||
var from: ManagedObject;
|
||||
var to: ManagedObject;
|
||||
var set1: ManagedObject;
|
||||
var set2: ManagedObject;
|
||||
var iter: ManagedObject;
|
||||
|
||||
/** Performs a single step according to the current state. */
|
||||
@ -140,34 +149,40 @@ function gc_step(): void {
|
||||
var obj: ManagedObject;
|
||||
switch (state) {
|
||||
case State.INIT: {
|
||||
from = changetype<ManagedObject>(allocate_memory(ManagedObject.SIZE));
|
||||
from.nextWithFlags = changetype<usize>(from);
|
||||
from.prev = from;
|
||||
to = changetype<ManagedObject>(allocate_memory(ManagedObject.SIZE));
|
||||
to.nextWithFlags = changetype<usize>(to);
|
||||
to.prev = to;
|
||||
iter = to;
|
||||
set1 = changetype<ManagedObject>(memory.allocate(ManagedObject.SIZE));
|
||||
set1.clear();
|
||||
set2 = changetype<ManagedObject>(memory.allocate(ManagedObject.SIZE));
|
||||
set2.clear();
|
||||
iter = set2;
|
||||
// fall-through
|
||||
}
|
||||
case State.IDLE: {
|
||||
// start by marking roots
|
||||
__gc_iterate_roots(function mark_root(ref: usize): void {
|
||||
if (ref) {
|
||||
let obj = changetype<ManagedObject>(ref - ManagedObject.SIZE);
|
||||
obj.makeBlack();
|
||||
obj.visitFn(ref);
|
||||
}
|
||||
});
|
||||
state = State.MARK;
|
||||
break;
|
||||
}
|
||||
case State.MARK: {
|
||||
obj = iter.next;
|
||||
if (obj != to) {
|
||||
if (obj != set2) {
|
||||
iter = obj;
|
||||
obj.makeBlack();
|
||||
obj.visitFn(changetype<usize>(obj) + ManagedObject.SIZE);
|
||||
} else {
|
||||
obj = iter.next;
|
||||
if (obj == to) {
|
||||
let temp = from;
|
||||
from = to;
|
||||
to = temp;
|
||||
if (obj == set2) {
|
||||
let set1_ = set1;
|
||||
set1 = set2;
|
||||
set2 = set1_;
|
||||
Flags.WHITE ^= 1;
|
||||
Flags.BLACK ^= 1;
|
||||
iter = from.next;
|
||||
iter = set1.next;
|
||||
state = State.SWEEP;
|
||||
}
|
||||
}
|
||||
@ -175,12 +190,11 @@ function gc_step(): void {
|
||||
}
|
||||
case State.SWEEP: {
|
||||
obj = iter;
|
||||
if (obj != to) {
|
||||
if (obj !== set2) {
|
||||
iter = obj.next;
|
||||
free_memory(changetype<usize>(obj));
|
||||
memory.free(changetype<usize>(obj));
|
||||
} else {
|
||||
to.nextWithFlags = changetype<usize>(to);
|
||||
to.prev = to;
|
||||
set2.clear();
|
||||
state = State.IDLE;
|
||||
}
|
||||
break;
|
||||
@ -188,66 +202,42 @@ function gc_step(): void {
|
||||
}
|
||||
}
|
||||
|
||||
/** Allocates a managed object. */
|
||||
/** Garbage collector interface. */
|
||||
@global
|
||||
export function gc_allocate(
|
||||
size: usize,
|
||||
visitFn: (obj: usize) => void
|
||||
): usize {
|
||||
assert(size <= MAX_SIZE_32 - ManagedObject.SIZE);
|
||||
var obj = changetype<ManagedObject>(allocate_memory(ManagedObject.SIZE + size));
|
||||
obj.makeWhite();
|
||||
obj.visitFn = visitFn;
|
||||
from.insert(obj);
|
||||
return changetype<usize>(obj) + ManagedObject.SIZE;
|
||||
}
|
||||
export namespace gc {
|
||||
|
||||
/** Visits a reachable object. Called from the visitFn functions. */
|
||||
@global
|
||||
export function gc_visit(obj: ManagedObject): void {
|
||||
if (state == State.SWEEP) return;
|
||||
if (obj.isWhite) obj.makeGray();
|
||||
}
|
||||
|
||||
/** Registers a managed child object with its parent object. */
|
||||
@global
|
||||
export function gc_register(parent: ManagedObject, child: ManagedObject): void {
|
||||
if (parent.isBlack && child.isWhite) parent.makeGray();
|
||||
}
|
||||
|
||||
/** Iterates the root set. Provided by the compiler according to the program. */
|
||||
@global
|
||||
export declare function gc_roots(): void;
|
||||
|
||||
/** Performs a full garbage collection cycle. */
|
||||
@global
|
||||
export function gc_collect(): void {
|
||||
// begin collecting if not yet collecting
|
||||
switch (state) {
|
||||
case State.INIT:
|
||||
case State.IDLE: gc_step();
|
||||
/** Allocates a managed object. */
|
||||
export function alloc(
|
||||
size: usize,
|
||||
visitFn: (ref: usize) => void
|
||||
): usize {
|
||||
assert(size <= MAX_SIZE_32 - ManagedObject.SIZE);
|
||||
var obj = changetype<ManagedObject>(memory.allocate(ManagedObject.SIZE + size));
|
||||
obj.makeWhite();
|
||||
obj.visitFn = visitFn;
|
||||
set1.insert(obj);
|
||||
return changetype<usize>(obj) + ManagedObject.SIZE;
|
||||
}
|
||||
|
||||
/** Visits a reachable object. Called from the visitFn functions. */
|
||||
export function visit(obj: ManagedObject): void {
|
||||
if (state == State.SWEEP) return;
|
||||
if (obj.isWhite) obj.makeGray();
|
||||
}
|
||||
|
||||
/** References a managed child object from its parent object. */
|
||||
export function ref(parent: ManagedObject, child: ManagedObject): void {
|
||||
if (parent.isBlack && child.isWhite) parent.makeGray();
|
||||
}
|
||||
|
||||
/** Performs a full garbage collection cycle. */
|
||||
export function collect(): void {
|
||||
// begin collecting if not yet collecting
|
||||
switch (state) {
|
||||
case State.INIT:
|
||||
case State.IDLE: gc_step();
|
||||
}
|
||||
// finish the cycle
|
||||
while (state != State.IDLE) gc_step();
|
||||
}
|
||||
// finish the cycle
|
||||
while (state != State.IDLE) gc_step();
|
||||
}
|
||||
|
||||
declare function allocate_memory(size: usize): usize;
|
||||
declare function free_memory(ptr: usize): void;
|
||||
|
||||
// Considerations
|
||||
//
|
||||
// - An API that consists mostly of just replacing `allocate_memory` would be ideal, possibly taking
|
||||
// any additional number of parameters that are necessary, like the parent and the visitor.
|
||||
//
|
||||
// - Not having to generate a helper function for iterating globals but instead marking specific
|
||||
// nodes as roots could simplify the embedding, but whether this is feasible or not depends on its
|
||||
// performance characteristics and the possibility of tracking root status accross assignments.
|
||||
// For example, root status could be implemented as some sort of referenced-by-globals counting
|
||||
// and a dedicated list of root objects.
|
||||
//
|
||||
// - In 32-bit specifically, there is some free space in TLSF object headers due to alignment that
|
||||
// could be repurposed to store some GC information, like a class id. Certainly, this somewhat
|
||||
// depends on the efficiency of the used mechanism to detect this at compile time, including when
|
||||
// a different allocator is used.
|
||||
//
|
||||
// - Think about generations.
|
||||
|
Reference in New Issue
Block a user