mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-06-22 11:11:43 +00:00
Initial new rt integration
This commit is contained in:
@ -8,29 +8,68 @@ It is based on [the TLSF memory manager](./tlsf.ts) and [a pure reference counti
|
||||
Interface
|
||||
---------
|
||||
|
||||
* **__rt_allocate**(size: `usize`, id: `u32` = 0): `usize`<br />
|
||||
* **__alloc**(size: `usize`, id: `u32` = 0): `usize`<br />
|
||||
Dynamically allocates a chunk of memory of at least the specified size and returns its address.
|
||||
Alignment is guaranteed to be 16 bytes to fit up to v128 values naturally.
|
||||
|
||||
* **__rt_reallocate**(ref: `usize`, size: `usize`): `usize`<br />
|
||||
* **__realloc**(ref: `usize`, size: `usize`): `usize`<br />
|
||||
Dynamically changes the size of a chunk of memory, possibly moving it to a new address.
|
||||
|
||||
* **__rt_free**(ref: `usize`): `void`<br />
|
||||
* **__free**(ref: `usize`): `void`<br />
|
||||
Frees a dynamically allocated chunk of memory by its address.
|
||||
|
||||
* **__rt_retain**(ref: `usize`): `void`<br />
|
||||
Retains a reference.
|
||||
* **__retain**(ref: `usize`): `void`<br />
|
||||
Retains a reference to an instance of a reference type. The instance doesn't become collected as long as there's at least one retained reference.
|
||||
|
||||
* **__rt_release**(ref: `usize`): `void`<br />
|
||||
Releases a reference.
|
||||
* **__release**(ref: `usize`): `void`<br />
|
||||
Releases a reference to an instance of a reference type. The instance is considered for collection once all references to it have been released.
|
||||
|
||||
* **__rt_collect**(): `void`<br />
|
||||
Forces a full garbage collection cycle.
|
||||
* **__collect**(): `void`<br />
|
||||
Forces a full garbage collection cycle. By default this means that reference cycles are resolved and possibly collected.
|
||||
|
||||
* **__rt_typeinfo**(id: `u32`): `void`<br />
|
||||
Obtains the runtime type information for objects of the kind represented by the specified id.
|
||||
* **__visit**(ref: `usize`, cookie: `u32`): `void`<br />
|
||||
Concrete visitor implementation called during traversal. Cookie can be used to indicate one of multiple operations.
|
||||
|
||||
Built-ins
|
||||
---------
|
||||
|
||||
The following functions are generated by the compiler based on compile-time information that wouldn't be available or inefficient to provide otherwise.
|
||||
|
||||
* **__info**(id: `u32`): `void`<br />
|
||||
Obtains the runtime type information for objects with the specified runtime id. Runtime type information is a set of flags indicating whether a reference type is managed, an array or similar, and what the relevant alignments when creating an instance are etc.
|
||||
|
||||
* **__visit_globals**(cookie: `u32`)<br />
|
||||
Calls `__visit` on each global that is of a reference type. Not used anymore (originally provided to support tracing GCs) but still here for possible future use.
|
||||
|
||||
* **__visit_members**(ref: `usize`, cookie: `u32`)<br />
|
||||
Calls `__visit` on each member of the instance pointed to by `ref` that is of a reference type.
|
||||
|
||||
Stub
|
||||
----
|
||||
|
||||
The fully functional yet minimal [stub implementation](./stub.ts) provides dynamic memory allocation only but doesn't include sophisticated support to deallocate objects. Useful for prototyping or very short-lived programs with hardly any memory footprint.
|
||||
A fully functional yet minimal (as in code size) [stub implementation](./index-stub.ts) that provides dynamic memory allocation but no deallocation. Useful for prototyping or very short-lived programs with hardly any memory footprint. The [none implementation](./index-none.ts) is the same as the stub implementation without any runtime exports.
|
||||
|
||||
Integration notes
|
||||
-----------------
|
||||
|
||||
Working with the runtime internals within standard library code can be tricky and requires knowledge of where the compiler will insert runtime calls automatically. For example, whenever a value of a reference type is assigned to a local, a global or a field, the compiler *might* insert a `__retain` call, respectively whenever such a value becomes unassigned from one, *might* insert a `__release` call. When a value is handled as an `usize` (i.e. when it comes from `__alloc` or is `changetype<usize>`ed), no such insertion happens (afterwards), but as soon as a `changetype<RefType>`ed (again), the side-effects introduced by automatic insertion must be understood.
|
||||
|
||||
A `__retain` call is inserted when a value of a reference type
|
||||
* is assigned to a local, global or a field **if** the value is not already the exact same value as stored before
|
||||
* is an argument to a function call, including `this` (i.e. `str.indexOf` retains `str`)
|
||||
* is returned from a function (i.e. no need to manually `__retain` if explicitly `changetype`d)
|
||||
|
||||
A `__release` call is inserted when a value of a reference type
|
||||
* becomes unassigned from a local, global or a field due to assigning a new value **if** the value is not already the exact same value as stored before
|
||||
* is popped together with its local from the current scope, i.e. a local declared with `let` in a block, or otherwise at the end of a function
|
||||
|
||||
If not taken into account properly
|
||||
* a memory leak will occur when `__retain`ed more often than intended
|
||||
* a double-free will occur when `__release`d more often than intended
|
||||
|
||||
Also note that a `load<T>(x)` with a reference type acts like a `changetype<T>(load<usize>(x))` and does not `__retain` unless the result is assigned to a local.
|
||||
|
||||
Some best practices are:
|
||||
* Use the fresh `__alloc`ed reference in `usize` form where possible, e.g. when just copying raw bytes is necessary, and `changetype` it once on return.
|
||||
* When providing such a `usize` reference to a function, if the value isn't needed anymore afterwards, just `changetype` it on the call which will `__retain` and `__release` it automatically, including freeing it if wasn't retained before, or, if still needed afterwards, assign the `changetype`d reference to a local first and provide the local as the argument, hence keeping the reference alive as long as the local or any subsequent target is.
|
||||
* If it's not avoidable to `changetype` to the actual reference type, do it inline in an expression and avoid assigning to a local.
|
||||
|
@ -12,10 +12,22 @@
|
||||
// @ts-ignore: decorator
|
||||
@inline export const DEBUG = true;
|
||||
|
||||
/** Common block structure. */
|
||||
@unmanaged export class CommonBlock {
|
||||
// ╒════════════════ Common block 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
|
||||
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤
|
||||
// │ MM info │ -16
|
||||
// ├───────────────────────────────────────────────────────────────┤
|
||||
// │ GC info │ -12
|
||||
// ├───────────────────────────────────────────────────────────────┤
|
||||
// │ runtime id │ -8
|
||||
// ├───────────────────────────────────────────────────────────────┤
|
||||
// │ runtime size │ -4
|
||||
// ╞═══════════════════════════════════════════════════════════════╡
|
||||
// │ ... │ ref
|
||||
@unmanaged export class BLOCK {
|
||||
/** Memory manager info. */
|
||||
mmInfo: usize; // WASM64 might need adaption
|
||||
mmInfo: usize; // WASM64 needs adaption
|
||||
/** Garbage collector info. */
|
||||
gcInfo: u32;
|
||||
/** Runtime class id. */
|
||||
@ -24,16 +36,53 @@
|
||||
rtSize: u32;
|
||||
}
|
||||
|
||||
/////////////////////////////////// Type information interface ////////////////////////////////////
|
||||
|
||||
import { RTTI_BASE } from "../runtime";
|
||||
import { RTTIData } from "../common/rtti";
|
||||
// @ts-ignore: decorator
|
||||
@inline export const BLOCK_OVERHEAD = (offsetof<BLOCK>() + AL_MASK) & ~AL_MASK;
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
function __rt_typeinfo(id: u32): u32 {
|
||||
var ptr: usize = RTTI_BASE;
|
||||
@inline export const BLOCK_MAXSIZE: usize = (1 << 30) - BLOCK_OVERHEAD;
|
||||
|
||||
/////////////////////////////////// Type information interface ////////////////////////////////////
|
||||
|
||||
import { RTTI_BASE } from "builtins";
|
||||
import { RTTIData, RTTIFlags } from "common/rtti";
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@unsafe @global
|
||||
export function __typeinfo(id: u32): RTTIFlags {
|
||||
var ptr = RTTI_BASE;
|
||||
return !id || id > load<u32>(ptr)
|
||||
? unreachable()
|
||||
: changetype<RTTIData>(ptr + id * offsetof<RTTIData>()).flags;
|
||||
}
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@unsafe @global
|
||||
export function __instanceof(ref: usize, superId: u32): bool { // keyword
|
||||
var id = changetype<BLOCK>(ref - BLOCK_OVERHEAD).rtId;
|
||||
var ptr = RTTI_BASE;
|
||||
if (id && id <= load<u32>(ptr)) {
|
||||
do if (id == superId) return true;
|
||||
while (id = changetype<RTTIData>(ptr + id * offsetof<RTTIData>()).base);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////// Helpers /////////////////////////////////////////////
|
||||
|
||||
import { idof } from "builtins";
|
||||
import { ArrayBufferView } from "arraybuffer";
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@unsafe @global
|
||||
export function __allocArray(length: i32, alignLog2: usize, id: u32, data: usize = 0): usize {
|
||||
var array = __alloc(offsetof<i32[]>(), id);
|
||||
var bufferSize = <usize>length << alignLog2;
|
||||
var buffer = __alloc(bufferSize, idof<ArrayBuffer>());
|
||||
changetype<ArrayBufferView>(array).data = changetype<ArrayBuffer>(buffer); // TODO/RT: retains
|
||||
changetype<ArrayBufferView>(array).dataStart = buffer;
|
||||
changetype<ArrayBufferView>(array).dataLength = bufferSize;
|
||||
store<i32>(changetype<usize>(array), length, offsetof<i32[]>("length_"));
|
||||
if (data) memory.copy(buffer, data, bufferSize);
|
||||
return array;
|
||||
}
|
||||
|
3
std/assembly/rt/index-full.ts
Normal file
3
std/assembly/rt/index-full.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { __alloc, __realloc, __free } from "./tlsf";
|
||||
export { __retain, __release, __collect } from "./purerc";
|
||||
export { __instanceof, __typeinfo } from "./common";
|
1
std/assembly/rt/index-none.ts
Normal file
1
std/assembly/rt/index-none.ts
Normal file
@ -0,0 +1 @@
|
||||
import "rt/index-stub";
|
@ -1,12 +1,4 @@
|
||||
import { AL_MASK, CommonBlock } from "./common";
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@inline
|
||||
const BLOCK_OVERHEAD = (offsetof<CommonBlock>() + AL_MASK) & ~AL_MASK;
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@inline
|
||||
const BLOCK_MAXSIZE: usize = (1 << 30) - BLOCK_OVERHEAD; // match TLSF
|
||||
import { AL_MASK, BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "rt/common";
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@lazy
|
||||
@ -16,11 +8,9 @@ var startOffset: usize = (HEAP_BASE + AL_MASK) & ~AL_MASK;
|
||||
@lazy
|
||||
var offset: usize = startOffset;
|
||||
|
||||
//////////////////////////////////// Memory manager interface /////////////////////////////////////
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@unsafe @global
|
||||
export function __rt_allocate(size: usize, id: u32): usize {
|
||||
export function __alloc(size: usize, id: u32): usize {
|
||||
if (size > BLOCK_MAXSIZE) unreachable();
|
||||
var ptr = offset + BLOCK_OVERHEAD;
|
||||
var newPtr = (ptr + max<usize>(size, 1) + AL_MASK) & ~AL_MASK;
|
||||
@ -33,7 +23,7 @@ export function __rt_allocate(size: usize, id: u32): usize {
|
||||
}
|
||||
}
|
||||
offset = newPtr;
|
||||
var block = changetype<CommonBlock>(ptr - BLOCK_OVERHEAD);
|
||||
var block = changetype<BLOCK>(ptr - BLOCK_OVERHEAD);
|
||||
block.rtId = id;
|
||||
block.rtSize = size;
|
||||
return ptr;
|
||||
@ -41,11 +31,11 @@ export function __rt_allocate(size: usize, id: u32): usize {
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@unsafe @global
|
||||
export function __rt_reallocate(ref: usize, size: usize): usize {
|
||||
var block = changetype<CommonBlock>(ref - BLOCK_OVERHEAD);
|
||||
export function __realloc(ref: usize, size: usize): usize {
|
||||
var block = changetype<BLOCK>(ref - BLOCK_OVERHEAD);
|
||||
var oldSize = <usize>block.rtSize;
|
||||
if (size > oldSize) {
|
||||
let newRef = __rt_allocate(size, block.rtId);
|
||||
let newRef = __alloc(size, block.rtId);
|
||||
memory.copy(newRef, ref, oldSize);
|
||||
ref = newRef;
|
||||
} else {
|
||||
@ -56,30 +46,40 @@ export function __rt_reallocate(ref: usize, size: usize): usize {
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@unsafe @global
|
||||
export function __rt_free(ref: usize): void {
|
||||
export function __free(ref: usize): void {
|
||||
}
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@unsafe @global
|
||||
export function __rt_reset(): void { // special
|
||||
offset = startOffset;
|
||||
}
|
||||
|
||||
/////////////////////////////////// Garbage collector interface ///////////////////////////////////
|
||||
// @unsafe @global
|
||||
// export function __reset(): void { // special
|
||||
// offset = startOffset;
|
||||
// }
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
export function __rt_retain(ref: usize): void {
|
||||
export function __retain(ref: usize): usize {
|
||||
return ref;
|
||||
}
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
export function __rt_release(ref: usize): void {
|
||||
export function __release(ref: usize): void {
|
||||
}
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
export function __rt_collect(): void {
|
||||
function __visit(ref: usize, cookie: u32): void {
|
||||
}
|
||||
|
||||
export { __rt_typeinfo };
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
function __retainRelease(ref: usize, oldRef: usize): usize {
|
||||
return ref;
|
||||
}
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
export function __collect(): void {
|
||||
}
|
||||
|
||||
export { __instanceof, __typeinfo } from "rt/common";
|
13
std/assembly/rt/index.d.ts
vendored
Normal file
13
std/assembly/rt/index.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
declare function __alloc(size: usize, id: u32): usize;
|
||||
declare function __realloc(ref: usize, size: usize): usize;
|
||||
declare function __free(ref: usize): void;
|
||||
declare function __retain(ref: usize): void;
|
||||
declare function __release(ref: usize): void;
|
||||
declare function __retainRelease(ref: usize, oldRef: usize): usize;
|
||||
declare function __collect(): void;
|
||||
declare function __typeinfo(id: u32): u32;
|
||||
declare function __instanceof(ref: usize, superId: u32): bool;
|
||||
declare function __visit(ref: usize, cookie: i32): void;
|
||||
declare function __visit_globals(cookie: u32): void;
|
||||
declare function __visit_members(ref: usize, cookie: u32): void;
|
||||
declare function __allocArray(length: i32, alignLog2: usize, id: u32, data?: usize): usize;
|
@ -1,58 +0,0 @@
|
||||
import { AL_MASK, DEBUG } from "./common";
|
||||
|
||||
//////////////////////////////////// Memory manager interface /////////////////////////////////////
|
||||
|
||||
import { ROOT, Block, BLOCK_OVERHEAD, initializeRoot, allocateBlock, reallocateBlock, freeBlock } from "./tlsf";
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
export function __rt_allocate(size: usize, id: u32): usize {
|
||||
var root = ROOT;
|
||||
if (!root) {
|
||||
initializeRoot();
|
||||
root = ROOT;
|
||||
}
|
||||
var block = allocateBlock(root, size);
|
||||
block.rtId = id;
|
||||
return changetype<usize>(block) + BLOCK_OVERHEAD;
|
||||
}
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
export function __rt_reallocate(ref: usize, size: usize): usize {
|
||||
if (DEBUG) assert(ROOT); // must be initialized
|
||||
assert(ref != 0 && !(ref & AL_MASK)); // must exist and be aligned
|
||||
return changetype<usize>(reallocateBlock(ROOT, changetype<Block>(ref - BLOCK_OVERHEAD), size)) + BLOCK_OVERHEAD;
|
||||
}
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
export function __rt_free(ref: usize): void {
|
||||
if (DEBUG) assert(ROOT); // must be initialized
|
||||
assert(ref != 0 && !(ref & AL_MASK)); // must exist and be aligned
|
||||
freeBlock(ROOT, changetype<Block>(ref - BLOCK_OVERHEAD));
|
||||
}
|
||||
|
||||
/////////////////////////////////// Garbage collector interface ///////////////////////////////////
|
||||
|
||||
import { increment, decrement, collectCycles } from "./pure";
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
export function __rt_retain(ref: usize): void {
|
||||
if (ref) increment(changetype<Block>(ref - BLOCK_OVERHEAD));
|
||||
}
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
export function __rt_release(ref: usize): void {
|
||||
if (ref) decrement(changetype<Block>(ref - BLOCK_OVERHEAD));
|
||||
}
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
export function __rt_collect(): void {
|
||||
collectCycles();
|
||||
}
|
||||
|
||||
export { __rt_typeinfo };
|
@ -1,14 +1,10 @@
|
||||
import { DEBUG } from "./common";
|
||||
import { Block, freeBlock, ROOT } from "./tlsf";
|
||||
import { DEBUG, BLOCK_OVERHEAD, BLOCK } from "rt/common";
|
||||
import { Block, freeBlock, ROOT } from "rt/tlsf";
|
||||
import { RTTIFlags } from "common/rtti";
|
||||
|
||||
/////////////////////////// A Pure Reference Counting Garbage Collector ///////////////////////////
|
||||
// see: https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon03Pure.pdf
|
||||
|
||||
// TODO: make visitors eat cookies so we can compile direct calls into a switch
|
||||
function __rt_visit_members(s: Block, cookie: i32): void { unreachable(); }
|
||||
function __rt_flags(classId: u32): u32 { return unreachable(); }
|
||||
const ACYCLIC_FLAG: u32 = 0;
|
||||
|
||||
// ╒══════════════════════ GC Info structure ══════════════════════╕
|
||||
// │ 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│
|
||||
@ -66,7 +62,9 @@ const ACYCLIC_FLAG: u32 = 0;
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global
|
||||
function __rt_visit(s: Block, cookie: i32): void {
|
||||
function __visit(ref: usize, cookie: i32): void {
|
||||
if (ref < HEAP_BASE) return;
|
||||
var s = changetype<Block>(ref - BLOCK_OVERHEAD);
|
||||
switch (cookie) {
|
||||
case VISIT_DECREMENT: {
|
||||
decrement(s);
|
||||
@ -100,18 +98,18 @@ function __rt_visit(s: Block, cookie: i32): void {
|
||||
}
|
||||
|
||||
/** Increments the reference count of the specified block by one.*/
|
||||
export function increment(s: Block): void {
|
||||
function increment(s: Block): void {
|
||||
var info = s.gcInfo;
|
||||
assert((info & ~REFCOUNT_MASK) == ((info + 1) & ~REFCOUNT_MASK)); // overflow
|
||||
s.gcInfo = info + 1;
|
||||
}
|
||||
|
||||
/** Decrements the reference count of the specified block by one, possibly freeing it. */
|
||||
export function decrement(s: Block): void {
|
||||
function decrement(s: Block): void {
|
||||
var info = s.gcInfo;
|
||||
var rc = info & REFCOUNT_MASK;
|
||||
if (rc == 1) {
|
||||
__rt_visit_members(s, VISIT_DECREMENT);
|
||||
__visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_DECREMENT);
|
||||
if (!(info & BUFFERED_MASK)) {
|
||||
freeBlock(ROOT, s);
|
||||
} else {
|
||||
@ -119,7 +117,7 @@ export function decrement(s: Block): void {
|
||||
}
|
||||
} else {
|
||||
if (DEBUG) assert(rc > 0);
|
||||
if (!(__rt_flags(s.rtId) & ACYCLIC_FLAG)) {
|
||||
if (!(__typeinfo(s.rtId) & RTTIFlags.ACYCLIC)) {
|
||||
s.gcInfo = BUFFERED_MASK | COLOR_PURPLE | (rc - 1);
|
||||
if (!(info & BUFFERED_MASK)) {
|
||||
appendRoot(s);
|
||||
@ -164,7 +162,9 @@ function growRoots(): void {
|
||||
}
|
||||
|
||||
/** Collects cyclic garbage. */
|
||||
export function collectCycles(): void {
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
export function __collect(): void {
|
||||
|
||||
// markRoots
|
||||
var roots = ROOTS;
|
||||
@ -205,7 +205,7 @@ function markGray(s: Block): void {
|
||||
var info = s.gcInfo;
|
||||
if ((info & COLOR_MASK) != COLOR_GRAY) {
|
||||
s.gcInfo = (info & ~COLOR_MASK) | COLOR_GRAY;
|
||||
__rt_visit_members(s, VISIT_MARKGRAY);
|
||||
__visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_MARKGRAY);
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,7 +217,7 @@ function scan(s: Block): void {
|
||||
scanBlack(s);
|
||||
} else {
|
||||
s.gcInfo = (info & ~COLOR_MASK) | COLOR_WHITE;
|
||||
__rt_visit_members(s, VISIT_SCAN);
|
||||
__visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_SCAN);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -225,7 +225,7 @@ function scan(s: Block): void {
|
||||
/** Marks a block as black (in use) if it was found to be reachable during the collection phase. */
|
||||
function scanBlack(s: Block): void {
|
||||
s.gcInfo = (s.gcInfo & ~COLOR_MASK) | COLOR_BLACK;
|
||||
__rt_visit_members(s, VISIT_SCANBLACK);
|
||||
__visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_SCANBLACK);
|
||||
}
|
||||
|
||||
/** Collects all white (member of a garbage cycle) nodes when completing the collection phase. */
|
||||
@ -233,7 +233,31 @@ function collectWhite(s: Block): void {
|
||||
var info = s.gcInfo;
|
||||
if ((info & COLOR_MASK) == COLOR_WHITE && !(info & BUFFERED_MASK)) {
|
||||
// s.gcInfo = (info & ~COLOR_MASK) | COLOR_BLACK;
|
||||
__rt_visit_members(s, VISIT_COLLECTWHITE);
|
||||
__visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_COLLECTWHITE);
|
||||
}
|
||||
freeBlock(ROOT, s);
|
||||
}
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
export function __retain(ref: usize): usize {
|
||||
if (ref > HEAP_BASE) increment(changetype<Block>(ref - BLOCK_OVERHEAD));
|
||||
return ref;
|
||||
}
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
export function __release(ref: usize): void {
|
||||
if (ref > HEAP_BASE) decrement(changetype<Block>(ref - BLOCK_OVERHEAD));
|
||||
}
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
export function __retainRelease(ref: usize, oldRef: usize): usize {
|
||||
if (ref != oldRef) {
|
||||
let heapBase = HEAP_BASE;
|
||||
if (ref > heapBase) increment(changetype<Block>(ref - BLOCK_OVERHEAD));
|
||||
if (oldRef > heapBase) decrement(changetype<Block>(oldRef - BLOCK_OVERHEAD));
|
||||
}
|
||||
return ref;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { AL_BITS, AL_SIZE, AL_MASK, DEBUG, CommonBlock } from "./common";
|
||||
import { AL_BITS, AL_MASK, DEBUG, BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "rt/common";
|
||||
|
||||
/////////////////////// The TLSF (Two-Level Segregate Fit) memory allocator ///////////////////////
|
||||
// see: http://www.gii.upv.es/tlsf/
|
||||
@ -69,7 +69,7 @@ import { AL_BITS, AL_SIZE, AL_MASK, DEBUG, CommonBlock } from "./common";
|
||||
// │ if free: back ▲ │ ◄─┘
|
||||
// └───────────────────────────────────────────────────────────────┘ payload ┘ >= MIN SIZE
|
||||
// F: FREE, L: LEFTFREE
|
||||
@unmanaged export class Block extends CommonBlock {
|
||||
@unmanaged export class Block extends BLOCK {
|
||||
|
||||
/** Previous free block, if any. Only valid if free, otherwise part of payload. */
|
||||
prev: Block | null;
|
||||
@ -79,15 +79,13 @@ import { AL_BITS, AL_SIZE, AL_MASK, DEBUG, CommonBlock } from "./common";
|
||||
// If the block is free, there is a 'back'reference at its end pointing at its start.
|
||||
}
|
||||
|
||||
// Block constants. Overhead is always present, no matter if free or used. Also, a block must have
|
||||
// a minimum size of three pointers so it can hold `prev`, `next` and `back` if free.
|
||||
// Block constants. A block must have a minimum size of three pointers so it can hold `prev`,
|
||||
// `next` and `back` if free.
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@inline export const BLOCK_OVERHEAD: usize = (offsetof<Block>("prev") + AL_MASK) & ~AL_MASK;
|
||||
@inline const BLOCK_MINSIZE: usize = (3 * sizeof<usize>() + AL_MASK) & ~AL_MASK; // prev + next + back
|
||||
// @ts-ignore: decorator
|
||||
@inline const BLOCK_MINSIZE: usize = (3 * sizeof<usize>() + AL_MASK) & ~AL_MASK;// prev + next + back
|
||||
// @ts-ignore: decorator
|
||||
@inline const BLOCK_MAXSIZE: usize = 1 << (FL_BITS + SB_BITS - 1); // exclusive
|
||||
// @inline const BLOCK_MAXSIZE: usize = 1 << (FL_BITS + SB_BITS - 1); // exclusive, lives in common.ts
|
||||
|
||||
/** Gets the left block of a block. Only valid if the left block is free. */
|
||||
// @ts-ignore: decorator
|
||||
@ -532,3 +530,32 @@ export function freeBlock(root: Root, block: Block): void {
|
||||
block.mmInfo = blockInfo | FREE;
|
||||
insertBlock(root, block);
|
||||
}
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
export function __alloc(size: usize, id: u32): usize {
|
||||
var root = ROOT;
|
||||
if (!root) {
|
||||
initializeRoot();
|
||||
root = ROOT;
|
||||
}
|
||||
var block = allocateBlock(root, size);
|
||||
block.rtId = id;
|
||||
return changetype<usize>(block) + BLOCK_OVERHEAD;
|
||||
}
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
export function __realloc(ref: usize, size: usize): usize {
|
||||
if (DEBUG) assert(ROOT); // must be initialized
|
||||
assert(ref != 0 && !(ref & AL_MASK)); // must exist and be aligned
|
||||
return changetype<usize>(reallocateBlock(ROOT, changetype<Block>(ref - BLOCK_OVERHEAD), size)) + BLOCK_OVERHEAD;
|
||||
}
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
export function __free(ref: usize): void {
|
||||
if (DEBUG) assert(ROOT); // must be initialized
|
||||
assert(ref != 0 && !(ref & AL_MASK)); // must exist and be aligned
|
||||
freeBlock(ROOT, changetype<Block>(ref - BLOCK_OVERHEAD));
|
||||
}
|
||||
|
Reference in New Issue
Block a user