Initial implementation if ugc, see #16; Fix tests

This commit is contained in:
dcodeIO
2018-01-18 01:46:41 +01:00
parent 461daab2a2
commit 9cdfa35938
24 changed files with 715 additions and 173 deletions

View File

@ -1,5 +1,5 @@
TLSF
====
TLSF memory allocator
=====================
A port of [Matt Conte's implementation](https://github.com/mattconte/tlsf) of the [TLSF](http://www.gii.upv.es/tlsf/) memory allocator to AssemblyScript.

View File

@ -1,4 +1,4 @@
tlsf.ts is based on:
tlsf.ts is based on https://github.com/mattconte/tlsf
Two Level Segregated Fit memory allocator, version 3.1.
Written by Matthew Conte

View File

@ -35,7 +35,7 @@ class BlockHeader {
static readonly OVERHEAD: usize = sizeof<usize>();
// User data starts directly after the size field in a used block.
static readonly USERDATA_OFFSET: usize = sizeof<usize>() + sizeof<usize>();
static readonly DATA_OFFSET: usize = sizeof<usize>() + sizeof<usize>();
// A free block must be large enough to store its header minus the size of
// the prev_phys_block field, and no larger than the number of addressable
@ -114,12 +114,12 @@ class BlockHeader {
/** Gets the block header matching the specified data pointer. */
static fromDataPtr(ptr: usize): BlockHeader {
return changetype<BlockHeader>(ptr - BlockHeader.USERDATA_OFFSET);
return changetype<BlockHeader>(ptr - BlockHeader.DATA_OFFSET);
}
/** Returns the address of this block's data. */
toDataPtr(): usize {
return changetype<usize>(this) + BlockHeader.USERDATA_OFFSET;
return changetype<usize>(this) + BlockHeader.DATA_OFFSET;
}
/** Gets the next block after this one using the specified size. */
@ -174,7 +174,7 @@ class BlockHeader {
return this.size >= BlockHeader.SIZE + size;
}
/* Splits a block into two, the second of which is free. */
/** Splits a block into two, the second of which is free. */
split(size: usize): BlockHeader {
// Calculate the amount of space left in the remaining block.
var remain = BlockHeader.fromOffset(
@ -194,7 +194,7 @@ class BlockHeader {
return remain;
}
/* Absorb a free block's storage into this (adjacent previous) free block. */
/** Absorb a free block's storage into this (adjacent previous) free block. */
absorb(block: BlockHeader): void {
assert(!this.isLast,
"previous block can't be last"
@ -205,7 +205,7 @@ class BlockHeader {
}
}
/* The TLSF control structure. */
/** The TLSF control structure. */
@explicit
class Control extends BlockHeader { // Empty lists point here, indicating free
@ -289,7 +289,7 @@ class Control extends BlockHeader { // Empty lists point here, indicating free
this.insertFreeBlock(block, fl_out, sl_out);
}
/* Inserts a free block into the free block list. */
/** Inserts a free block into the free block list. */
insertFreeBlock(block: BlockHeader, fl: i32, sl: i32): void {
var current = this.blocks(fl, sl);
assert(current,
@ -311,7 +311,7 @@ class Control extends BlockHeader { // Empty lists point here, indicating free
this.sl_bitmap_set(fl, this.sl_bitmap(fl) | (1 << sl))
}
/* Removes a free block from the free list.*/
/** Removes a free block from the free list.*/
removeFreeBlock(block: BlockHeader, fl: i32, sl: i32): void {
var prev = block.prev_free;
var next = block.next_free;

20
examples/ugc/README.md Normal file
View File

@ -0,0 +1,20 @@
μgc garbage collector
=====================
A port of [Bach Le's μgc garbage collector library](https://github.com/bullno1/ugc) to AssemblyScript.
Instructions
------------
To build [assembly/ugc.ts](./assembly/ugc.ts) to an untouched and an optimized `.wasm` including their respective `.wast` representations, run:
```
$> npm run build
```
Afterwards, to run the included [test](./tests/index.js):
```
$> npm install
$> npm test
```

View File

@ -0,0 +1,25 @@
ugc.ts is based on https://github.com/bullno1/ugc
Copyright (c) 2017, Bach Le
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,6 @@
{
"extends": "../../../std/assembly.json",
"include": [
"./**/*.ts"
]
}

View File

@ -0,0 +1,287 @@
/////////////////////////// μ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));
}

11
examples/ugc/package.json Normal file
View File

@ -0,0 +1,11 @@
{
"name": "@assemblyscript/ugc",
"version": "1.0.0",
"private": true,
"scripts": {
"build": "npm run build:untouched && npm run build:optimized",
"build:untouched": "asc assembly/ugc.ts -t ugc.untouched.wast -b ugc.untouched.wasm --validate",
"build:optimized": "asc -O assembly/ugc.ts -b ugc.optimized.wasm -t ugc.optimized.wast --validate --noAssert --runPasses inlining",
"test": "node tests"
}
}

View File

@ -0,0 +1,26 @@
var fs = require("fs");
// NOTE that this doesn't do anything useful, yet
var ugc = new WebAssembly.Instance(new WebAssembly.Module(fs.readFileSync(__dirname + "/../ugc.untouched.wasm"))).exports;
function mem(memory, offset, count) {
if (!offset) offset = 0;
if (!count) count = 1024;
var mem = new Uint8Array(memory.buffer, offset);
var stackTop = new Uint32Array(memory.buffer, 4, 1)[0];
var hex = [];
for (var i = 0; i < count; ++i) {
var o = (offset + i).toString(16);
while (o.length < 3) o = "0" + o;
if ((i & 15) === 0) {
hex.push("\n" + o + ":");
}
var h = mem[i].toString(16);
if (h.length < 2) h = "0" + h;
hex.push(h);
}
console.log(hex.join(" ") + " ...");
}
mem(ugc.memory, 0, 1024);