-
-
Notifications
You must be signed in to change notification settings - Fork 667
Runtime
NOTE: This is a work in progress and the respective PR has not yet been merged into master.
AssemblyScript provides a set of runtime templates to automatically include support for managed objects when compiling a program. As the name says, a runtime template provides functionality that performs certain operations, that cannot be performed at compile time, during runtime, like, for example:
- Include a memory allocator to be able to use
new
expressions / instantiate managed classes - Include a garbage collector that either traces or counts the references of unreachable objects
Previous versions of the compiler 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 default runtime suitable for most tasks. The runtime template to include can be specified with the --runtime
flag:
-
--runtime default
The runtime that is included by default, consisting of the TLSF memory allocator and the ITCM garbage collector. -
--runtime arena
A very small prototyping runtime without free/GC support. Equivalent to just importingallocator/arena
. -
--runtime none
Just basic runtime functionality likeinstanceof
. Equivalent to the initial bare-bones behaviour but now also including a runtime header on managed objects.
The --runtime
option can also be used to specify an additional entry file relative to baseDir
that sets up custom runtime functionality, i.e. picks another memory allocator or garbage collector, provides additional global functions, you name it.
Depending on which runtime is used, all or part of the following interface is exposed externally. The reason for having these functions in the first place is that managed objects must be set up to function properly, like taking care of the runtime header and the ins and outs of garbage collection.
-
$.newObject(payloadSize:
u32
, id:u32
):usize
Allocates and registers, but doesn't initialize the data of, a new managed object of the specified kind. -
$.newString(length:
i32
):usize
Allocates and registers, but doesn't initialize the data of, a newString
of the specified length. -
$.newArrayBuffer(byteLength:
i32
):usize
Allocates and registers, but doesn't initialize the data of, a newArrayBuffer
of the specified byteLength. -
$.newArray(id:
u32
, buffer:usize
= 0):usize
Allocates and registers a newArray
of the specified kind using the given backing buffer. -
$.instanceof(ref:
usize
, id:u32
): bool
Determines whether a managed object is considered to be an instance of the class represented by the specified runtime id. -
$.retain(ref:
usize
):void
Retains a managed object externally, making sure that it doesn't become collected. -
$.release(ref:
usize
):void
Releases a managed object externally, allowing it to become collected. -
$.collect():
void
Performs a full garbage collection cycle. -
$.setArgc(argc:
i32
):void
Sets the number of present arguments of the next varargs call. The loader does this automatically.
In order to provide basic functionality like instanceof
, 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.
╒════════════════════ Managed header (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
├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤ ┐
│ .classId unique id of the respective class │
├───────────────────────────────────────────────────────────────┤
│ .payloadLength size of 'payload' │
├───────────────────────────────────────────────────────────────┤ SIZE w/o GC ┘
│ .reserved1 reserved space for GC │ ◄─┐
├───────────────────────────────────────────────────────────────┤ usize
│ .reserved2 reserved space for GC │ ◄─┘
╞═══════════════════════════════════════════════════════════════╡ SIZE w/ GC ┘
│ ... payload, reference points here ... |
The reserved
fields are only present if a garbage collector has been included, so the header has a size of 8 bytes in WASM32 without a GC respectively 16 bytes in WASM32 with a GC.
However, if you are implementing AssemblyScript into your own engine and, for example, create your own objects externally, it is necessary to account for a managed object's runtime header. In C, this could look like:
typedef struct {
uint32_t classId;
uint32_t payloadSize;
} HALF_HEADER;
typedef struct {
HALF_HEADER header;
size_t reserved1;
size_t reserved2;
} FULL_HEADER;
#define HEADER FULL_HEADER
typedef struct {
HEADER __header;
// ... payload, ref points here ...
} MyClass ;
with the define either substituting HEADER
with HALF_HEADER
if no GC is expected to be present or FULL_HEADER
otherwise. The classId
can be obtained from a dummy object instantiated on the AssemblyScript side for example, while the payloadSize
is the size of the object excluding the runtime header.
Now, when a managed object is created externally, the object must be registered with AssemblyScript before being passed to a module function. From JavaScript, this would look like this:
// as entry
export { gc };
// js code
myModule.gc.register(ref, classId, payloadSize);
myModule.someExport(ref);
TODO: how to call gc.register
from C.