assemblyscript Documentation

A TypeScript-like language for WebAssembly.

17,927 stars WebAssembly 1 file ยท ~6,732 tokens #assemblyscript#compiler#typescript#wasm#webassembly
RAW Doc

1. Project Overview & Quickstart (AssemblyScript/assemblyscript)

File: README.md

AssemblyScript compiles a variant of TypeScript (basically JavaScript with types) to WebAssembly using Binaryen. It generates lean and mean WebAssembly modules while being just an npm install away.

[About](https://www.assemblyscript.org)  ยท  [Getting started](https://www.assemblyscript.org/getting-started.html)  ยท  [Examples](https://www.assemblyscript.org/examples.html)  ยท  [Built with AssemblyScript](https://www.assemblyscript.org/built-with-assemblyscript.html)

Contributors

Thanks to our sponsors!

Most of the maintainers and contributors do this open source work in their free time. If you use AssemblyScript for a serious task or plan to do so, and you'd like us to invest more time on it, please donate to our OpenCollective. By sponsoring this project, your logo will show up below. Thank you so much for your support!

Development instructions

A development environment can be set up by cloning the repository:

sh
git clone https://github.com/AssemblyScript/assemblyscript.git
cd assemblyscript
npm install
npm link

The link step is optional and makes the development instance available globally. The full process is documented as part of the repository:


File: cli/README.md

Frontend for Node.js

Usage

For an up to date list of available command line options, see:

text
$> asc --help

API

The API accepts the same options as the CLI but also lets you override stdout and stderr. Example:

js
import asc from "assemblyscript/asc";

const { error, stdout } = await asc.main([
  "myModule.ts",
  "--outFile", "myModule.wasm",
  "--optimize",
  "--sourceMap",
  "--stats"
]);
if (error) {
  console.log("Compilation failed: " + error.message);
} else {
  console.log(stdout.toString());
}

The result has the following structure:

Property Description
error Encountered error, if any
stdout Standard output stream
stderr Standard error stream
stats Statistics

You can also compile a single source string directly (note that this API has limited functionality):

js
import asc from "assemblyscript/asc";
const { binary, text, stdout, stderr } = await asc.compileString(`...`, { optimize: 2 });
...

Available command line options can also be obtained programmatically:

js
import { options } from "assemblyscript/asc";
...

File: lib/loader/README.md

AssemblyScript Loader

A tiny module loader that makes working with AssemblyScript modules as convenient as it gets without sacrificing efficiency. It about mirrors the relevant parts of the WebAssembly API while also providing utility to allocate and read strings, arrays and classes.

DEPRECATION NOTICE: The loader has been deprecated in AssemblyScript 0.20. It will likely continue to work for a while, but it is recommended to switch to the new static bindings generation.

Example

ts
import loader from "@assemblyscript/loader"; // or require
loader.instantiate(
  // Binary to instantiate
  fetch("optimized.wasm"), // or fs.readFileSync
                           // or fs.promises.readFile
                           // or just a buffer
  // Additional imports
  { ... }
).then(({ exports }) => {
  ...
})

The loader basically instantiates the module using WebAssembly APIs, but also adds additional utility.

Installation

The loader can be installed from npm:

sh
npm install --save @assemblyscript/loader

On the web:

html
<script type="module" src="https://cdn.jsdelivr.net/npm/@assemblyscript/loader/index.js"></script>

<script src="https://cdn.jsdelivr.net/npm/@assemblyscript/loader/umd/index.js"></script>

Usage

One task the loader does not perform is to implicitly translate between WebAssembly pointers and JavaScript objects, and that's where the mixed in utility comes into play. For example, if one has

ts
// AssemblyScript
export function concat(a: string, b: string): string {
  return a + b
}

and then wants to call concat externally, the string arguments cannot just be JavaScript strings but must first be allocated in the module's memory with their lifetime tracked, like so:

js
// JavaScript
const { concat } = myModule.exports
const { __newString, __getString } = myModule.exports

function doConcat(aStr, bStr) {
  let aPtr = __newString(aStr)
  let bPtr = __newString(bStr)
  let cPtr = concat(aPtr, bPtr)
  let cStr = __getString(cPtr)
  return cStr
}

console.log(doConcat("Hello ", "world!"))

Creating arrays

Arrays (or more advanced classes for that matter) require a bit more cooperation because we need to know their value type in order to work with them properly. To achieve this, every class has a unique id internally, and a chunk of runtime type information (RTTI) is shipped with the module to evaluate class types. Here's an example of working with an Int32Array:

ts
// AssemblyScript
export function sum(arr: Int32Array): i32 {
  let sum = 0
  for (let i = 0, k = arr.length; i < k; ++i) {
    sum += unchecked(arr[i])
  }
  return sum
}
export const Int32Array_ID = idof()
js
// JavaScript
const { sum, Int32Array_ID } = myModule.exports
const { __newArray } = myModule.exports

function doSum(values) {
  const arrPtr = __newArray(Int32Array_ID, values)
  return sum(arrPtr)
}

console.log(doSum([1, 2, 3]))

This works with all kinds of arrays, except that ids are different and values are interpreted differently, of course.

Reading arrays

If one is instead interested in the values of an array being returned by the module, there are two approaches to this. Let's say we have the following module:

ts
// AssemblyScript
export function getRandomArray(len: i32): Int32Array {
  const arr = new Int32Array(len)
  // fill with random values
  return arr
}

The first is, obviously, to read the array's values from the module's memory by essentially copying them to a JS array

js
// JavaScript
const { getRandomArray } = myModule.exports
const { __getArray } = myModule.exports

function doGetRandomArray(len) {
  const arrPtr = getRandomArray(len)
  const values = __getArray(arrPtr)
  return values
}

console.log(doGetRandomArray(10))

which is always safe, while the second is to create a live view on the array, enabling two-way modification of its values:

js
// JavaScript
const { getRandomArray } = myModule.exports
const { __getArrayView, __pin, __unpin } = myModule.exports

function doGetRandomArrayView(len) {
  const arrPtr = __pin(getRandomArray(len)) // pin if necessary
  const view = __getArrayView(arrPtr)
  return { ptr, view }
}

const randomArray = doGetRandomArrayView(10)
console.log(randomArray.view)
__unpin(randomArray.ptr) // unpin if necessary

The latter variant can be more efficient (and useful) but is a little dangerous because the view may become detached from the module's memory when memory automatically grows. Also, the viewed array can grow automatically when pushed to, with the view then referencing random memory. Pushing to an array can be avoided quite easily, yet it is notoriously hard to predict when module memory grows - but one can try to set a sufficiently large size of --initialMemory or defensively trigger a sufficiently large dynamic allocation being freed immediately before dealing with potentially problematic views.

Custom classes

As mentioned earlier, the loader understands how to make a nice object structure of a module's exports, and it is possible to utilize it to work with classes in a more natural way. For example, when calling the following function externally

ts
// AssemblyScript
export class Foo {
  constructor(public str: string) {}
  getString(): string {
    return this.str
  }
}

export function getFoo(): Foo { // this one
  return new Foo("Hello world!")
}

one can wrap the received pointer in a myModule.exports.Foo instance:

js
// JavaScript
const { Foo, getFoo } = myModule.exports
const { __getString, __pin, __unpin } = myModule.exports

const fooPtr = __pin(getFoo()) // pin if necessary
const foo = Foo.wrap(fooPtr)
const strPtr = foo.getString()
console.log(__getString(strPtr))
__unpin(fooPtr) // unpin if necessary

API

For reference, here comes the full API provided by the loader.

::: tip
Copying from and extending the examples above is typically sufficient.
:::

Static members

  • ts
    function instantiate<T>(
      moduleOrBuffer: WasmInstantiable,
      imports?: WasmImports
    ): Promise<ASUtil & T>

    Asynchronously instantiates an AssemblyScript module from anything that can be instantiated.

  • ts
    function instantiateSync<T>(
      moduleOrBuffer: WasmInstantiable,
      imports?: WasmImports
    ): ASUtil & T

    Synchronously instantiates an AssemblyScript module from a WebAssembly.Module or binary buffer. Not recommended.

  • ts
    function instantiateStreaming<T>(
      response: Response | PromiseLike<Response>,
      imports?: WasmImports
    ): Promise<ASUtil & T>

    Asynchronously instantiates an AssemblyScript module from a response, i.e. as obtained by fetch.

  • ts
    function demangle<T>(
      exports: WasmExports,
      baseModule?: Object
    ): T

    Demangles an AssemblyScript module's exports to a friendly object structure. You usually don't have to call this manually as instantiation does this implicitly.

Note that T above can either be omitted if the shape of the module is unknown, or can reference a .d.ts (i.e. typeof MyModule) as produced by the compiler with the -d option.

Module instance utility

The following utility functions are mixed into the module's exports.

  • ts
    function __newString(str: string): number

    Allocates a new string in the module's memory and returns a pointer to it. Requires --exportRuntime for access to __new.

  • ts
    function __newArray(
      id: number,
      values: valuesOrCapacity?: number[] | ArrayBufferView | number
    ): number

    Allocates a new array in the module's memory and returns a pointer to it. The id is the unique runtime id of the respective array class. If you are using Int32Array for example, the best way to know the id is an export const Int32Array_ID = idof(). Requires --exportRuntime for access to __new. The values parameter ัan also be used to pre-allocate an otherwise empty array of a certain capacity.

  • ts
    function __getString(ptr: number): string

    Copies a string's value from the module's memory to a JavaScript string. ptr must not be zero.

  • ts
    function __getFunction(ptr: number): ((...args: unknown[]) => unknown) | null

    Gets a callable function object from the module's memory containing its table index. ptr must not be zero.

  • ts
    function __getArrayBuffer(ptr: number): ArrayBuffer

    Copies an ArrayBuffer's value from the module's memory to a JavaScript buffer. ptr must not be zero.

  • ts
    function __getArray(ptr: number): number[]

    Copies an array's values from the module's memory to a JavaScript array. Infers the array type from RTTI. ptr must not be zero.

  • ts
    function __getArrayView(ptr: number): TypedArray

    Gets a live view on the values of an array in the module's memory. Infers the array type from RTTI. ptr must not be zero.

    This differs from __getArray in that the data isn't copied but remains live in both directions. That's faster but also unsafe because if the array grows or becomes garbage collected, the view will no longer represent the correct memory region and modifying its values in this state will most likely corrupt memory or otherwise explode. Use, but use with care.

  • ts
    function __getInt8ArrayView(ptr: number): Int8Array
    function __getUint8ArrayView(ptr: number): Uint8Array
    function __getUint8ClampedArrayView(ptr: number): Uint8ClampedArray
    function __getInt16ArrayView(ptr: number): Int16Array
    function __getUint16ArrayView(ptr: number): Uint16Array
    function __getInt32ArrayView(ptr: number): Int32Array
    function __getUint32ArrayView(ptr: number): Uint32Array
    function __getInt64ArrayView(ptr: number): BigInt64Array
    function __getUint64ArrayView(ptr: number): BigUint64Array
    function __getFloat32ArrayView(ptr: number): Float32Array
    function __getFloat64ArrayView(ptr: number): Float64Array

    Slightly more efficient variants of __getArrayView where the type of the array is know beforehand. Doesn't try to infer the type.

Module instance runtime interface

When compiling with --exportRuntime, the loader will expose the runtime interface (__new, __pin, __unpin, __collect) as well.

Convenience vs. efficiency

Making the loader's API any more convenient has its tradeoffs. One would either have to include extended type information with the module itself or generate an additional JavaScript file of glue code that does (and hides) all the lifting. As such, one can consider the loader as a small and efficient building block that can do it all, yet does not sacrifice efficiency. If that's not exactly what you are looking for, take a look at more convenient tools below. Just remember that these have tradeoffs.

More convenient tools

  • as-bind is a library, built on top of the loader, to make passing high-level data structures between AssemblyScript and JavaScript more convenient.

Advanced usage

Direct memory access

All of the above can be mixed with direct memory accesses on myModule.exports.memory.buffer, for instance by adhering to class layout.

TypeScript definitions

The compiler is able to emit definitions using the -d command line option that are compatible with modules demangled by the loader, and these can be used for proper typings in development:

ts
// TypeScript
import type * as MyModule from "myModule"; // pointing at the generated d.ts

loader.instantiate<typeof MyModule>(
  fetch("myModule.wasm"),
  { ... }
).then(({ exports }) => {
  ...
})

File: lib/rtrace/README.md

AssemblyScript Rtrace

A tiny utility to sanitize the AssemblyScript runtime. Records allocations and frees performed by the runtime and emits an error if something is off. Also checks for leaks.

Instructions

Compile your module that uses the full or half runtime with -use ASC_RTRACE=1 --exportStart _initialize and include an instance of this module as the import named rtrace.

js
const rtrace = new Rtrace({
  onerror(err, info) {
    // handle error
  },
  oninfo(msg) {
    // print message, optional
  },
  getMemory() {
    // obtain the module's memory,
    // e.g. using --exportStart:
    return instance.exports.memory;
  }
});

const { module, instance } = await WebAssembly.instantiate(...,
  rtrace.install({
    ...imports...
  })
);
instance.exports._initialize();
...

if (rtrace.active) {
  let leakCount = rtr.check();
  if (leakCount) {
    // handle error
  }
}

Note that references in globals which are not cleared before collection is performed appear as leaks, including their inner members. A TypedArray would leak itself and its backing ArrayBuffer in this case for example. This is perfectly normal and clearing all globals avoids this.


File: lib/README.md

Library

Additional packages provided by the main package.

Package Description
@assemblyscript/loader Module loader utility
@assemblyscript/rtrace Runtime tracing utility
binaryen Binaryen proxy

The Binaryen proxy herein is imported accross the code base and forwards the
binaryen npm package by default. It can be modified to use a custom build,
for example for testing purposes.


File: scripts/README.md

Scripts

Various scripts for development purposes.

Default target

Command Description
npm install Install required dependencies
npm run build Builds the compiler
npm run watch Builds the compiler and watches for changes
npm run check Performs sanity checks on code style etc.
npm test Runs the test suite

WebAssembly target

Command Description
npm run asbuild Compiles the compiler to WebAssembly
npm run bootstrap Bootstraps the compiler to WebAssembly

File: src/README.md

Compiler

Portable compiler sources that compile to both JavaScript using tsc and WebAssembly using asc.

Architecture

Usage

js
import assemblyscript from "assemblyscript";
...

Building

Building to JavaScript

To build the compiler, run:

sh
npm run build

The rebuild automatically when there are changes, do:

sh
npm run watch

Building to WebAssembly

To build the compiler to a WebAssembly binary, run:

sh
npm run bootstrap

Uses the AssemblyScript compiler compiled to JavaScript to compile itself to WebAssembly, building to WebAssembly again using itself compiled to WebAssembly. Builds to build/. Performs a git diff to make sure that both the initial and the final artifacts are the same. Note that this builds the compiler as a library, while the asc frontend setting it up and feeding it source files is JavaScript for now.

Running asc with the WebAssembly variant:

ts
asc [options...] --wasm build/assemblyscript.release-bootstrap.js

Running the compiler tests with the WebAssembly variant:

ts
npm run test:compiler -- --wasm build/assemblyscript.release-bootstrap.js

File: std/assembly/rt/README.md

The AssemblyScript Runtime

The runtime provides the functionality necessary to dynamically allocate and deallocate memory of objects, arrays and buffers, as well as collect garbage that is no longer used. The current implementation is either a Two-Color Mark & Sweep (TCMS) garbage collector that must be called manually when the execution stack is unwound or an Incremental Tri-Color Mark & Sweep (ITCMS) garbage collector that is fully automated with a shadow stack, implemented on top of a Two-Level Segregate Fit (TLSF) memory manager. It's not designed to be the fastest of its kind, but intentionally focuses on simplicity and ease of integration until we can replace it with the real deal, i.e. Wasm GC.

Interface

Garbage collector / `--exportRuntime`

  • __new(size: usize, id: u32 = 0): usize
    Dynamically allocates a GC object of at least the specified size and returns its address.
    Alignment is guaranteed to be 16 bytes to fit up to v128 values naturally.
    GC-allocated objects cannot be used with __realloc and __free.

  • __pin(ptr: usize): usize
    Pins the object pointed to by ptr externally so it and its directly reachable members and indirectly reachable objects do not become garbage collected.

  • __unpin(ptr: usize): void
    Unpins the object pointed to by ptr externally so it can become garbage collected.

  • __collect(): void
    Performs a full garbage collection.

Internals

  • __alloc(size: usize): usize
    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.

  • __realloc(ptr: usize, size: usize): usize
    Dynamically changes the size of a chunk of memory, possibly moving it to a new address.

  • __free(ptr: usize): void
    Frees a dynamically allocated chunk of memory by its address.

  • __renew(ptr: usize, size: usize): usize
    Like __realloc, but for __newed GC objects.

  • __link(parentPtr: usize, childPtr: usize, expectMultiple: bool): void
    Introduces a link from a parent object to a child object, i.e. upon parent.field = child.

  • __visit(ptr: usize, cookie: u32): void
    Concrete visitor implementation called during traversal. Cookie can be used to indicate one of multiple operations.

  • __visit_globals(cookie: u32): void
    Calls __visit on each global that is of a managed type.

  • __visit_members(ptr: usize, cookie: u32): void
    Calls __visit on each member of the object pointed to by ptr.

  • __typeinfo(id: u32): RTTIFlags
    Obtains the runtime type information for objects with the specified runtime id. Runtime type information is a set of flags indicating whether a type is managed, an array or similar, and what the relevant alignments when creating an instance externally are etc.

ITCMS / `--runtime incremental`

The Incremental Tri-Color Mark & Sweep garbage collector maintains a separate shadow stack of managed values in the background to achieve full automation. Maintaining another stack introduces some overhead compared to the simpler Two-Color Mark & Sweep garbage collector, but makes it independent of whether the execution stack is unwound or not when it is invoked, so the garbage collector can run interleaved with the program.

There are several constants one can experiment with to tweak ITCMS's automation:

  • --use ASC_GC_GRANULARITY=1024
    How often to interrupt. The default of 1024 means "interrupt each 1024 bytes allocated".
  • --use ASC_GC_STEPFACTOR=200
    How long to interrupt. The default of 200% means "run at double the speed of allocations".
  • --use ASC_GC_IDLEFACTOR=200
    How long to idle. The default of 200% means "wait for memory to double before kicking in again".
  • --use ASC_GC_MARKCOST=1
    How costly it is to mark one object. Budget per interrupt is GRANULARITY * STEPFACTOR / 100.
  • --use ASC_GC_SWEEPCOST=10
    How costly it is to sweep one object. Budget per interrupt is GRANULARITY * STEPFACTOR / 100.

TCMS / `--runtime minimal`

If automation and low pause times aren't strictly necessary, using the Two-Color Mark & Sweep garbage collector instead by invoking collection manually at appropriate times when the execution stack is unwound may be more performant as it simpler and has less overhead. The execution stack is typically unwound when invoking the collector externally, at a place that is not indirectly called from Wasm.

STUB / `--runtime stub`

The stub is a maximally minimal runtime substitute, consisting of a simple and fast bump allocator with no means of freeing up memory again, except when freeing the respective most recently allocated object on top of the bump. Useful where memory is not a concern, and/or where it is sufficient to destroy the whole module including any potential garbage after execution.

See also: Garbage collection

2. Official Technical Reference & Guides (AssemblyScript/website)

AssemblyScript's Website

The site is built with VuePress and reuses its default theme to ease upgrading to newer versions.

The more important files are:

Editor component

Markdown files can make use of a custom editor component using the editor language tag, like so:

text
```editor
#!optimize=size&runtime=none&noAssert&explicitStart&enable=simd,reference-types
export function add(a: i32, b: i32): i32 {
  return a + b
}

#!html
<script type="module">
const exports = await instantiate(await compile(), { /* imports */ })
console.log(exports.add(1, 2))
</script>
```

The first line is an optional shebang indicating selected compiler options. Available options are:

  • optimize = string
    Optimization preset to use. Valid presets are speed, size and none
  • noAssert
    Replaces assertions with just their value without trapping.
  • debug
    Enables debug information in emitted binaries.
  • runtime = string
    Specifies the runtime variant to include in the program. Valid variants are full, half, stub and none.
  • noExportMemory
    Does not export the memory to the host.
  • importMemory
    Imports the memory from the host.
  • exportTable
    Exports the function table to the host.
  • importTable
    Imports the function table from the host.
  • explicitStart
    Exports an explicit start function to call.
  • memoryBase = integer
    Sets the start offset of emitted memory segments.
  • tableBase = integer
    Sets the start offset of emitted table elements.
  • use = string
    Comma separated list of global aliases, e.g. to switch the default 'Math' implementation used: Math=JSMath
  • enable = string
    Comma-separated list of future WebAssembly features to enable. Valid features are sign-extension, bulk-memory, simd, threads and reference-types.

The current source and associated compiler options can be serialized into a base64 blob this way. For example, when the ๐Ÿ”— button is clicked, document.location.hash is updated with that blob and the then-sharable link is copied to clipboard.

Building

To work on the site locally, install the dependencies and start a development server serving at localhost:8080:

sh
npm install
npm start

To build the site to dist, i.e. to verify that it works as expected:

sh
npm run build

Distribution files can also be served instead of using the development server with:

sh
npm run serve

The page is automatically deployed on pushes to the repository, plus at least once a day to sync contributors and sponsors.