Skip to content

Runtime

Daniel Wirtz edited this page May 26, 2019 · 27 revisions

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
    Like full 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
    Like stub 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.

Runtime header

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.

Runtime type information

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.

Reference counting

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.

 

Clone this wiki locally