-
-
Notifications
You must be signed in to change notification settings - Fork 667
Runtime
An AssemblyScript runtime adds support for the parts of memory management and garbage collection that can't be performed at compile-time, but must be performed during runtime, as the name suggest.
There are currently five variations to pick from using the --runtime
parameter:
-
--runtime full
TLSF memory allocator and PureRC garbage collector with the necessary exports to create managed objects externally. This is the default. -
--runtime half
Likefull
but without the exports. Useful where no high-level interaction with the host is required, so the optimizer can eliminate what's dead code. -
--runtime stub
Minimal arena memory allocator without support for freeing memory (no garbage collection) with the necessary exports to create managed objects externally. -
--runtime none
Likestub
but without the exports. Essentially evaporates after optimizations. -
--runtime pathToYourImplementation
The hard way: Your very own implementation of everything.
Previous versions of the compiler (pre 0.7) didn't include any runtime functionality by default, leaving it up to the developer to import relevant implementations manually, while newer versions of the compiler include a runtime suitable for most tasks.
The individual runtime functions are described at std/assembly/rt and additional usage instructions are included with the loader.
In order to provide the functionality mentioned above, each managed object (not annotated @unmanaged
) has a hidden header that contains the necessary information. Usually, one doesn't come into contact with the runtime header because it is "hidden", that means that it is located before the address that a reference points at. However, if you are for some reason thinking of tinkering with the runtime header directly, first be warned, then here's it's structure:
╒════════════════ 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
The allocator (TLSF) stores its information in the MM info
field:
╒═══════════════ MM info interpretation (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
├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┼─┴─┴─┴─╫─┴─┴─┴─┤
│ | FL │ SB = SL + AL │ ◄─ usize
└───────────────────────────────────────────────┴───────╨───────┘
FL: first level, SL: second level, AL: alignment, SB: small block
The reference counting implementation (PureRC) stores its information in the GC info
field:
╒═══════════════════ GC info interpretation ════════════════════╕
│ 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│
├─┼─┴─┴─┼─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤
│B│color│ refCount │
└─┴─────┴───────────────────────────────────────────────────────┘
B: buffered
The other two fields are the unique computed id of the respective object that can be obtained via idof<MyClass>()
and the actual size in bytes used by the object following the header, that is for example used to compute the .length
of a string or the .byteLength
of an ArrayBuffer. The total length of 16 bytes isn't coincidental by the way: The largest basic value in WebAssembly as of today is a 16 bytes v128
vector, hence an alignment of 16 bytes is the common denominator anyway and greatly simplifies the implementation.
To make it easier to work with managed objects externally, like allocating them or obtaining useful information about collections, the compiler inserts some minimal RTTI as a memory data segment. The address of RTTI can be obtained from the RTTI_BASE
global internally, respectively the __rtti
export externally.
╒═════════════════════ RTTI interpretation ═════════════════════╕
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
├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤ ◄─ RTTI_BASE
│ count │
╞═══════════════════════════════════════════════════════════════╡ ┐
│ Typeinfo#flags [id=0] │ id < count
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│ Typeinfo#base [id=0] │
├───────────────────────────────────────────────────────────────┤
│ ... │
For this, the compiler maps concrete classes to a unique runtime id. The ids 0 (ArrayBuffer), 1 (String) and 2 (ArrayBufferView) are always the same, with all the others sequentially representing classes that are become actually compiled. Unmanaged classes (those annotated @unmanaged
) are treated like ArrayBuffers. The .base
field is the id of the base class, if a derived class, and the .flags
field includes a set of flags describing the general shape/capabilities of an object, described in std/assembly/shared/typeinfo.ts. The loader for example makes use of RTTI and is probably the best example.
AssemblyScript uses a variant of automatic reference counting (ARC) that automatically inserts calls to __retain
(increments reference count by 1) and __release
(decrements reference count by 1). When the reference count of an object reaches 0, it becomes collected. It also includes a deferred cycle collector that is able to resolve and collect structures composed of cyclic references. The exact mechanism used is described in this paper with some adjustments explained in this repo. It's inner workings are very similar to that of other reference counted languages, with about the same pros and cons. In general, reference counting has the advantage that it runs concurrently with a program, while having the disadvantage that it might do too much work if optimized poorly. We nonetheless decided for ARC because it has relatively predictable performance, which makes it a good fit for WebAssembly, which is about exactly that, and TLSF as the memory allocator.
Detailed information, including how the low level API works, is available within the runtime's README and sources.