LICENSE Documentation

Official TypeScript developer resources and documentation for wasmerio/LICENSE. Verified technical context for AI.

0 stars TypeScript TypeScript 1 file · ~64,239 tokens
RAW Doc

1. Project Overview & Quickstart (wasmerio/LICENSE)

LICENSE

Open-source repository wasmerio/LICENSE

Repository Details

Note: High-volume repository documentation is actively indexed and synchronized by YakaAI.

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

File: README.md

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>
loader.instantiate(module_wasm, { /* imports */ })
  .then(({ exports }) => {
    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.


File: package.json

{
"name": "@assemblyscript/website",
"private": true,
"version": "0.0.0",
"license": "Apache-2.0",
"description": "AssebmlyScript's Website.",
"scripts": {
"start": "vuepress dev src --host localhost --no-cache",
"build": "vuepress build src",
"serve": "vuepress serve src",
"preinstall": "npx npm-force-resolutions"
},
"devDependencies": {
"@vuepress/plugin-html-redirect": "^0.1.2",
"he": "^1.2.0",
"node-fetch": "^2.6.1",
"pngquant": "^3.1.0",
"prism-themes": "^1.5.0",
"sharp": "^0.27.0",
"vuepress": "^1.8.0",
"vuepress-plugin-serve": "^2.0.4",
"vuepress-plugin-sitemap": "^2.3.1"
},
"resolutions": {
"watchpack": "1.6.1"
}
}


File: data/testimonials.json

[
{
"name": "Surma",
"text": "You are now able to write WebAssembly without learning a new language, and harness all these benefits WebAssembly might offer you. I think that is kind of powerful. AssemblyScript is absolutely usable, and very enjoyable!",
"title": "WebAssembly for Web Developers (Google I/O ’19)",
"link": "https://youtu.be/njt-Qzw0mVY",
"date": "May 8th, 2019"
},
{
"name": "Alon Zakai",
"text": "AssemblyScript is designed with WebAssembly and code size in mind. It's not an existing language that we are using for a new purpose but it's a language designed for WebAssembly. It has great wasm-opt integration, in fact it's built with it, and it's very easy to get good code size.",
"title": "Shipping Tiny WebAssembly Builds (WebAssembly Summit)",
"link": "https://www.youtube.com/watch?v=_lLqZR4ufSI",
"date": "Feb 10, 2020"
},
{
"name": "Peter Salomonsen",
"text": "I chose AssemblyScript because it has high-level readability and low-level control. It's like a high-level language but you get that low-level feeling and you can even directly write WebAssembly intrinsics if you want to.",
"title": "WebAssembly Music (WebAssembly Summit)",
"link": "https://www.youtube.com/watch?v=C8j_ieOm4vE",
"date": "Feb 10, 2020"
}
]


File: data/tiers.json

{
"platinum": {
"name": "Platinum Sponsors",
"minAmount": 50000,
"size": 80
},
"gold": {
"name": "Gold Sponsors",
"minAmount": 10000,
"size": 64
},
"silver": {
"name": "Silver Sponsors",
"minAmount": 2000,
"size": 48
},
"bronze": {
"name": "Bronze Sponsors",
"minAmount": 200,
"size": 36
},
"backer": {
"name": "Individual Backers",
"minAmount": 1,
"size": 32
}
}


File: src/basics.md



Basics

WebAssembly is fundamentally different from JavaScript, ultimately enabling entirely new use cases not only on the web. Consequently, AssemblyScript is much more similar to a static compiler than it is to a JavaScript VM. One can think of it as a mix of TypeScript's high level syntax and C's low-level capabilities. This page is dedicated to getting you up to speed in no time.

Strictness

Unlike TypeScript, which targets a JavaScript environment with all of its dynamic features, AssemblyScript targets WebAssembly with all of its static guarantees, hence intentionally avoids the dynamicness of JavaScript where it cannot be compiled ahead of time efficiently.

Limiting ourselves to where WebAssembly excels, for example by assigning or inferring definite types, eliminates the need to ship a JIT doing the heavy lifting at runtime, yielding predictable performance right from the start of execution while guaranteeing that the resulting WebAssembly modules are small.

Static typing

The first peculiarity one is going to notice when writing AssemblyScript is that its basic types are a bit different from TypeScript's in that it uses WebAssembly's more specific integer and floating point types, with JavaScript's number merely an alias of WebAssembly's f64.

A JavaScript JIT would try to figure out the best representation of a numeric value while the code is executing, making assumptions as values flow around, potentially recompiling code multiple times, while AssemblyScript lets the developer specify the ideal type in advance with all its pros and cons. To give a few examples:

No any or undefined

ts
// 😢
function foo(a?) {
  var b = a + 1
  return b
}

// 😊
function foo(a: i32 = 0): i32 {
  var b = a + 1
  return b
}

No union types

ts
// 😢
function foo(a: i32 | string): void {
}

// 😊
function foo<T>(a: T): void {
}

Strictly typed objects

ts
// 😢
var a = {}
a.prop = "hello world"

// 😊
var a = new Map<string,string>()
a.set("prop", "hello world")

// 😊
class A {
  constructor(public prop: string) {}
}
var a = new A("hello world")

Sandbox

WebAssembly modules execute in a sandbox, providing strong security guarantees for all sorts of use cases not necessarily limited to the browser. As such, a module has no immediate access to the DOM or other external APIs out of the box. Also, WebAssembly does not yet have a concept of objects, making it necessary to translate and exchange objects via pointers to linear memory either through the module's exports and imports or by reading from respectively writing to the module's linear memory. The loader is able to help there.

Quirks

Some patterns behave a little different in AssemblyScript compared to TypeScript. This section covers the more or less obvious ones and shows how to deal with them, so you can keep them in mind and build something great today.

Triple equals

The special semantics of the === operator (true when both the value and type match) don't make a lot of sense in AssemblyScript because comparing two values of incompatible types is illegal anyway. Hence the === operator has been repurposed to perform an identity comparison, evaluating to true if both operands are the exact same object:

ts
var a = "hello"
var b = a
var c = "h" + a.substring(1)

if (a === b) { /* true */ }
if (a === c) { /* false */ }
if (a == c) { /* true */ }

This semantical change turned out to be quite useful during implementation of some parts of the standard library where knowing if something is actually exactly the same makes a lot of sense, but it can be confusing for those coming from a TypeScript background where using === for everything unless required otherwise is common good practice. However, the alternative here is to make === a redundanat alias of == by sacrificing an otherwise useful feature, and it's still unclear if that'd be objectively better, so has been postponed multiple times.

Nullability checks

Like TypeScript, the AssemblyScript compiler can propagate whether a value is nullable or not after a check:

ts
function doSomething(something: string | null): void {
  if (something) {
    something.length // works
  }
}

This doesn't work on fields, however, because their value can change in between the check and the use of the value:

ts
function doSomething(foo: Foo): void {
  if (foo.something) {
    // ... some code ...
    foo.something.length // fails
  }
}

For example, foo.something might be a property that sets the underlying field to null when accessed, or code in between may set the field to null. In fact, TypeScript fails in both scenarios, leading to a runtime error. However, there can't be runtime errors like this in AssemblyScript, so one must instead use a local to be safe:

ts
function doSomething(foo: Foo): void {
  let something = foo.something
  if (something) {
    something.length // works
  }
}

Non-linear compilation

AssemblyScript does not compile a module linearly, but starts at the module's exports and only compiles what's reachable from them, often referred to as tree-shaking. As such, dead code is always validated syntactically, but not necessarily checked for semantic correctness. While this mechanism significantly helps to reduce compile times and feels almost natural to those familiar with executing JavaScript, it may initially feel a little strange not only to those with a background in traditional compilers, for example because emitted diagnostics do not happen linearly, but also to those with a background in TypeScript, because even type annotations remain unchecked as part of dead code. The exception to the rule is top-level code, including top-level variable declarations and their initializers, that must be evaluated as soon as the respective file would first execute.

So far, so good. Shall we continue with a status of WebAssembly and language features?


File: src/built-with-assemblyscript.md



Built with AssemblyScript

A place for all things AssemblyScript. Feel free to add your projects and applications.

Benchmarks

  • Wasm Mandelbrot (demo)
    A mandelbrot rendered using a variety of WebAssembly tools (emscripten, AssemblyScript, asm.js, etc ...)
  • WasmBoy Benchmark
    Benchmarking WasmBoy emulator.
  • webassembly-wave (demo)
    JavaScript vs WebAssembly performance comparison - wave equation demo (JS, C++, AssemblyScript).
  • Sort Colors Benchmark (demo)
    Bench of sorting random colors by hue component on 2d canvas.
  • wasm-n-body
    WebAssembly N-body simulation benchmark (use 1000 bodies compare to original).
  • as-benchmarks (demo)
    A set of computational benchmarks (JS, AssemblyScript).

Blockchain

Build Tools

Editors

  • WebAssembly Studio
    Learn, Teach, Work and Play in the WebAssembly Studio.
  • Fastly Terrarium
    A compilation toolchain and sandbox to run WebAssembly server-side, with native support for AssemblyScript.
  • NEAR Protocol Examples
    Examples that use GitPod to develop and deploy distributed web apps using NEAR smart contracts authored in AssemblyScript.
  • Iodide Notebook
    Experimental tool for scientific communication and exploration on the web (more).

Emulators

  • wasmBoy (demo)
    Game Boy / Game Boy Color Emulator Library, written for WebAssembly using AssemblyScript.
  • Atari 2600
    An Atari 2600 emulator written in AssemblyScript compiled to WebAssembly.
  • as-spectrum-engine
    AssemblyScript implementation of a ZX Spectrum emulator engine.

Games

  • Chess (play)
    A free and open source chess game using AssemblyScript and React.
  • Gomoku WASM (play)
    A Gomoku game implements with WebAssembly using as2d.

Graphics

  • as2d
    Bring the power of the CanvasRenderingContext2D prototype to AssemblyScript.
  • as-smallpt
    Port of C++ smallpt, a path tracing renderer.
  • Babylon.Font
    Create 3d text in BabylonJS.
  • seam-carving-as
    Seam Carving experiments with AssemblyScript.

Libraries

  • AS-LZMA
    LZMA Decoder written in AssemblyScript.
  • assemblyscript-bson
    BSON encoder / decoder.
  • assemblyscript-json
    JSON encoder / decoder.
  • as-bignum (formerly bignum.wasm)
    Fixed length big numbers like u128, i256, fp128 and etc for AssemblyScript.
  • ASTL
    Migration project from C++ STL (Standard Template Library) to AssemblyScript. Containers, iterators, algorithms and functors are supported.
  • Levenshtein Wasm
    An experimental lightning-fast Wasm-compiled levenshtein library.
  • superfasthash
    An implementation of the SuperFastHash non-cryptographic hashing algorithm in JavaScript and WebAssembly.
  • as-wasi
    An AssemblyScript API layer for WASI system calls.
  • WASM-Crypto
    An AssemblyScript set of cryptographic primitives for building authentication and key exchange protocols.
  • rabin-wasm
    Rabin fingerprinting implemented in WASM (used by js-ipfs).
  • galois
    Arithmetic and polynomial operations in finite fields.
  • merkle
    Merkle tree and other data structures.
  • xoroshiro128starstar
    An AssemblyScript port of xoroshiro128starstar.c
  • pixelmatch-asc
    Pixel level image comparison library.
  • as-bind
    Isomorphic library to handle passing high-level data structures between AssemblyScript and JavaScript, built on top of the AssemblyScript loader.
  • wasmer-as
    Helpers for dealing with assemblyscript memory inside wasmer-runtime.
  • wasmbind
    Work with AssemblyScript memory and classes from Python.
  • io-devices-lib
    Library for interacting with the Wasmer Experimental IO Devices.

Embedded / IoT

  • wasm3-arduino
    Usage example with wasm3 which run on Arduino, PlatformIO, Particle.
  • Wasm3_RGB_Lamp
    Animating an RGB lamp, using WebAssembly (AssemblyScript).
  • wasm-arduino-wifi
    Example how update WebAssembly via WiFi on arduino.

Testing

  • as-pect
    A test framework for AssemblyScript inspired by jest.

Project Templates

Other

  • wasm-matrix
    A Matrix effect in your terminal using AssemblyScript and WASI, deployed to WAPM.
  • WebAssembly music experiment
    A live coding environment in the browser for sequencing music with javascript and synthesizing instruments in AssemblyScript. Demonstrates compiling AssemblyScript directly in the browser, and live hot-swapping of webassembly modules.
  • WireWorld (demo)
    The Wireworld Computer in Javascript/Canvas.

File: src/compiler.md



Compiler

Similar to TypeScript's tsc compiling to JavaScript, AssemblyScript's asc compiles to WebAssembly.

Command line options

Entry file\(s\)

Non-option arguments are treated as the names of entry files. A single program can have multiple entries, with the exports of each entry becoming the exports of the WebAssembly module. Exports of imported files that are not entry files do not become WebAssembly module exports.

sh
asc entryFile.ts

General

text
--version, -v         Prints just the compiler's version and exits.
--help, -h            Prints this message and exits.
--noColors            Disables terminal colors.
--config              Configuration file to apply. CLI arguments take precedence.
--target              Target configuration to use. Defaults to 'release'.

asconfig.json

Instead of providing the options outlined below on the command line, a configuration file typically named asconfig.json can be used. It may look like in the following example, excluding comments:

json
{
  "extends": "./path/to/other/asconfig.json", // (optional) base config
  "entries": [
    // (optional) entry files, e.g.
    "./assembly/index.ts"
  ],
  "options": {
    // common options, e.g.
    "importTable": true
  },
  "targets": {
    // (optional) targets
    "release": {
      // additional options for the "release" target, e.g.
      "optimize": true,
      "binaryFile": "myModule.release.wasm"
    },
    "debug": {
      // additional options for the "debug" target, e.g.
      "debug": true,
      "binaryFile": "myModule.debug.wasm"
    }
  }
}

Provided command line options override options in the configuration file.

Optimization

The compiler can optimize for both speed and size. --optimizeLevel (0-3) indicates how much the compiler focuses on optimizing the code with --shrinkLevel (0-2, 1=s, 2=z) indicating how much it focuses on keeping the size low during code generation and while optimizing. A convenient shorthand is -O[optimizeLevel][shrinkLevel] , with shrink level indicated by appending the letter s (1) or z (2) to the optimize level.

text
--optimize, -O        Optimizes the module. Typical shorthands are:

                       Default optimizations   -O / -O3s
                       Make a release build    -O --noAssert
                       Make a debug build      --debug
                       Optimize for speed      -O3
                       Optimize for size       -O3z --converge

--optimizeLevel       How much to focus on optimizing code. [0-3]
--shrinkLevel         How much to focus on shrinking code size. [0-2, s=1, z=2]
--converge            Re-optimizes until no further improvements can be made.
--noAssert            Replaces assertions with just their value without trapping.

Output

Typical output formats are WebAssembly binary (.wasm) and/or text format (.wat). Often, both are used in tandem to run and also inspect generated code.

text
--outFile, -o         Specifies the output file. File extension indicates format.
--binaryFile, -b      Specifies the binary output file (.wasm).
--textFile, -t        Specifies the text output file (.wat).

There are several other output formats as well for tooling purposes with varying levels of maturity.

text
--jsFile, -j          Specifies the JavaScript output file (.js).
--idlFile, -i         Specifies the WebIDL output file (.webidl).
--tsdFile, -d         Specifies the TypeScript definition output file (.d.ts).

Debugging

For easier debugging a source map can be emitted alongside the WebAssembly binary.

text
--sourceMap           Enables source map generation. Optionally takes the URL
                      used to reference the source map from the binary file.

It is also often useful to emit debug information, like function names, alongside the binary.

text
--debug               Enables debug information in emitted binaries.

Features

There are several flags that enable or disable specific WebAssembly or compiler features. By default, only the bare minimum is exposed, and fully standardized WebAssembly features will be used.

text
--importMemory        Imports the memory from 'env.memory'.
--noExportMemory      Does not export the memory as 'memory'.
--initialMemory       Sets the initial memory size in pages.
--maximumMemory       Sets the maximum memory size in pages.
--sharedMemory        Declare memory as shared. Requires maximumMemory.
--importTable         Imports the function table from 'env.table'.
--exportTable         Exports the function table as 'table'.
--runtime             Specifies the runtime variant to include in the program.

                       incremental  TLSF + incremental GC (default)
                       minimal      TLSF + lightweight GC invoked externally
                       stub         Minimal runtime stub (never frees)
                       ...          Path to a custom runtime implementation

--exportRuntime       Exports the runtime helpers (__new, __collect etc.).
--explicitStart       Exports an explicit '_start' function to call.
--enable              Enables WebAssembly features being disabled by default.

                        sign-extension      Sign-extension operations
                        bulk-memory         Bulk memory operations.
                        simd                SIMD types and operations.
                        threads             Threading and atomic operations.
                        reference-types     Reference types and operations.
                        gc                  Garbage collection (anyref, WIP).

--disable             Disables WebAssembly features being enabled by default.

                        mutable-globals     Mutable global imports and exports.

--use, -u             Aliases a global object under another name, e.g., to switch
                      the default 'Math' implementation used: --use Math=JSMath
                      Can also be used to introduce an integer constant.
--lowMemoryLimit      Enforces very low (<64k) memory constraints.

Linking

Specifying the base offsets of compiler-generated memory respectively the table leaves some space for other data in front. In its current form this is mostly useful to link additional data into an AssemblyScript binary after compilation, be it by populating the binary itself or initializing the data externally upon initialization. One good example is leaving some scratch space for a frame buffer.

text
--memoryBase          Sets the start offset of emitted memory segments.
--tableBase           Sets the start offset of emitted table elements.

API

To integrate with the compiler, for example to post-process the AST, one or multiple custom transforms can be specified.

text
--transform           Specifies the path to a custom transform to 'require'.

Other

Other options include those forwarded to Binaryen and various flags useful in certain situations.

Binaryen

text
--trapMode            Sets the trap mode to use.

                       allow  Allow trapping operations. This is the default.
                       clamp  Replace trapping operations with clamping semantics.
                       js     Replace trapping operations with JS semantics.

--runPasses           Specifies additional Binaryen passes to run after other
                      optimizations, if any. See: Binaryen/src/passes/pass.cpp
--noValidate          Skips validating the module using Binaryen.

And the kitchen sink

text
--noColors            Disables terminal colors.
--baseDir             Specifies the base directory of input and output files.
--extension           Specifies an alternative file extension to use.
--noUnsafe            Disallows the use of unsafe features in user code.
                      Does not affect library files and external modules.
--noEmit              Performs compilation as usual but does not emit code.
--measure             Prints measuring information on I/O and compile times.
--pedantic            Make yourself sad for no good reason.
--lib                 Adds one or multiple paths to custom library components and
                      uses exports of all top-level files at this path as globals.
--path                Adds one or multiple paths to package resolution, similar
                      to node_modules. Prefers an 'ascMain' entry in a package's
                      package.json and falls back to an inner 'assembly/' folder.
--traceResolution     Enables tracing of package resolution.
--listFiles           Lists files to be compiled and exits.
-- ...                Specifies node.js options (CLI only). See: node --help

API

The compiler can also be used programmatically.


File: src/debugging.md



Debugging

Recommendations on how to debug AssemblyScript modules.

Source maps

The compiler supports generating a source map alongside a binary using the --sourceMap option.

Relative source maps

By default, specifying the --sourceMap option will create a source mapping section pointing to the source map with a relative path, defaulting to myModule.wasm.map with myModule being the name of the respective binary. This works when instantiating a module with WebAssembly.instantiateStreaming because the VM can obtain the absolute URL the source map is relative to from the provided Response object, but does not work if a module is instantiated from a buffer or otherwise without a path context.

Absolute source maps

Where relative source maps cannot be used, for example if WebAssembly.instantiate is used to instantiate a module from a binary buffer, it is also possible to specify an absolute path to the source map using --sourceMap absolutePathToSourceMap.

Stack traces

A JavaScript VM is able to display stack traces originating in WebAssembly code. However, optimizing a WebAssembly module will usually drop all the debug information, making these stack traces hard to grasp. Using an unoptimized module or a module otherwise preserving debug information can help here. The AssemblyScript compiler preserves debug information by specifying the --debug compiler option.

Disabling assertions

By default, the compiler will preserve any assert(expression)s in a module, leading to an abort if one of the respective expectations failed. These assertions can be disabled with the --noAssert compiler option, though, essentially replacing them with nops, doing nothing. Doing so can lead to smaller binaries once sufficiently confident that no assertions will be hit anyway, but also introduces the risk that a module explodes for no longer asserted reasons.

Overriding `abort`

As mentioned above, assertions require that an implementation of the abort interface is present, which by default is imported as abort from within the env module, handling aborts externally. This can be overridden by specifying a custom abort handler through --use abort=assembly/index/myAbort (here: a function named myAbort in assembly/index.ts) or the abort interface can be disabled completely (just trapping with an unreachable) through --use abort=. The signature of the abort function, if overridden, is:

ts
function abort(
  message: string | null,
  fileName: string | null,
  lineNumber: u32,
  columnNumber: u32
): void

Manual tracing

The standard library provides a relatively basic trace utility function that is imported from the host and understood by the loader. For example

ts
trace("HERE", 2, value, otherValue)

will, by default, call the trace function imported from the env module with a string message and two arbitrary values that can be anything convertible to an f64. The loader for example will log the output to console. Similar to overriding abort, the implementation can be overridden using the --use compiler option. Signature of the trace function is:

ts
function trace(
  message: string,
  n: i32 = 0, // number of given parameters a0 to a4
  a0?: f64,
  a1?: f64,
  a2?: f64,
  a3?: f64,
  a4?: f64
): void

One thing to note here is that calling trace in top-level statements can lead to situations where memory is accessed during instantiation, hence not being able to read the message without taking the respective precautions.

Breakpoints

Some JavaScript engines also support adding break points when running WebAssembly binaries. Please consult your engine's documentation.

Additional resources


File: src/development.md



Development

Instructions for setting up a development environment.

::: tip
Please see our contribution guidelines before submitting your first pull request.
:::

Setting up a development environment

The following sets up a development environment of the compiler where editing the sources applies changes immediately without having to go through a build step:

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

Note that a development environment runs the sources directly by default, but will use distributions files once built. Cleaning the distribution files again with npm run clean resets this. Whether asc is running the sources or distribution files can be determined by checking the displayed version number. If it states -dev, it runs the sources directly.

Building distribution files

To build an UMD bundle to dist/assemblyscript.js (depends on binaryen.js), including a browser version of asc to dist/asc.js (depends on assemblyscript.js):

sh
npm run build

Cleaning the distribution files (again):

sh
npm run clean

Linting potential changes:

sh
npm run check

Running the tests:

sh
npm test

Running everything in order (check, clean, test, build, test):

sh
npm run all

Creating tests

The test suite is composed of a set of tests for each component, like the parser, the compiler and runtime. For each pull request, all the tests are run automatically and the PR only goes green if it passes all of them. Please see the instructions within the repository for all the details.


File: src/editor-test.md

Editor Test

Text before

editor
#!runtime=stub
// Editor A

export function add(a: i32, b: i32): i32 {
  return a + b
}

export function sub(a: i32, b: i32): i32 {
  return a - b
}

#!html
<script>
loader.instantiate(module_wasm, { /* imports */ })
  .then(({ exports }) => {
    const output = document.getElementById('output')
    output.value += `add(1, 2) = ${exports.add(1, 2)}\n`
    output.value += `sub(1, 2) = ${exports.sub(1, 2)}\n`
  })
</script>

<textarea id="output" style="height: 100%; width: 100%" readonly></textarea>

Text between

editor
#!runtime=stub
// Editor B

export function sub(a: i32, b: i32): i32 {
  return a - b
}

export function add(a: i32, b: i32): i32 {
  return a + b
}

#!html
<script>
loader.instantiate(module_wasm, { /* imports */ })
  .then(({ exports }) => {
    const output = document.getElementById('output')
    output.value += `sub(2, 3) = ${exports.sub(2, 3)}\n`
    output.value += `add(2, 3) = ${exports.add(2, 3)}\n`
  })
</script>

<textarea id="output" style="height: 100%; width: 100%" readonly></textarea>

Text after


File: src/environment.md



Environment

A WebAssembly environment is more limited than a usual browser environment, but AssemblyScript tries to fill the gaps by reimplementing commonly known functionality, besides providing direct access to WebAssembly instructions through built-ins.

Standard library

AssemblyScript comes with its own standard library very much resembling what developers became used to when writing JavaScript, with a few specialized classes, like StaticArray, that exist in AssemblyScript only to tackle very specific problems.

Additional rather low-level WebAssembly functionality that the standard library makes extensive use of is described below.

Static type checks

By making use of the following special type checks, especially in generic contexts, untaken branches can be eliminated statically, leading to concrete WebAssembly functions that handle one type specificially.

  • ts
    function isInteger<T>(value?: T): bool

    Tests if the specified type or expression is of an integer type and not a reference. Compiles to a constant.

  • ts
    function isFloat<T>(value?: T): bool

    Tests if the specified type or expression is of a float type. Compiles to a constant.

  • ts
    function isSigned<T>(value?: T): bool

    Tests if the specified type or expression can represent negative numbers. Compiles to a constant.

  • ts
    function isReference<T>(value?: T): bool

    Tests if the specified type or expression is of a reference type. Compiles to a constant.

  • ts
    function isString<T>(value?: T): bool

    Tests if the specified type or expression can be used as a string. Compiles to a constant.

  • ts
    function isArray<T>(value?: T): bool

    Tests if the specified type or expression can be used as an array. Compiles to a constant.

  • ts
    function isFunction<T>(value?: T): bool

    Tests if the specified type or expression is of a function type. Compiles to a constant.

  • ts
    function isNullable<T>(value?: T): bool

    Tests if the specified type or expression is of a nullable reference type. Compiles to a constant.

  • ts
    function isDefined(expression: auto): bool

    Tests if the specified expression resolves to a defined element. Compiles to a constant.

  • ts
    function isConstant(expression: auto): bool

    Tests if the specified expression evaluates to a constant value. Compiles to a constant.

  • ts
    function isManaged<T>(expression: auto): bool

    Tests if the specified type or expression is of a managed type. Compiles to a constant. Usually only relevant when implementing custom collection-like classes.

Example

ts
function add<T>(a: T, b: T): T {
  return a + b // addition if numeric, string concatenation if a string
}

function add<T>(a: T, b: T): T {
  if (isString<T>()) { // eliminated if T is not a string
    return parseInt(a) + parseInt(b)
  } else { // eliminated if T is a string
    return a + b
  }
}

::: tip
If you are not going to use low-level WebAssembly in the foreseeable future, feel free to come back to the following paragraphs at a later time and continue at the next page right away.
:::

Sizes and alignments

  • ts
    function sizeof<T>(): usize

    Determines the byte size of the respective basic type. Means: If T is a class type, the size of usize is returned. To obtain the size of a class in memory, use offsetof<T>() instead. Compiles to a constant.

  • ts
    function offsetof<T>(fieldName?: string): usize

    Determines the offset of the specified field within the given class type. Returns the class type's end offset (means: where the next field would be located, before alignment) if field name has been omitted. Compiles to a constant. The fieldName argument must be a compile-time constant string because there is no information about field names anymore in the final binary. Hence, the field's name must be known at the time the returned constant is computed.

  • ts
    function alignof<T>(): usize

    Determines the alignment (log2) of the specified underlying basic type. Means: If T is a class type, the alignment of usize is returned. Compiles to a constant.

Utility

  • ts
    function assert<T>(isTrueish: T, message?: string): T

    Traps if the specified value is not true-ish, otherwise returns the non-nullable value. Like assertions in C, aborting the entire program if the expectation fails, with the --noAssert option to disable all assertions in production.

  • ts
    function instantiate<T>(...args: auto[]): T

    Instantiates a new instance of T using the specified constructor arguments.

  • ts
    function changetype<T>(value: auto): T

    Changes the type of a value to another one. Useful for casting class instances to their pointer values and vice-versa.

  • ts
    function idof<T>(): u32

    Obtains the computed unique id of a class type. Usually only relevant when allocating objects or dealing with RTTI externally.

  • ts
    function nameof<T>(value?: T): string

    Determines the name of a given type.

Low-level WebAssembly operations

Math

The following generic built-ins compile to WebAssembly instructions directly.

  • ts
    function clz<T>(value: T): T

    Performs the sign-agnostic count leading zero bits operation on a 32-bit or 64-bit integer. All zero bits are considered leading if the value is zero.

  • ts
    function ctz<T>(value: T): T

    Performs the sign-agnostic count tailing zero bits operation on a 32-bit or 64-bit integer. All zero bits are considered trailing if the value is zero.

  • ts
    function popcnt<T>(value: T): T

    Performs the sign-agnostic count number of one bits operation on a 32-bit or 64-bit integer.

  • ts
    function rotl<T>(value: T, shift: T): T

    Performs the sign-agnostic rotate left operation on a 32-bit or 64-bit integer.

  • ts
    function rotr<T>(value: T, shift: T): T

    Performs the sign-agnostic rotate right operation on a 32-bit or 64-bit integer.

  • ts
    function abs<T>(value: T): T

    Computes the absolute value of an integer or float.

  • ts
    function max<T>(left: T, right: T): T

    Determines the maximum of two integers or floats. If either operand is NaN, returns NaN.

  • ts
    function min<T>(left: T, right: T): T

    Determines the minimum of two integers or floats. If either operand is NaN, returns NaN.

  • ts
    function ceil<T>(value: T): T

    Performs the ceiling operation on a 32-bit or 64-bit float.

  • ts
    function floor<T>(value: T): T

    Performs the floor operation on a 32-bit or 64-bit float.

  • ts
    function copysign<T>(x: T , y: T): T

    Composes a 32-bit or 64-bit float from the magnitude of x and the sign of y.

  • ts
    function nearest<T>(value: T): T

    Rounds to the nearest integer tied to even of a 32-bit or 64-bit float.

  • ts
    function reinterpret<T>(value: auto): T

    Reinterprets the bits of the specified value as type T. Valid reinterpretations are u32/i32 to/from f32 and u64/i64 to/from f64.

  • ts
    function sqrt<T>(value: T): T

    Calculates the square root of a 32-bit or 64-bit float.

  • ts
    function trunc<T>(value: T): T

    Rounds to the nearest integer towards zero of a 32-bit or 64-bit float.

Memory

Similarly, the following built-ins emit WebAssembly instructions accessing or otherwise modifying memory.

  • ts
    function load<T>(ptr: usize, immOffset?: usize): T

    Loads a value of the specified type from memory. Equivalent to dereferencing a pointer in other languages.

  • ts
    function store<T>(ptr: usize, value: auto, immOffset?: usize): void

    Stores a value of the specified type to memory. Equivalent to dereferencing a pointer in other languages when assigning a value.

  • ts
    function memory.size(): i32

    Returns the current size of the memory in units of pages. One page is 64kb.

  • ts
    function memory.grow(value: i32): i32

    Grows linear memory by a given unsigned delta of pages. One page is 64kb. Returns the previous size of the memory in units of pages or -1 on failure.

    ::: warning
    Calling memory.grow where a memory manager is present might break it.
    :::

  • ts
    function memory.copy(dst: usize, src: usize, n: usize): void

    Copies n bytes from src to dst . Regions may overlap. Emits the respective instruction if bulk-memory is enabled, otherwise ships a polyfill.

  • ts
    function memory.fill(dst: usize, value: u8, n: usize): void

    Fills n bytes at dst with the given byte value. Emits the respective instruction if bulk-memory is enabled, otherwise ships a polyfill.

  • ts
    function memory.repeat(dst: usize, src: usize, srcLength: usize, count: usize): void

    Repeats a sequence of bytes given as src with srcLength count times into destination dst.

  • ts
    function memory.compare(lhs: usize, rhs: usize, n: usize): i32

    Compares the first n bytes of left and rigth and returns a value that indicates their relationship:

    • Negative value if the first differing byte in lhs is less than the corresponding byte in rhs.
    • Positive value if the first differing byte in lhs is greater than the corresponding byte in rhs.
    • Zero​ if all n bytes of lhs and rhs are equal.
  • ts
    function memory.data(size: i32, align?: i32): usize

    Gets a pointer to a zeroed static chunk of memory of the given size. Alignment defaults to 16. Arguments must be compile-time constants.

  • ts
    function memory.data<T>(values: T[], align?: i32): usize

    Gets a pointer to a pre-initialized static chunk of memory. Alignment defaults to the size of T. Arguments must be compile-time constants.

The immOffset argument is a bit special here, because it becomes an actual immediate of the respective WebAssembly instruction instead of a normal operand. Thus it must be provided as a compile time constant value. This can be a literal or the value of a const variable that the compiler can precompute.

**Memory Manager**

An unsafe interface to just the dynamic memory manager, resembling malloc, realloc and free in C.

  • ts
    function heap.alloc(size: usize): usize

    Allocates a chunk of memory of at least the specified size.

  • ts
    function heap.realloc(ptr: usize, size: usize): usize

    Reallocates a chunk of memory to have at least the specified size.

  • ts
    function heap.free(ptr: usize): void

    Frees a chunk of memory. Does hardly anything (most recent block only) with the "stub" runtime.

  • ts
    function heap.reset(): void

    Dangerously resets the entire heap. Specific to the "stub" runtime.

**Memory Utility**

Sign-agnostic endian conversions (reverse bytes).

  • ts
    function bswap<T>(value: T): T

    Reverses the byte order of the specified integer.

  • ts
    function bswap16<T>(value: T): T

    Reverses only the last 2 bytes regardless of the type argument.

Control flow

  • ts
    function select<T>(ifTrue: T, ifFalse: T, condition: bool): T

    Selects one of two pre-evaluated values depending on the condition. Differs from an if/else in that both arms are always executed and the final value is picked based on the condition afterwards. Performs better than an if/else only if the condition is random (means: branch prediction is not going to perform well) and both alternatives are cheap. It is also worth to note that Binaryen will do relevant optimizations like switching to a select automatically, so using a ternary ? : for example is just fine.

  • ts
    function unreachable(): auto

    Emits an unreachable instruction that results in a runtime error (trap) when executed. Both a statement and an expression of any type. Beware that trapping in managed code will most likely lead to memory leaks or even break the program because it ends execution prematurely.

Atomics 🦄

The following instructions represent the WebAssembly threads and atomics specification. Must be enabled with --enable threads.

  • ts
    function atomic.load<T>(ptr: usize, immOffset?: usize): T

    Atomically loads an integer value from memory and returns it.

  • ts
    function atomic.store<T>(ptr: usize, value: auto, immOffset?: usize): void

    Atomically stores an integer value to memory.

  • ts
    function atomic.add<T>(ptr: usize, value: T, immOffset?: usize): T

    Atomically adds an integer value in memory.

  • ts
    function atomic.sub<T>(ptr: usize, value: T, immOffset?: usize): T

    Atomically subtracts an integer value in memory.

  • ts
    function atomic.and<T>(ptr: usize, value: T, immOffset?: usize): T

    Atomically performs a bitwise AND operation on an integer value in memory.

  • ts
    function atomic.or<T>(ptr: usize, value: T, immOffset?: usize): T

    Atomically performs a bitwise OR operation on an integer value in memory.

  • ts
    function atomic.xor<T>(ptr: usize, value: T, immOffset?: usize): T

    Atomically performs a bitwise XOR operation on an integer value in memory.

  • ts
    function atomic.xchg<T>(ptr: usize, value: T, immOffset?: usize): T

    Atomically exchanges an integer value in memory.

  • ts
    function atomic.cmpxchg<T>(ptr: usize, expected: T, replacement: T, immOffset?: usize): T

    Atomically compares and exchanges an integer value in memory if the condition is met.

  • ts
    function atomic.wait<T>(ptr: usize, expected: T, timeout: i64): AtomicWaitResult

    Performs a wait operation on an address in memory suspending this agent if the integer condition is met. Return values are

    Value Description
    0 OK - Woken by another agent.
    1 NOT_EQUAL - Loaded value did not match the expected value.
    2 TIMED_OUT - Not woken before the timeout expired.
  • ts
    function atomic.notify(ptr: usize, count: i32): i32

    Performs a notify operation on an address in memory waking up suspended agents.

  • ts
    function atomic.fence(): void

    Performs a fence operation, preserving synchronization guarantees of higher level languages.

Again, the immOffset argument must be a compile time constant value.

SIMD 🦄

Likewise, these represent the WebAssembly SIMD specification. Must be enabled with --enable simd.

  • ts
    function v128(a: i8, ... , p: i8): v128

    Initializes a 128-bit vector from sixteen 8-bit integer values. Arguments must be compile-time constants.

  • ts
    function v128.splat<T>(x: T): v128

    Creates a vector with identical lanes.

  • ts
    function v128.extract_lane<T>(x: v128, idx: u8): T

    Extracts one lane as a scalar.

  • ts
    function v128.replace_lane<T>(x: v128, idx: u8, value: T): v128

    Replaces one lane.

  • ts
    function v128.shuffle<T>(a: v128, b: v128, ...lanes: u8[]): v128

    Selects lanes from either vector according to the specified lane indexes.

  • ts
    function v128.swizzle(a: v128, s: v128): v128

    Selects 8-bit lanes from the first vector according to the indexes [0-15] specified by the 8-bit lanes of the second vector.

  • ts
    function v128.load(ptr: usize, immOffset?: usize, immAlign?: usize): v128

    Loads a vector from memory.

  • ts
    function v128.load_splat<T>(ptr: usize, immOffset?: usize, immAlign?: usize): v128

    Creates a vector with identical lanes by loading the splatted value.

  • ts
    function v128.load_ext<TFrom>(ptr: usize, immOffset?: usize, immAlign?: usize): v128

    Creates a vector by loading the lanes of the specified integer type and extending each to the next larger type.

  • ts
    function v128.store(ptr: usize, value: v128, immOffset?: usize, immAlign?: usize): void

    Stores a vector to memory.

  • ts
    function v128.add<T>(a: v128, b: v128): v128

    Adds each lane.

  • ts
    function v128.sub<T>(a: v128, b: v128): v128

    Subtracts each lane.

  • ts
    function v128.mul<T>(a: v128, b: v128): v128

    Multiplies each lane.

  • ts
    function v128.div<T>(a: v128, b: v128): v128

    Divides each floating point lane.

  • ts
    function v128.neg<T>(a: v128): v128

    Negates each lane.

  • ts
    function v128.add_saturate<T>(a: v128, b: v128): v128

    Adds each signed small integer lane using saturation.

  • ts
    function v128.sub_saturate<T>(a: v128, b: v128): v128

    Subtracts each signed small integer lane using saturation.

  • ts
    function v128.shl<T>(a: v128, b: i32): v128

    Performs a bitwise left shift by a scalar on each integer lane.

  • ts
    function v128.shr<T>(a: v128, b: i32): v128

    Performs a bitwise right shift by a scalar on each integer lane.

  • ts
    function v128.and(a: v128, b: v128): v128

    Performs the bitwise a & b operation on each lane.

  • ts
    function v128.or(a: v128, b: v128): v128

    Performs the bitwise a | b operation on each lane.

  • ts
    function v128.xor(a: v128, b: v128): v128

    Performs the bitwise a ^ b operation on each lane.

  • ts
    function v128.andnot(a: v128, b: v128): v128

    Performs the bitwise !a & b operation on each lane.

  • ts
    function v128.not(a: v128): v128

    Performs the bitwise !a operation on each lane.

  • ts
    function v128.bitselect(a: v128, b: v128, mask: v128): v128

    Selects bits of either vector according to the specified mask.

  • ts
    function v128.any_true<T>(a: v128): bool

    Reduces a vector to a scalar indicating whether any lane is considered true.

  • ts
    function v128.all_true<T>(a: v128): bool

    Reduces a vector to a scalar indicating whether all lanes are considered true.

  • ts
    function v128.bitmask<T>(a: v128): bool

    Extracts the high bit of each integer lane (except 64-bit) and produces a scalar mask with all bits concatenated.

  • ts
    function v128.max<T>(a: v128, b: v128): v128

    Computes the maximum of each lane.

  • ts
    function v128.min<T>(a: v128, b: v128): v128

    Computes the minimum of each lane.

  • ts
    function v128.dot<T>(a: v128, b: v128): v128

    Computes the dot product of two 16-bit integer lanes each, yielding lanes one size wider than the input.

  • ts
    function v128.avgr<T>(a: v128, b: v128): v128)

    Computes the rounding average of each unsigned small integer lane.

  • ts
    function v128.abs<T>(a: v128): v128

    Computes the absolute value of each lane (except 64-bit integers).

  • ts
    function v128.sqrt<T>(a: v128): v128

    Computes the square root of each floating point lane.

  • ts
    function v128.ceil<T>(a: v128): v128

    Performs the ceiling operation on each lane.

  • ts
    function v128.floor<T>(a: v128): v128

    Performs the floor operation on each lane.

  • ts
    function v128.trunc<T>(a: v128): v128

    Rounds to the nearest integer towards zero of each lane.

  • ts
    function v128.nearest<T>(a: v128): v128

    Rounds to the nearest integer tied to even of each lane.

  • ts
    function v128.eq<T>(a: v128, b: v128): v128

    Computes which lanes are equal.

  • ts
    function v128.ne<T>(a: v128, b: v128): v128

    Computes which lanes are not equal.

  • ts
    function v128.lt<T>(a: v128, b: v128): v128

    Computes which lanes of the first vector are less than those of the second.

  • ts
    function v128.le<T>(a: v128, b: v128): v128

    Computes which lanes of the first vector are less than or equal those of the second.

  • ts
    function v128.gt<T>(a: v128, b: v128): v128

    Computes which lanes of the first vector are greater than those of the second.

  • ts
    function v128.ge<T>(a: v128, b: v128): v128

    Computes which lanes of the first vector are greater than or equal those of the second.

  • ts
    function v128.convert<TFrom>(a: v128): v128

    Converts each lane from integer to floating point.

  • ts
    function v128.trunc_sat<TTo>(a: v128): v128

    Truncates each lane from floating point to integer with saturation.

  • ts
    function v128.narrow<TFrom>(a: v128, b: v128): v128

    Narrows wider integer lanes to their respective narrower lanes.

  • ts
    function v128.widen_low<TFrom>(a: v128): v128

    Widens the low half of narrower integer lanes to their respective wider lanes.

  • ts
    function v128.widen_high<TFrom>(a: v128): v128

    Widens the high half of narrower integer lanes to their respective wider lanes.

  • ts
    function v128.qfma<T>(a: v128, b: v128, c: v128): v128

    Computes (a * b) + c for each floating point lane.

  • ts
    function v128.qfms<T>(a: v128, b: v128, c: v128): v128

    Computes (a * b) - c for each floating point lane.

In addition, the namespaces i8x16, i16x8, i32x4, i64x2 , f32x4 and f64x2 provide their respective non-generic instructions, like i32x4.splat etc. Each of them can also be used to create a literal directly:

  • ts
    function i8x16(a: i8, ... , p: i8): v128

    Initializes a 128-bit vector from sixteen 8-bit integer values. Arguments must be compile-time constants.

  • ts
    function i16x8(a: i16, ..., h: i16): v128

    Initializes a 128-bit vector from eight 16-bit integer values. Arguments must be compile-time constants.

  • ts
    function i32x4(a: i32, b: i32, c: i32, d: i32): v128

    Initializes a 128-bit vector from four 32-bit integer values. Arguments must be compile-time constants.

  • ts
    function i64x2(a: i64, b: i64): v128

    Initializes a 128-bit vector from two 64-bit integer values. Arguments must be compile-time constants.

  • ts
    function f32x4(a: f32, b: f32, c: f32, d: f32): v128

    Initializes a 128-bit vector from four 32-bit float values. Arguments must be compile-time constants.

  • ts
    function f64x2(a: f64, b: f64): v128

    Initializes a 128-bit vector from two 64-bit float values. Arguments must be compile-time constants.

The namespaces v8x16, v16x8, v32x4 and v64x2 provide the respective sign agnostic instructions according to text format.


File: src/examples.md


sidebarDepth: 0

Examples

A collection of AssemblyScript examples and snippets.

Starter examples

Small entertaining programs showcasing low-level WebAssembly capabilities. These compile to less than one or just a few kilobytes so their text format is easy to grasp.

[Mandelbrot](./examples/mandelbrot.md)

Renders the Mandelbrot set to a canvas using 2048 discrete color values computed on the JS side.

[Interference](./examples/interference.md)

Animates and renders an interference pattern to a canvas while keeping the image buffer in WebAssembly.

[Game of Life](./examples/game-of-life.md)

Continuously updates a cellular automaton and visualizes its state on a canvas according to user input.

Advanced examples

[Arrays](./examples/arrays.md)

Shows how to exchange and work with arrays using the loader.

Examples repository

Various more advanced examples are available as part of the examples repository, including a sophisticated example of using the loader, creating (node) libraries, utilizing the browser SDK and hooking into the compiler using transforms.

Additional resources

If you are interested in learning more about specific concepts, also make sure to give Wasm By Example a read.


File: src/exports-and-imports.md



Exports and imports

Setting up the API to talk to your WebAssembly module.

Exports

Exports work very much like in TypeScript, with the notable difference that exports from an entry file also become the exports of the resulting WebAssembly module.

Functions

ts
export function add(a: i32, b: i32): i32 {
  return a + b
}

Globals

ts
export const foo = 1
export var bar = 2

Exporting or importing mutable globals requires that the engine supports the post-MVP and now standardized Import/Export mutable globals proposal, which is enabled by default.

Classes

If an entire class (possibly part of a namespace) is exported from an entry file, its visible members will become distinct module exports using a JSDoc-like naming scheme that the loader understands to make a nice object structure of. For example

ts
export namespace foo {
  export class Bar {
    a: i32 = 1
    getA(): i32 { return this.a }
  }
}

will yield the raw function exports

  • foo.Bar#constructor
  • foo.Bar#get:a
  • foo.Bar#set:a
  • foo.Bar#getA

which the loader combines back to a foo.Bar object:

js
var bar = new myModule.foo.Bar()
bar.a = 2
console.log(bar.a)

Under the hood and without the loader, it'd look like this:

js
var thisBar = myModule["foo.Bar#constructor"](0)
myModule["foo.Bar#set:a"](thisBar, 2)
console.log(myModule["foo.Bar#get:a"](thisBar))

As one can see the this argument must be provided as an additional first argument to the raw functions. No argument or 0 as the first argument to a constructor indicates that the constructor is expected to allocate on its own (not extending a class with existing memory). Of course one usually doesn't have to deal with these internals since the loader will already take care of it.

Imports

With WebAssembly ES Module Integration still in the pipeline, imports utilize the ambient context currently. For example

ts
// env.ts
export declare function doSomething(foo: i32): void

creates an import of a function named doSomething from the env module, because that's the name of the file it lives is. It is also possible to use namespaces:

ts
// foo.ts
export declare namespace console {
  export function logi(i: i32): void
  export function logf(f: f64): void
}

This will import the functions console.logi and console.logf from the foo module. Bonus: Don't forget exporting namespace members if you'd like to call them from outside the namespace.

A typical pattern is to declare the interface of each external module in its own file, using the name of the module as its file name, so the external module can be imported as if it was just another source file:

ts
import { doSomething } from "./env";
import { console } from "./foo";

Custom naming

Where automatic naming is not sufficient, the @external decorator can be used to give an element another external name:

ts
// bar.ts
@external("doSomethingElse")
export declare function doSomething(foo: i32): void
// imports doSomethingElse from bar as doSomething

@external("foo", "baz")
export declare function doSomething(foo: i32): void
// imports baz from foo as doSomething

On values crossing the boundary

More complex values than the basic integer and floating point types, like class instances or function references, are represented by an index or pointer within the WebAssembly module. The compiler knows how to work with these because it also knows the concrete type associated with the value. On the outside, however, for example in JS-code interacting with a WebAssembly module, all that's seen is the basic value.

  • An instance of a class is a pointer to the structure in WebAssembly memory.
  • A function reference is the index of the function in the WebAssembly table.

This is true in both directions, hence also applies when providing a value to a WebAssembly import. The most common structures like String, ArrayBuffer and the typed arrays are documented in memory internals, and custom classes adhere to class layout.

Anatomy of a module

WebAssembly modules produced by the AssemblyScript compiler typically have the following set of imports and exports in addition to those defined in user code, with ? indicating conditional presence. Recommended scenarios like using modules together with the loader or utilizing WASI via import "wasi" take care of these automatically.

Exports

  • ts
    const memory: WebAssembly.Memory

    The module's memory instance. Can be disabled with --noExportMemory.

  • ts
    const table?: WebAssembly.Table

    The module's function table instance. Can be enabled with --exportTable.

  • ts
    function _start?(): void

    Explicit start function to call. Can be enabled with --explicitStart. Also works with WASI.

  • ts
    function __setArgumentsLength?(numArgs: i32): void

    Varargs helper to set what's known as arguments.length in JavaScript. Only present if there is at least one export of a function taking a variable number of arguments. Automatically utilized by the loader.

  • ts
    function __new?(size: usize, id: u32): usize
    function __pin?(ptr: usize): usize
    function __unpin?(ptr: usize): void
    function __collect?(): void

    Garbage collection helpers. Only present when compiling with --exportRuntime.

Imports

  • ts
    const env.memory?: WebAssembly.Memory

    The module's memory instance. Can be enabled with --importMemory.

  • ts
    const env.table?: WebAssembly.Table

    The module's table instance. Can be enabled with --importTable.

  • ts
    function env.abort?(message: usize, fileName: usize, line: u32, column: u32): void

    Called on unrecoverable errors. Typically present if assertions are enabled. Automatically implemented by the loader. Not present but otherwise implemented on import "wasi".

  • ts
    function env.trace?(message: usize, n: i32, a0..4?: f64): void

    Called when trace is called in user code. Only present if it is. Automatically implemented by the loader. Not present but otherwise implemented on import "wasi".

  • ts
    function env.seed?(): f64

    Called when the random number generator needs to be seeded. Only present if it is. Automatically implemented by the loader. Not present but otherwise implemented on import "wasi".


File: src/frequently-asked-questions.md


sidebarDepth: 0

Frequently asked questions

Questions that may arise before, at or after this point. so if you're wondering about one thing or another, here it is:

Is WebAssembly going to supersede JavaScript?

Probably not, and it is not meant to do so. There are use cases one can handle "better" than the other, with "better" depending on requirements. A requirement can be to make an algorithm more efficient on the one hand, which is where WebAssembly is good at, or to quickly add interactive elements to a web page, which JavaScript is great for. As always, picking the right tool for the job is key, and AssemblyScript just so happens to blur the line a bit.

Is WebAssembly faster than JavaScript?

Not always, but it can be. The execution characteristics of ahead-of-time compiled WebAssembly differ from just-in-time compiled JavaScript in that it is more predictable in a way that enables a WebAssembly program to remain on a reasonably fast path over the entire course of execution, while a JavaScript VM tries hard to do all sorts of smart optimizations before and while the code is executing. This implies that a JavaScript VM can make both very smart decisions, especially for well-written code with a clear intent, but might also have to reconsider its strategy to do something more general if its assumptions did not hold. If you are primarily interested in performance, rules of thumb could be:

Scenario Recommendation
Compute-heavy algorithm Use WebAssembly
Mostly interacts with the DOM Mostly use JavaScript
Games Use WebAssembly for CPU-intensive parts
WebGL Depends how much of it is calling APIs. Probably both.
Websites, Blogs, ... JavaScript is your friend

Or: WebAssembly is great for computational tasks, stuff with numbers, but still needs some time to become more convenient and efficient at the same time where sharing numbers between the module and the host is not enough.

Is AssemblyScript faster than JavaScript?

Not always, but there are use cases especially well-suited for it, like creating a Game Boy emulator by making use of its low-level capabilities, essentially emitting raw WebAssembly using a nicer syntax. But pre-existing TypeScript code doesn't magically become faster just by compiling to WebAssembly, especially when making extensive use of managed objects that require memory management and garbage collection (this has its cost in every language) or talking to the host in structures that WebAssembly isn't currently good at, like strings or more complex objects. Low-level code (just functions, numbers, math and hard work) is always the best choice when all you care about is raw performance.

How does AssemblyScript compare/relate to C++/Rust?

First and foremost: Both Emscripten (C++) and Rust have very mature tooling to compile to WebAssembly and are made by the smartest people in this field. Also, both can make use of compiler infrastructure that has been created by many individuals and corporations over years. In contrast, AssemblyScript is a relatively young project with limited resources that strives to create a viable alternative from another perspective.

More precisely: AssemblyScript is putting anything web - from APIs to syntax to WebAssembly - first and then glues it all together, while others lift an existing ecosystem to the web. We believe that both approaches are fantastic, and fortunately for us, there is Binaryen, a compiler infrastructure and toolchain library for WebAssembly primarily created by the main author of Emscripten, that we can utilize to considerably close the gap. We are very thankful for it. Binaryen is not as optimal for AssemblyScript-generated code as it is for LLVM-generated code in a few cases, but it's already pretty good and continuously becoming better. It's also noteworthy that AssemblyScript is still behind in specific language features, but we are working on that.

In short: AssemblyScript differs in that it is new and tries another approach. It's not as mature as Emscripten and Rust, but there is something about the idea that is definitely appealing. If you find it appealing as well, AssemblyScript is for you.

Will AssemblyScript support all of TypeScript eventually?

It very likely won't. While TypeScript adds some static typing to JavaScript, its type system also tries to describe any possible dynamic feature of JavaScript. TypeScript is a superset of JavaScript after all. As such there is a point where it becomes infeasible to support JavaScript features that TypeScript can describe without adding an interpreter mode to AssemblyScript, which is a non-goal.

Will interop between AssemblyScript and JavaScript become better?

This is an open question. While there are WebAssembly proposals specifically aiming at improving interop, not all of them consider excellent interoperability between WebAssembly and the existing web platform as important as we do.

One point of disagreement for example stems from AssemblyScript's decision to chose JavaScript's 16-bit string encoding as a basis, initially exactly to aid compatibility and interop on the web, while others primarily interested in systems languages are advocating for just UTF-8. The UTF-8 everywhere sentiment may appear noble on paper, yet the reality we live in is that it would unnecessarily handicap AssemblyScript or other languages with most of their APIs designed for 16-bit Unicode, due to always having to re-encode and/or copy or alternatively having to break their APIs to be efficient. Our hopes are, however, since Java, .NET and many more languages are facing a similar challenge, that there'll ultimately be a solution that covers more languages.

Similarly, WebAssembly has primarily been designed with systems languages in mind so far and it did an exceptional job at bringing these to the web, yet it appears that the now predominant low-level mindset may eventually run counter to designing features that work well with the existing web platform, which is rather high-level. As such it is still subject to discussion whether for example WebAssembly GC strings, arrays and other objects will have good interop with JavaScript, or if JavaScript strings, arrays and other objects will have good interop with WebAssembly GC respectively. It is certainly important to design these APIs carefully, yet, in our opinion, WebAssembly may be missing the opportunity of its lifetime if it is not designed with excellent interop with JavaScript and the web in mind.

If you are interested in these aspects, feel free to join us in the respective spec discussions to inform the process.

How can I help?

There are various ways to help. AssemblyScript is an open source project, and everyone is welcome to contribute code, documentation, or time. We also have an OpenCollective for those preferring to help the project out with a sponsorship.


File: src/garbage-collection.md


sidebarDepth: 2

Garbage Collection

::: tip NOTE
This section covers the new runtime added in AssemblyScript 0.18. The previous runtime documentation including migration guidelines can be found here.
:::

AssemblyScript implements garbage collection on top of linear memory while the WebAssembly GC 🦄 proposal is still in the works. More precisely it implements multiple runtime variants that are useful for different use cases, with the default being your typical memory manager and GC. The interface of the different variants is always the same, so these are interchangeable, yet differ in their level of sophistication. In case of doubt, starting out with the default is probably just fine.

Runtime interface

Using the --exportRuntime compiler option, an interface to the runtime can be exported from the module to the host, so it becomes possible to allocate new objects and invoke the garbage collector externally.

  • ts
    function __new(size: usize, id: u32): usize

    Allocates a new garbage collected instance of the object represented by the specified class id, of at least the specified size. Returns the pointer to the object, typically pointing at the object's data respectively its first field.

    Note that the loader provides more convenient wrappers like __newString and __newArray as well. __new is just the underlying implementation. The respective class id for the id argument can be obtained within WebAssembly via idof<AClass>(), typically exporting it to the host as a constant to use with __new or __newArray.

  • ts
    function __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.

  • ts
    function __unpin(ptr: usize): void

    Unpins the object pointed to by ptr externally so it can become garbage collected.

  • ts
    function __collect(): void

    Performs a full garbage collection.

To interface with the runtime externally, for example to allocate objects in linear memory, you'll probably want to use the loader as well.

Runtime variants

With the --runtime compiler option, one of multiple runtime variants can be selected, which determines the memory allocator and garbage collector included in a program:

text
--runtime             Specifies the runtime variant to include in the program.

                         incremental  TLSF + incremental GC (default)
                         minimal      TLSF + lightweight GC invoked externally
                         stub         Minimal runtime stub (never frees)
                         ...          Path to a custom runtime implementation

  --exportRuntime       Exports the runtime helpers (__new, __collect etc.).

Incremental runtime

This is the default runtime variant included in an AssemblyScript program, combining an Incremental Tri-Color Mark & Sweep (ITCMS) garbage collector with a Two-Level Segregate Fit (TLSF) memory manager. It is fully automated as one would expect from a typical GC, but its implementation has to go to lengths, involving some overhead, to polyfill functionality that is not yet provided by WebAssembly itself. It needs to maintain a shadow stack for example, and instruments binaries with it.

The incremental runtime is most suitable for non-trivial programs where short pause times are important, like in games, or where manually invoking the GC is not an option, like in WASI applications.

Usage instructions:

  • When a managed object is exclusively referenced externally, exports.__pin(objPtr) it so it doesn't become collected when WebAssembly code executes.
  • When the external object is not needed anymore, exports.__unpin(objPtr) it so it can become collected again.
  • Note that a pinned object also keeps its directly reachable members and any indirectly reachable objects alive.
  • It is safe to omit pinning an object externally if it is known to be referenced from within WebAssembly anyhow.
  • It is possible to force a full collection (finishes current cycle, does another full cycle) using exports.__collect(), if so desired.

Example usage:

js
function run() {
  let gameEnded = exports.updateGameProducingGarbage() // incrementally performs GC
  if (gameEnded) {
    exports.renderEndgameScreenProducingGarbage()
    exports.__collect() // optionally clean up the remains
  } else {
    requestAnimationFrame(run)
    exports.renderGameProducingGarbage() // incrementally performs GC
  }
}
run()

Minimal runtime

The minimal runtime is similar to the incremental runtime, except that it doesn't go to lengths to polyfill WebAssembly features that are not yet available. It features a simpler (non-incremental) Two-Color Mark & Sweep (TCMS) garbage collector on top of TLSF, making it a reasonable compromise.

The minimal runtime works best for use-cases where the GC can be invoked externally at appropriate places, that is when the WebAssembly execution stack is known to be fully unwound. The execution stack is typically unwound when exports.__collect() is invoked neither directly nor indirectly from WebAssembly, for example when invoking it from JavaScript only. The AssemblyScript compiler itself is a good candidate for the minimal runtime for example, as it does bounded amounts of work in bounded amounts of time, so it is sufficient to call exports.__collect() occasionally when specific phases of compilation are complete.

Unlike the incremental runtime, the minimal runtime does not run interleaved with the program but always does a full garbage collection cycle when invoked externally, stopping the program while doing so. It is important to not overdo invoking the GC to avoid marking a lot but sweeping little for that reason. However, when used wisely, it typically has better throughput than the incremental runtime.

Usage instructions:

  • Whenever you see fit, invoke exports.__collect() externally to collect all garbage. Note that this will mark & sweep all objects, so don't overdo calling it to avoid unnecessary overhead from marking a lot but sweeping little. In case of doubt, benchmark to find a reasonable sweet spot.
  • When a managed object is exclusively referenced externally, exports.__pin(objPtr) it so it doesn't become collected when exports.__collect() is being invoked.
  • When the external object is not needed anymore, exports.__unpin(objPtr) it so it can become collected again.
  • Note that a pinned object also keeps its directly reachable members and any indirectly reachable objects alive.
  • It is safe to omit pinning an object externally if
    • it is known to be referenced from within WebAssembly anyhow, directly or indirectly reachable from any root, like a global.
    • it is solely used as an argument to a WebAssembly function and not used externally anymore afterwards.
    • it is guaranteed that exports.__collect() will not be called while it is alive.
  • In case of doubt, pin.

Example usage:

js
function compute(arg) {
  exports.doSomeHeavyWorkProducingGarbage(arg)
  ...
  exports.__collect() // clean up all garbage
}
compute(1)
compute(2)
compute(3)

Stub runtime

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. As such, the stub runtime is useful where dynamic allocation, or garbage for that matter, is not a concern, for example because the program doesn't produce any or only executes a single time with a bounded limit of garbage before the entire module is terminated and collected by the host again.

Usage instructions:

  • Since the stub runtime never frees memory, there is nothing special to take care of except that it leaks memory by design.

Example usage:

js
function compute() {
  exports.doSomeHeavyWorkProducingGarbage()
}
compute()
// throw away the entire module instance

Custom runtime

Even though of limited usefulness, it is also possible to create a new runtime (essentially a memory manager and a garbage collector) from scratch and include it instead of one of the options provided by AssemblyScript itself. To do so, a custom runtime needs to provide the implementations of the externally visible interfaces like __new, plus the underlying interfaces like __alloc and __free, on top of compiler-generated functionality like __visit_members, as described in more detail in the runtime's README. Looking at the implementation of the relatively simple stub runtime before diving into the more sophisticated ones may be a good starting point.

  • Runtimes typically include a general purpose memory manager to also drive heap.alloc and heap.free
  • Garbage collection is precise, with compiler-generated visitors aware of what's a pointer and what's not
  • The interface is tailored for tracing GCs, so attempts to implement other forms of GC may not be viable

The future: WebAssembly GC

The WebAssembly GC 🦄 proposal is still in the works but our focus is to switch to it as soon as possible, hopefully making most of the above redundant and the overall experience more convenient.


File: src/index.md


text
<svg viewBox="0 0 768 256"></svg>

A language made for WebAssembly.

text
<a href="/introduction.html" class="docs">
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"></svg>
  Get Started
</a>
<a href="https://github.com/AssemblyScript" target="_blank" rel="noopener" class="github">
  <svg viewBox="0 0 24 24"></svg>
  GitHub
</a>
<a href="https://www.npmjs.com/package/assemblyscript" target="_blank" rel="noopener" class="npm">
  <svg viewBox="0 0 24 24"></svg>
  npm
</a>





<h2>Designed for WebAssembly</h2>
AssemblyScript targets WebAssembly's feature set specifically, giving developers low-level control over their code.


<h2>Familiar TypeScript syntax</h2>
Being a variant of TypeScript makes it easy to compile to WebAssembly without learning a new language.


<h2>Right at your fingertips</h2>
Integrates with the existing Web ecosystem - no heavy toolchains to set up. Simply `npm install` it!
editor
#!runtime=stub
/** Calculates the n-th Fibonacci number. */
export function fib(n: i32): i32 {
  var a = 0, b = 1
  if (n > 0) {
    while (--n) {
      let t = a + b
      a = b
      b = t
    }
    return b
  }
  return a
}

#!html
<textarea id="output" style="height: 100%; width: 100%" readonly></textarea>
<script>
loader.instantiate(module_wasm, { /* imports */ })
  .then(({ exports }) => {
    const output = document.getElementById('output')
    for (let i = 0; i <= 10; ++i) {
      output.value += `fib(${i}) = ${exports.fib(i)}\n`
    }
  })
</script>

AssemblyScript is free and open source software released under the Apache License, Version 2.0, builds upon Binaryen and is based on the WebAssembly specification. It is brought to you by the following awesome people:

Why AssemblyScript?

Thanks to our sponsors!

Most of the core team members and most 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](https://opencollective.com/assemblyscript/donate) to our [OpenCollective](https://opencollective.com/assemblyscript). By sponsoring this project, your logo will show up below. Thank you so much for your support!

Join our Discord

If you have questions only a human can answer, would like to show others what you are working on or just want to hang out with other AssemblyScript folks, make sure to [join our Discord server](https://discord.gg/assemblyscript)! There you'll find channels for **#announcements**, **#help**, and more.

File: src/interoperability.md



Interoperability

How to talk to AssemblyScript from C or other languages.

Strings

Strings in AssemblyScript are stored in WTF-16 encoding, closely matching JavaScript and its string APIs. When embedding AssemblyScript into a C or Rust environment, conversion between WTF-16 and UTF-8 is typically necessary (API).

Details to keep in mind: In UTF-16, certain Unicode code points are represented by two UTF-16 code units called surrogate pairs. However, just like in JavaScript, strings are not sanitized so lone surrogates can appear in a string, which are technically ill-formed UTF-16. This is done to mimic JavaScript as closely as possible, and avoids string re-encoding when calling JavaScript APIs from WebAssembly.

We hope that future WebAssembly features will be designed with both C/Rust and JS/WebAPIs in mind without penalizing the respective other. (1, 2)

Class layout

Layout of objects in memory resembles that of non-packed C structs, and @unmanaged classes (not tracked by GC, essentially C structs) can be used to describe them:

c
struct Foo {
  uint32_t bar;
  uint16_t baz;
  uint64_t qux;
}
ts
@unmanaged class Foo {
  bar: u32; // aligned to 4 bytes
  baz: u16; // aligned to 2 bytes
  qux: u64; // aligned to 8 bytes
}

offsetof<Foo>("bar") == 0
offsetof<Foo>("baz") == 4
offsetof<Foo>("qux") == 8
offsetof<Foo>() == 16

var foo = changetype<Foo>(ptrToFoo)
foo.bar // -> i32.load(ptrToFoo, 0)
foo.baz // -> i32.load16_u(ptrToFoo, 4)
foo.qux // -> i64.load(ptrToFoo, 8)

More complex structures usually require manual offset calculation, mostly for the reason that it is not feasible to implement something more specific into AssemblyScript as it does not use such structures on its own:

c
struct Foo {
  int32_t bar;
  uint32_t baz[10];
}
ts
@unmanaged class Foo {
  bar: i32
  getBaz(i: i32): u32 {
    return load<u32>(
      changetype<usize>(this) + (<usize>i << alignof<u32>()),
      4 // or use offsetof/sizeof to calculate the base offset plus alignment
    )
  }
}

Calling convention

Exported functions will properly wrap return values, but functions not visible externally do not guarantee proper wrapping.

ts
export function getU8(): u8 {
  return someU8 // will wrap to someU8 & 0xff
}

On the other hand, calling any AssemblyScript function does not require wrapping of arguments.

Optional arguments

If a function exported from AssemblyScript accepts optional arguments, the exports.__setArgumentsLength(numArguments) helper must be called with the number of actual arguments (while zeroing all the others) to inform the varargs stub to fill in the default values of omitted arguments. The loader does this automatically.

Garbage collection

Also see the dedicated page on garbage collection.


File: src/introduction.md


sidebarDepth: 0

Introduction

AssemblyScript compiles a strict variant of TypeScript (a typed superset of JavaScript) to WebAssembly using Binaryen, looking like:

ts
export function fib(n: i32): i32 {
  var a = 0, b = 1
  if (n > 0) {
    while (--n) {
      let t = a + b
      a = b
      b = t
    }
    return b
  }
  return a
}
sh
asc fib.ts -b fib.wasm -O3

Its architecture differs from a JavaScript VM in that it compiles a program ahead of time, quite similar to your typical C compiler. One can think of it as TypeScript syntax on top of WebAssembly instructions, statically compiled, to produce lean and mean WebAssembly binaries.

In a nutshell

On top of WebAssembly types, AssemblyScript not only provides low-level built-ins to access WebAssembly features directly, making it suitable as a thin layer on top of Wasm, but also comes with its own JavaScript-like standard library and a relatively small memory management and garbage collection runtime, making it suitable as a general purpose higher-level language for WebAssembly today.

For example, on the lowest level, memory can be accessed using the load<T>(offset) and store<T>(offset, value) built-ins that compile to WebAssembly instructions directly

ts
store<i32>(8, load<i32>(0) + load<i32>(4))

while the standard library provides higher-level implementations of Array, ArrayBuffer, DataView, Math (double and single precision), Uint8Array, String, Map, Set etc.:

ts
var view = new Int32Array(12)
view[2] = view[0] + view[1]

It is worth to note, however, that AssemblyScript still has its quirks, and we are patiently waiting for future WebAssembly features (marked as 🦄 throughout the documentation) to become available for us to use.

Sounds appealing to you? Read on!


File: src/loader.md


sidebarDepth: 2

Loader

AssemblyScript provides 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.

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 to it while evaluating export names and making a nice object structure of them. For example, if one exports an entire class Foo from the module, there will be an exports.Foo constructor etc. available when using the loader.

::: tip NOTE
Allocating objects or invoking the GC externally requires compiling with --exportRuntime. See: Garbage collection.
:::

Installation

For each version of the AssemblyScript compiler, there is a standalone version of the loader that can be installed from npm:

sh
npm install --save @assemblyscript/loader

On the web:

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

::: tip
If you need a specific version of the loader, append the respective version number as usual.
:::

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<Int32Array>()
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: 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.

  • 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 __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. See: Garbage collection

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 MyModule from "myModule"; // pointing at the generated d.ts

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

File: src/memory.md



Memory

Similar to other languages that use linear memory, and until the GC 🦄 proposal becomes available, all data in AssemblyScript is stored at a specific offset in linear memory so other parts of the program can read and modify it.

Importing memory

The WebAssembly.Memory instance used by your program can be imported from the host by using the --importMemory command line option. The module will then expect an import named memory within the env module to be provided upon instantiation.

An imported memory becomes the singleton memory of the module and can be accessed in the same way as a non-imported memory. Note that if the module defines data segments, it will place the data into the imported memory upon instantiation of the module, which must be taken into account when pre-populating the memory externally (e.g. when utilizing --memoryBase to reserve some space as described in Memory Regions below).

Exporting memory

A module exports its memory as memory by default, unless disabled with --noExportMemory.

Accessing memory during instantiation

AssemblyScript runs any top-level statements as part of the emitted WebAssembly module's implicit start function by default. Since the implicit start function executes immediately upon instantiation, this leads to a special case because control flow has not yet yielded back to the host, so the module's exported memory only becomes available externally after the implicit start function completes. The implicit start function may however already trigger external code that requires to read from memory, like when printing a string to console.

There are two solutions to this:

  1. Exporting an explicit start function

    The --explicitStart command line argument can be used to force exporting the start function as _start instead of making it the module's implicit start function. When doing so it is important to call exports._start() once before calling any other exports to properly initialize the module first.

  2. Preparing memory pre-instantiation and importing it

    An alternative is to create the WebAssembly.Memory object before instantiating the module, and using --importMemory to import it. This way around the memory is already known when the implicit start function executes.

Memory regions

Internally, there are two regions of memory the compiler is aware of:

Static memory

Memory starts with static data, like strings and arrays (of constant values) the compiler encountered while translating the program. Unlike in other languages, there is no concept of a stack in AssemblyScript and it instead relies on WebAssembly's execution stack exclusively.

A custom region of memory can be reserved using the --memoryBase option. For example, if one needs an image buffer of exactly N bytes, instead of allocating it one could reserve that space, telling the compiler to place its own static data afterwards, partitioning memory in this order:

Region Description
Reserved memory As specified with --memoryBase
Static memory Starting right after reserved memory, ends at __heap_base
Dynamic memory Starting right after static memory, starts at __heap_base

Dynamic memory

Dynamic memory, commonly known as the heap, is managed by the garbage collector, at runtime. When space for a new object is requested by the program, the runtime's memory manager reserves a suitable region and returns a pointer to it to the program. Once an object is not needed anymore and becomes unreachable, the garbage collector returns the object's memory to the memory manager for reuse.

The memory manager guarantees an alignment of 16 bytes, so an ArrayBuffer, which can be the backing buffer of multiple views, always fits up to v128 values with natural alignment.

Internals

WebAssembly is specified to use little-endian and as a result, so is AssemblyScript.

Objects in AssemblyScript have a common hidden header used by the runtime to keep track of them. The header includes information about the block used by the memory manager, state information used by the garbage collector, a unique id per concrete class and the data's actual size. The length of a String (id = 1) is computed from that size for example. The header is "hidden" in that the reference to an object points right after it, at the first byte of the object's actual data.

Common header layout

Name Offset Type Description
#mmInfo -20 usize Memory manager info
#gcInfo -16 usize Garbage collector info
#gcInfo2 -12 usize Garbage collector info
#rtId -8 u32 Unique id of the concrete class
#rtSize -4 u32 Size of the data following the header
0 Payload starts here

In Wasm32, the header is aligned to 16 bytes right after mmInfo and rtSize, with 16 bytes of managed information in between. The object's payload starts at 16 bytes alignment again.

The most basic objects using the common header are ArrayBuffer and String. These do not have any fields but are just data:

ArrayBuffer layout

Name Offset Type Description
-20 See above Common header
[0] 0 8-bit untyped The first byte
[1] 1 8-bit untyped The second byte
...
[N] N 8-bit untyped The N-th byte

String layout

Name Offset Type Description
-20 See above Common header
[0] 0 u16 The first character
[1] 2 u16 The second character
...
[N] N << 1 u16 The N-th character

Collections

Collections like Array, Map and Set use one or multiple ArrayBuffers to store their data, but their backing buffers are only exposed for typed arrays, as it is the case in JavaScript. This is because collections can grow automatically, which would otherwise lead to no longer valid references to buffers sticking around.

ArrayBufferView layout

This is not an actual class but a helper to simplify the implementation of the typed array views. Each typed array view like Uint8Array is like a subclass of ArrayBufferView with a static value type attached.

Name Offset Type Description
-20 See above Common header
#data 0 ArrayBuffer Backing buffer reference
#dataStart 4 usize Start of the data within .data
#dataLength 8 u32 Length of the data from .dataStart

Array layout

Name Offset Type Description
-20 See above Common header
0 See above ArrayBufferView
.length 12 i32 Mutable length of the data the user is interested in

The layouts of Set and Map are a bit more sophisticated and therefore not mentioned here, but feel free to take a look at their sources.


File: src/peculiarities.md



Peculiarities

Special features that exist for internal implementation purposes or are otherwise nice to have.

Annotations

Decorators work more like actual compiler annotations in AssemblyScript.

Annotation Description
@inline Requests inlining of a constant or function.
@lazy Requests lazy compilation of a variable. Useful to avoid unnecessary globals.
@global Registers an element to be part of the global scope.
@external Changes the external name of an imported element. @external(module, name) changes both the module and element name, @external(name) changes the element name only.
@operator Annotates a method as a binary operator overload. See below.
@operator.binary Alias of @operator.
@operator.prefix Annotates a method as a unary prefix operator overload. See below.
@operator.postfix Annotates a method as a unary postfix operator overload. See below.

Custom decorators can be given meaning by using a transform.

Operator overloads

Operator overloads can only be used on class methods. The respective argument types must match the class type.

Binary operations

ts
@operator(OP)
static __op(left: T, right :T): T { ... }

@operator(OP)
__op(right: T): T  { ... }
OP Description
"[]" Checked indexed get
"[]=" Checked indexed set
"{}" Unchecked indexed get
"{}=" Unchecked indexed set
"==" Equality
"!=" Inequality
">" Greater than
">=" Greater than or equal
"<" Less than
"<=" Less than or equal
">>" Arithmetic right shift
">>>" Logical right shift
"<<" Left shift
"&" Bitwise AND
`" "`
"^" Bitwise XOR
"+" Addition
"-" Subtraction
"*" Multiplication
"/" Division
"**" Exponentiation
"%" Remainder

The === operation cannot be overloaded (is identity equality).

Unary prefix operations

ts
@operator.prefix(OP)
static __op(self: T): T { ... }

@operator.prefix(OP)
__op(): T { ... }
OP Description Notes
"!" Logical NOT
"~" Bitwise NOT
"+" Unary plus
"-" Unary negation
"++" Prefix increment Instance overload reassigns
"--" Prefix decrement Instance overload reassigns

::: tip
Note that increment and decrement overloads can have slightly different semantics. If the overload is declared as an instance method, on ++a the compiler does emit code that reassigns the resulting value to a while if the overload is declared static, the overload behaves like any other overload, skipping the otherwise implicit assignment.
:::

Unary postfix operations

ts
@operator.postfix(OP)
static __op(self: T): T { ... }

@operator.postfix(OP)
__op(): T { ... }
OP Description Notes
"++" Postfix increment Instance overload reassigns
"--" Postfix decrement Instance overload reassigns

::: tip
Overloaded postfix operations do not preserve the original value automatically.
:::

Range limits

The following range limits are present as global constants for convenience:

  • ts
    const i8.MIN_VALUE: i8 = -128
    const i8.MAX_VALUE: i8 = 127
  • ts
    const i16.MIN_VALUE: i16 = -32768
    const i16.MAX_VALUE: i16 = 32767
  • ts
    const i32.MIN_VALUE: i32 = -2147483648
    const i32.MAX_VALUE: i32 = 2147483647
  • ts
    const i64.MIN_VALUE: i64 = -9223372036854775808
    const i64.MAX_VALUE: i64 = 9223372036854775807
  • ts
    const isize.MIN_VALUE: isize // WASM32: i32.MIN_VALUE, WASM64: i64.MIN_VALUE
    const isize.MAX_VALUE: isize // WASM32: i32.MAX_VALUE, WASM64: i64.MAX_VALUE
  • ts
    const u8.MIN_VALUE: u8 = 0
    const u8.MAX_VALUE: u8 = 255
  • ts
    const u16.MIN_VALUE: u16 = 0
    const u16.MAX_VALUE: u16 = 65535
  • ts
    const u32.MIN_VALUE: u32 = 0
    const u32.MAX_VALUE: u32 = 4294967295
  • ts
    const u64.MIN_VALUE: u64 = 0
    const u64.MAX_VALUE: u64 = 18446744073709551615
  • ts
    const usize.MIN_VALUE: usize = 0
    const usize.MAX_VALUE: usize // WASM32: u32.MAX_VALUE, WASM64: u64.MAX_VALUE
  • ts
    const bool.MIN_VALUE: bool = 0
    const bool.MAX_VALUE: bool = 1
  • ts
    const f32.MIN_VALUE: f32 = -3.40282347e+38
    const f32.MAX_VALUE: f32 = 3.40282347e+38
    const f32.MIN_SAFE_INTEGER: f32 = -16777215
    const f32.MAX_SAFE_INTEGER: f32 = 16777215
    const f32.EPSILON: f32 = 1.19209290e-07
  • ts
    const f64.MIN_VALUE: f64 = -1.7976931348623157e+308
    const f64.MAX_VALUE: f64 = 1.7976931348623157e+308
    const f64.MIN_SAFE_INTEGER: f64 = -9007199254740991
    const f64.MAX_SAFE_INTEGER: f64 = 9007199254740991
    const f64.EPSILON: f64 = 2.2204460492503131e-16

Tree-shaking

Module-level

The compiler only compiles elements reachable from the entry file and ignores everything that remains unused. This is very similar to a JavaScript VM's behavior when running a JavaScript file, but unlike in TypeScript it also affects checking of types. Helps to reduce compilation times and binary sizes significantly.

Branch-level

In addition to module-level tree-shaking, the compiler ignores branches that it can prove won't be taken. Works with constants, built-ins that compile to a constant, expressions that can be precomputed to a constant, plus the following globals to detect specific compiler flags or features:

  • ts
    const ASC_TARGET: i32

    Indicates the compilation target. Possible values are 0 = JS (portable), 1 = WASM32, 2 = WASM64.

  • ts
    const ASC_NO_ASSERT: bool

    Whether --noAssert has been set.

  • ts
    const ASC_MEMORY_BASE: usize

    The value of --memoryBase.

  • ts
    const ASC_OPTIMIZE_LEVEL: i32

    The value of --optimizeLevel. Possible values are 0, 1, 2 and 3.

  • ts
    const ASC_SHRINK_LEVEL: i32

    The value of --shrinkLevel. Possible values are 0, 1 and 2.

  • ts
    const ASC_LOW_MEMORY_LIMIT: i32

    The value of --lowMemoryLimit.

  • ts
    const ASC_EXPORT_RUNTIME: i32

    Whether --exportRuntime has been set.

  • ts
    const ASC_FEATURE_SIGN_EXTENSION: bool
    const ASC_FEATURE_MUTABLE_GLOBALS: bool
    const ASC_FEATURE_NONTRAPPING_F2I: bool
    const ASC_FEATURE_BULK_MEMORY: bool
    const ASC_FEATURE_SIMD: bool
    const ASC_FEATURE_THREADS: bool
    const ASC_FEATURE_EXCEPTION_HANDLING: bool
    const ASC_FEATURE_TAIL_CALLS: bool
    const ASC_FEATURE_REFERENCE_TYPES: bool
    const ASC_FEATURE_MULTI_VALUE: bool
    const ASC_FEATURE_GC: bool
    const ASC_FEATURE_MEMORY64: bool

    Whether the respective feature is enabled.

    For example, if a library supports SIMD but also wants to provide a fallback when being compiled without SIMD support:

    ts
    if (ASC_FEATURE_SIMD) {
      // compute with SIMD operations
    } else {
      // fallback without SIMD operations
    }
  • ts
    const ASC_WASI: i32

    A constant only being defined when WASI bindings are used via import "wasi". Can be checked with isDefined(ASC_WASI).


File: src/portability.md



Portability

With AssemblyScript using a stricter variant of TypeScript's syntax with a few semantic differences, there comes the opportunity to compile the same code to JavaScript with tsc and WebAssembly with asc. The AssemblyScript compiler itself is portable code.

Portable Stdlib

Besides the full standard library, AssemblyScript provides a portable variant of the functionality that is present in both JavaScript and WebAssembly. In addition to that, the portable library lifts some of the functionality that is only available with asc to JavaScript, like the portable conversions mentioned below.

To use the portable library, add the following somewhere along your build step so the portable features are present in the environment

js
require("assemblyscript/std/portable")

and extend assemblyscript/std/portable.json instead of assemblyscript/std/assembly.json within your tsconfig.json. The AssemblyScript compiler itself is an example of how this can be done.

::: tip
The portable standard library is still pretty much a work in progress and we are extending it as we go while working on the compiler. If you need something specific, feel free to improve its definitions and feature set.
:::

Differences

There are a few semantics differences that must be taken care of.

Conversions

While asc understands the meaning of

ts
// non-portable
let someFloat: f32 = 1.5
let someInt: i32 = <i32>someFloat

and then inserts the correct conversion steps, tsc does not because all numeric types are just aliases of number. Hence, when targeting JavaScript with tsc, the above will result in

js
var someFloat = 1.5
var someInt = someFloat

which is obviously wrong. To account for this, portable conversions can be used, resulting in actually portable code. For example

ts
// portable
let someFloat: f32 = 1.5
let someInt: i32 = i32(someFloat)

will essentially result in

js
var someFloat = 1.5
var someInt = someFloat | 0

which is correct. The best way of dealing with this is asking yourself the question: What would this code do when compiled to JavaScript?

Overflows

Likewise, again because asc knows the meaning but tsc does not, overflows must be handled explicitly:

ts
// non-portable
let someU8: u8 = 255
let someOtherU8: u8 = someU8 + 1
ts
// portable
let someU8: u8 = 255
let someOtherU8: u8 = u8(someU8 + 1)

essentially resulting in

js
let someU8 = 255
let someOtherU8 = (someU8 + 1) & 0xff

API features

In JavaScript, some parts of the standard library function a little more loosely than how they would when compiling to WebAssembly. While the portable definitions try to take care of this, one example where this can happen is Map#get returning undefined when a key cannot be found in JavaScript, while resulting in an abort in WebAssembly, where it is necessary to first check that the key exists using Map#has. The best way of dealing with this is asking yourself the question: What would this code do when compiled to WebAssembly?

Limitations

In JavaScript, all numeric values are IEEE754 doubles that cannot represent the full range of values fitting in a 64-bit integer (max. safe integer is 2^53 - 1). Hence i64 and u64 are not portable and not present in std/portable. There are several ways to deal with this. One is to use an i64 polyfill like in this example.

Other than that, portable code (JavaScript) does not have a concept of memory, so there are no load and store implementations. Technically this can be polyfilled in various ways, but no default is provided since actual implementations are expected to be relatively specific (for instance: the portable compiler accesses Binaryen's memory).


File: src/quick-start.md


sidebarDepth: 0

Quick Start

Paving the way to what might be your first AssemblyScript module.

Prerequisites

The following assumes that a recent version of Node.js and its package manager npm (comes with Node.js) are installed, with the commands below executed in a command prompt. Basic knowledge about writing and working with TypeScript modules, which is very similar, is a plus.

Setting up a new project

To get started with AssemblyScript, switch to a new directory and initialize a new node module:

sh
npm init

Now install both the loader and the compiler using npm. Let's assume that the compiler is not required in production and make it a development dependency:

sh
npm install --save @assemblyscript/loader
npm install --save-dev assemblyscript

::: tip
If you need a specific version of the loader and/or the compiler, append the respective version number as usual.
:::

Once installed, the compiler provides a handy scaffolding utility to quickly set up a new AssemblyScript project, for example in the directory of the just initialized node module:

sh
npx asinit .

It automatically creates the recommended directory structure and configuration files:

text
This command will make sure that the following files exist in the project
directory '/path/to/mymodule':

  ./assembly
  Directory holding the AssemblyScript sources being compiled to WebAssembly.

  ./assembly/tsconfig.json
  TypeScript configuration inheriting recommended AssemblyScript settings.

  ./assembly/index.ts
  Example entry file being compiled to WebAssembly to get you started.

  ./build
  Build artifact directory where compiled WebAssembly files are stored.

  ./build/.gitignore
  Git configuration that excludes compiled binaries from source control.

  ./index.js
  Main file loading the WebAssembly module and exporting its exports.

  ./tests/index.js
  Example test to check that your module is indeed working.

  ./asconfig.json
  Configuration file defining both a 'debug' and a 'release' target.

  ./package.json
  Package info containing the necessary commands to compile to WebAssembly.

Do you want to proceed? [Y/n]

Once initialized, edit the sources in assembly/, tweak compiler options in asconfig.json to fit your needs, and run the build command to compile your module to WebAssembly:

sh
npm run asbuild

Next steps

Once compiled, you may run the tests in tests/index.js:

sh
npm test

add imports to the generated index.js (instantiates the module and re-exports it):

js
...
const imports = {
  "assembly/index": {
    declaredImportedFunction: function(...) { ... }
  }
};
...

and ultimately use your WebAssembly module like a normal node module:

js
const myModule = require("path/to/mymodule");
myModule.add(1, 2);

::: tip NOTE
Your module's exports only understand integers and floats for now, with strings and objects being passed as pointers, but we'll get into that later when covering the loader.
:::

Read on to learn more!


File: src/runtime.md



Runtime

::: danger WARNING
This section is OUTDATED and covers version 0.17 of the AssemblyScript compiler. It has been superseded by the new runtime introduced in 0.18 that is now described under Garbage collection.

Migration from AssemblyScript 0.17 to 0.18 requires re-evaluating uses of __retain and __release, since these have been replaced with __pin and __unpin with slightly different semantics. An object can now be exactly pinned or not pinned, but cannot be pinned multiple times, and WebAssembly exports do not have a special case of pre-retaining objects for the caller anymore. Also, runtime names have changed, and instead of distinguishing between full (runtime exported) and half (runtime not exported), there's now an option --exportRuntime to achieve the effect with any runtime variant.
:::

Documentation for AssemblyScript 0.17:

AssemblyScript's runtime takes care of all the ins and outs of memory management and garbage collection, yet the compiler lets a developer choose the ideal runtime variant for their use case.

Variants

Variant Description
full A proper memory manager and reference-counting based garbage collector, with runtime interfaces being exported to the host for being able to create managed objects externally.
half The same as full but without any exports, i.e. where creating objects externally is not required. This allows the optimizer to eliminate parts of the runtime that are not needed.
stub A minimalist arena memory manager without any means of freeing up memory again, but the same external interface as full. Useful for very short-lived programs or programs with hardly any memory footprint, while keeping the option to switch to full without any further changes. No garbage collection.
none The same as stub but without any exports, for the same reasons as explained in half. Essentially evaporates entirely after optimizations.

The default runtime included in a program is the full runtime, but deciding for another variant using the --runtime option can help to reduce binary size significantly. The full runtime adds about 2KB of additional optimized code to your module, which is not that much considering what it brings to the table, but might still be overkill for simple programs.

::: tip
Previous versions of the compiler (pre 0.7) did not include any runtime functionality by default but instead required importing an allocator and potentially an experimental tracing collector. This is not supported anymore by recent versions of the compiler.
:::

Interface

The following paragraphs are relevant in low-level code respectively when working with objects externally only. In normal high-level code, the compiler utilizes these mechanisms automatically.

Allocating managed objects

When allocating a managed object, it is necessary to also provide its unique class id so the runtime can properly recognize it. The unique id of any managed type can be obtained via idof(). Each concrete class (like String, Array, Array<f32>) has its own id. The ids of ArrayBuffer (id=0), String (id=1) and ArrayBufferView (id=2) are always the same, while all other ids are generated sequentially on first use of a class and differ between modules. Hence, it is usually necessary to export Uint8Array_ID = idof() for example when allocating one externally. The relevant interface is:

  • ts
    function __new(size: usize, id: u32): usize
    Dynamically allocates an object represented by the specified id of at least the given size in bytes and returns its address. Alignment is guaranteed to be 16 bytes to fit up to v128 values naturally. Does not zero memory.

The loader provides some additional functionality for convenience, like __newString.

Managing lifetimes

The concept is simple: If a reference to an object is established, the reference count of the object is increased by 1 (a reference is retained), and when a reference to an object is deleted, the reference count of the object is decreased by 1 (a reference is released). If the reference count of an object reaches 0, it is considered for collection and its memory ultimately returned to the memory manager for reuse. The relevant interface is:

  • ts
    function __retain(ptr: usize): usize

    Retains a reference to the object pointed to by ptr. The object doesn't become collected as long as there's at least one retained reference to it. Returns the pointer.

  • ts
    function __release(ptr: usize): void

    Releases a reference to the object pointer to by ptr. The object is considered for collection once all references to it have been released.

The compiler inserts retain and release calls automatically and this is opaque to a user on a higher level. On a lower level, for instance when dealing with managed objects externally, it is necessary to understand and adhere to the rules the compiler applies.

{% hint style="info" %}
Technically, both __retain and __release are nops when using the stubruntime, so one can either decide to skip the following for good or spend a little extra time to account for the possibility of upgrading to full later on.
{% endhint %}

Rules

  1. A reference to an object is retained when assigning it to a target (local, global, field or otherwise inserting it into a structure) of a reference type, with the exceptions stated in (3).
  2. A reference to an object is released when assigning another object to a target of a reference type previously retaining a reference to it, or if the lifetime of the local or structure currently retaining a reference to an object ends.
  3. A reference to an object is not released but remains retained when returning a reference typed object from a call (function, getter, constructor or operator overload). Instead, the caller is expected to perform the release. This also means:
    • If a reference to the object would be immediately retained by assigning the object to a target of a reference type, the compiler will not retain it twice, but skip retaining it on assignment.
    • If a reference to the object will not be immediately retained, the compiler will insert a temporary local into the current scope that autoreleases the reference to the object at the end of the scope.

::: tip
Objects created by calling __new start with a reference count of 0. This is not the case for constructors, these behave like calls. Built-ins like store<T> emit instructions directly and don't behave like calls.
:::

Working with references externally

Working with objects through imports and exports, like when using the loader, is relatively straight-forward. However, if not handled properly, the program will either leak memory, free objects prematurely or even break. So here's some advice:

  • Always __retain a reference to manually __new'ed objects and __release the reference again when done with the object.
  • Always __release the reference to an object that was a return value of a call (see above) when done with it. It is not necessary to __retain a reference to returned objects.
  • Always __retain a reference to an object read from memory, and __release the reference again when done with the object.

Working with references internally

Working with objects internally, like when creating custom standard library components or otherwise writing low-level code, requires special care because switching between pointers and reference types can become quite tricky. The internal interface also provides additional utility that is only relevant in very specific cases.

::: tip
One common point of confusion here is that the rules above operate on types, not values. Means: If the target is of a reference-type, the rules apply, but if the target is of an usize type, the rules do not apply, even if the value is a changetype(..)'d object.
:::

ts
{
  let ref = new ArrayBuffer(10) // retains (reference type)
  let buf = changetype<usize>(ref) // does not retain (usize)
  ...
  // compiler will automatically __release(ref)
}
ts
{
  let buf = changetype<usize>(new ArrayBuffer(10)) // does not retain (usize)
  // inserts a temporary, because _the object_ is not immediately assigned
  ...
  // compiler will automatically __release(theTemp)
}
ts
{
  let ref = changetype<ArrayBuffer>(__new(10, idof<ArrayBuffer>())
  // retains on ref, because after changetype an object is assigned
  ...
  // compiler will automatically __release(ref)
}

Collecting garbage

By default, the full and half runtime will automatically try to collect cyclic garbage when memory must be grown. This behavior can be disabled by setting gc.auto = false in performance critical code. Likewise, if there is a good opportunity to collect cyclic garbage at a given point in time, like if the application is idle, gc.collect() can be called to force a full garbage collection cycle. Protip: If no cyclic structures are used, no garbage must be collected.

Implementation

The memory manager used by AssemblyScript is a variant of TLSF (Two-Level Segregate Fit memory allocator) and it is accompanied by PureRC (a variant of A Pure Reference Counting Garbage Collector) with slight modifications of assumptions to avoid unnecessary work. Essentially, TLSF is responsible for partitioning dynamic memory into chunks that can be used by the various objects, while PureRC keeps track of their lifetimes.

Performance considerations

Both the TLSF memory manager and the concept of reference counting are a good fit for predictable performance scenarios. Remember: WebAssembly is about exactly that. It is not necessarily the fastest choice in every possible scenario, though.

It is also likely that our implementations are not as optimized yet as ultimately possible. If you are smarter than us, please let us know of your thoughts and findings.

Internals

If you are interested in the inner workings, the internal APIs are explained in the runtime's README file and of course in its sources - feel free to take a look!

Runtime type information \(RTTI\)

Every module using managed objects contains a memory segment with basic type information, that the loader for example uses when allocating new arrays. Internally, RTTI is used to perform dynamic instanceof checks and to determine whether a class is inherently acyclic. The memory offset of RTTI can be obtained by reading the __rtti_base global. Essentially, the compiler maps every concrete class to a unique id, starting with 0 (=ArrayBuffer), 1 (=String) and 2 (=ArrayBufferView) . For each such class, the compiler remembers the id of the respective base class, if any, and a set of flags describing the class. Flags for example contain information about key and value alignments, whether a class is managed and so on. Structure is like this:

Name Offset Type Description
#count 0 u32 Number of concrete classes
#flags[0] 4 u32 Flags describing the class with id=0
#base[0] 8 u32 Base class id of the class with id=0
#flags[1] 12 u32 ... etc. ...

Flag values are currently still in flux, but if you are interested in these, feel free to take a look at the sources.

Notes

Unlike other reference counting implementions, allocating an object starts with a reference count of 0. This is useful in standard library code where memory is first allocated and then assigned to a variable, establishing the first reference.

The compiler does some extra work to evaluate whether an object can potentially become part of a reference cycle. Any object that cannot directly or indirectly reference another object of its kind is considered inherently acyclic, so it does not have to be added to the cycle buffer for further evaluation by deferred garbage collection.

Due to the lack of random access to WebAssembly's execution stack (remember: AssemblyScript does not have a stack on its own), values returned from a function become pre-retained for the caller, expecting that it will release the reference at the end, or give the reference to its own caller. This also leads to situations where sometimes a reference must be retained on assignment, and sometimes it must not. It also leads to situations where two branches must be unified, for example if there is a pre-retained value in one arm and one that is not in the other. The compiler is taking care of these situations, but it also requires special care when dealing with the reference counting internals within the standard library.

The compiler also utilizes a concept of so called autorelease locals. Essentially, if a reference enters a block of code from somewhere else, is pre-retained but not immediately assigned, such a local is added to the current scope to postpone the necessary release until the scope is exited.

One thing not particularly ideal at this point is that each function is expected to retain the reference-type arguments it is given, and release them again at the end. This is necessary because the compiler does just a single pass and doesn't know whether a variable becomes reassigned beforehand (no SSA or similar, that's left to Binaryen in an attempt to avoid duplicating its code). It does already skip this for certain variables like this that cannot be reassigned, but there are definitely more optimization opportunities along the way. Due to AssemblyScript essentially being a compiler on top of another compiler, it is somewhat unclear however where to perform such optimizations.


File: src/status.md


sidebarDepth: 2

Status

Not all language features are equally viable to implement on top of WebAssembly's current capabilities, and while AssemblyScript is already useful today, there is also still a lot to do. Keep in mind that WebAssembly is an evolving technology, and so is AssemblyScript.

Philosophy

The original idea which made AssemblyScript attractive is that it aims to be a thin and efficient layer on top of WebAssembly with a familiar syntax, ultimately producing lean and mean binaries. This idea is composed of two components, however, that are sometimes orthogonal: Lean and mean implies that we have to stay close to WebAssembly's capabilities, i.e. not going too far already where implementations of certain language features would be inefficient or increase binary size disproportionately, while a familiar syntax naturally begs for supporting more of the original language right now.

As such, while AssemblyScript wants to stay as close as possible to being a language that feels very familiar with JavaScript and TypeScript developers, and aims to preserve compatiblity with existing tooling, it ultimately is a language compiling to WebAssembly and will prioritize language features that allow developers to write fast and small WebAssembly modules on top of the features that WebAssembly already provides.

WebAssembly features

Some crucial language features rely on future WebAssembly functionality to be efficient. The following table aims to give an overview from a WebAssembly perspective:

WebAssembly spec Engines AssemblyScript (flag) What's the plan?
✔️ Finished proposal
Import/export mutable globals ✔️ Global variable interop
BigInt integration 1 ✔️ 64-bit integer interop
Non-trapping F2I ⏳ Checked and unchecked casts
Sign-extension ⏳ sign-extension Efficient small integer casts
Multi-value Tuple return values?
🏁 Standardize the feature
Reference Types ⏳ reference-types Prerequisite for garbage collection
Bulk memory ⏳ bulk-memory Replace memcpy, memset
🔨 Implementation phase
Tail call
Fixed-width SIMD ⏳ simd Expose as built-ins; Auto-vectorize?
Multiple memories
Custom annotations
📖 Spec text available
Threads ⏳ threads Expose as built-ins; WebWorker?
ESM integration Natural web interop
Exception handling ⏳ exception-handling Implement exceptions
Function references Implement closures
Memory64 ⏳ Provide a Wasm64 target
💡 Feature proposal
Type Imports Web interop?
Garbage collection Reuse host GC; Share objects?
Interface Types Non-web interop?
Feature detection
Extended name section ⏳ Debug names for locals etc.
Flexible vectors Expose as built-ins
Call Tags
Module Linking Linking pre-compiled modules
Branch hinting likely(x), unlikely(x)?

Chrome  
Firefox  
Safari  
Node.js  
Wasmtime (1 native i64 support)

Language features

As such, certain higher-level language features still have their limitations or are not yet available. From a language perspective:

Feature What to expect?
🐤 Functional
Bootstrap The compiler can compile itself to WebAssembly, passing the test suite.
Classes and interfaces Largely implemented in linear memory. Some caveats. (needs GC 🦄)
Standard library Largely implemented in linear memory. Some caveats.
Generics Monomorphized templates for now. (maybe post-MVP GC 🦄)
Garbage collection Implemented in linear memory for now. (needs GC 🦄)
Interop with JS Enabled by the loader package. (needs Type imports / Interface Types 🦄)
🐣 Limited
Union types Nullable class types only. Can use generics with static type checks instead. (No proposal so far)
Symbols Implemented, but no deep compiler integration yet.
JSON Third-party library available.
RegExp Third-party library available.
🥚 Not implemented
Closures Perhaps implement in linear memory. (needs Function references 🦄)
Iterators Not implemented yet. Depends on symbols.
Rest parameters Perhaps implement in linear memory. (No proposal so far)
Exceptions Throwing currently aborts the program. (needs Exception handling 🦄)
Promises There is no concept of async/await yet due to the lack of an event loop. (No proposal so far)
BigInt There are no BigInts yet, but there are i64s.
🕳️ Not supported
Dynamicness AssemblyScript avoids overly dynamic JavaScript features by design.

Bootstrap

The first release able to bootstrap itself and pass the test suite is v0.18, released in January 2021. Note that the compiler is not technically "self hosted" in WebAssembly still, as it currently uses a JavaScript frontend for I/O and links to Binaryen (C++ with Emscripten), which also requires some JavaScript glue code. As such, to make the compiler work in a WebAssembly-only engine like Wasmtime, the next steps would be to work towards a WebAssembly-only build of Binaryen, and replace the I/O parts provided by asc with WASI.

Classes and interfaces

These mostly work, with a a few caveats.

  • Access modifiers like private and protected are not currently enforced. Likely to be enforced in the future.
  • Interface fields must be implemented as getters and setters. Likely to be lifted in the future.
  • Note that WebAssembly doesn't magically neglect the runtime cost of making extensive use of managed classes, so there are often faster alternatives.

Standard library

Some standard library APIs function a little different than in JavaScript to account for differences introduced by static typing or missing WebAssembly features. We are also maintaining a separate status document specific to the standard library.

Generics

AssemblyScript compiles generics to one concrete method or function per set of unique contextual type arguments, also known as monomorphisation. Implications are that a module only includes and exports concrete functions for sets of type arguments actually used and that concrete functions can be shortcutted with static type checks at compile time, which turned out to be quite useful.

  • The compiler does not currently enforce extends Y clauses on type parameters. Likely to be enforced in the future.
  • WebAssembly GC 🦄 may introduce more sophisticated mechanisms like reified generics, potentially post-MVP.
  • Concrete functions compiling to the exact same code are de-duplicated during optimization.

Garbage collection

Garbage collection is currently implemented in linear memory, independent from the host, and is best paired with the loader for interop.

Interop with JS

WebAssembly only understands numeric values as of today and cannot easily exchange objects with JavaScript. Hence, when an object is returned from WebAssembly to JavaScript, what the caller passes and the callee receives is a pointer to the object in linear memory. Note that WebAssembly does not know what to do with JavaScript objects passed to it, as the VM will implicitly convert the object to a number when it crosses the boundary, which is typically not what you want.

For example, to pass a string to a WebAssembly export, one first has to allocate the string in the WebAssembly module's linear memory, and then pass the resulting pointer to the WebAssembly export. The same is true for arrays and other objects.

For now, the loader provides the utility necessary to translate between objects in linear memory and JavaScript objects (e.g. with __newString and __getString), and our hopes are on Type Imports 🦄, Interface Types 🦄 and perhaps Garbage collection 🦄 to make interop more convenient eventually.

See also: Will interop between AssemblyScript and JavaScript become better?

Union types

WebAssembly cannot efficiently represent locals or globals of dynamic types, so union types are not supported in AssemblyScript. One can however take advantage of the fact that AssemblyScript is a static compiler, with monomorphized generics and static type checks, to achieve a similar effect:

ts
function addOrConcat<T>(a: T, b: T): T {
  return a + b; // concats if a string, otherwise adds
}

function addOrSomethingElse<T>(a: T, b: T): T {
  if (isString<T>()) {
    return "something else"; // eliminated if T is not a string
  } else {
    return a + b; // eliminated if T is a string
  }
}

Another effect of the above is that AssemblyScript does not have an any type or undefined value.

Symbols

The standard library implements Symbol, and it is possible to work with and create new symbols, but there is no deep compiler integration like registration of Symbol.iterator etc. yet.

JSON

JSON integration in the compiler itself is still an open question due to its untyped nature. May require a mix of reflection and schema-specific code generation for natural integration.

Solutions being developed by the community:

RegExp

Regular expressions have been on our todo list for quite a while. It's mostly that a good implementation becomes complicated pretty quickly with special Unicode cases, exponential behavior and so on. Also, an ideal RegExp implementation would be compatible with the ECMAScript specification, reasonably fast and integrate deeply with the compiler, so RegExp literals can be pre-compiled (to WebAssembly code or an intermediate bytecode), making it unnecessary most of the time to ship the entire engine with a module.

Solutions being developed by the community:

Closures

Closures (functions with a captured environment) are not yet supported and we are waiting for the Function References 🦄 and Garbage collection 🦄 (captured environments are GC'ed) proposals to land. However, since this is a crucial language feature, we may end up with a filler implementation using linear memory. Not available yet, though.

In the meantime we recommend to restructure code so closures are not necessary, i.e. instead of writing

ts
function computeSum(arr: i32[]): i32 {
  var sum = 0
  arr.forEach(value => {
    sum += value // fails
  })
  return sum
}

restructure to

ts
var sum: i32 // becomes a WebAssembly Global
function computeSum(arr: i32[]): i32 {
  sum = 0
  arr.forEach(value => {
    sum += value // works
  })
  return sum
}

or to

ts
function computeSum(arr: i32[]): i32 {
  var sum = 0
  for (let i = 0, k = arr.length; i < k; ++i) {
    sum += arr[i] // works
  }
  return sum
}

Iterators

It has not been attempted to implement iterators due to uncertainty how efficient iterators will be using frequent dynamic allocation. Also requires symbols for deep integration. Therefore for ... of loops are not currently supported. To work around the limitation of not having iterators, otherwise non-functional standard library APIs return an array for now:

Map\

  • ts
    function keys(): Array<K>
  • ts
    function values(): Array<V>

Set\

  • ts
    function values(): Array<T>

Rest parameters

It has not yet been attempted to implement variadic functions due to uncertainty how efficient it will be without random stack access. The risk is that doing dynamic allocations instead may introduce an unfortunate hidden cost to function calls.

In the meantime, optional function arguments, which do not have such a hidden cost, may be able to help:

ts
function handleGiven(a: i32, b: i32 = -1, c: i32 = -1): void {
  handle(a);
  if (~b) {
    handle(b);
    if (~c) {
      handle(c);
    }
  }
}

Exceptions

Exceptions are not yet supported and we are waiting for the Exception handling 🦄 proposal to land. It is not yet feasible to implement exceptions without the help of the proposal as throwing and catching an exception requires stack unwinding, so the following will currently crash the program with a call to abort("message", ...):

ts
function doThrow(): void {
  throw new Error("message")
}

In the meantime we recommend to do as they did in the olden days and return an error code or null to indicate an exception.

Promises

The concept of async execution requires an underlying concept of an event loop, which browsers and Node.js have but WebAssembly does not. In the meantime, it is recommended to stick to synchronous code within WebAssembly or call back into WebAssembly when an external async operation completes.

BigInt

We have favored the use of WebAssembly's native 64-bit integers over BigInts and their INTn notation so far, since BigInts can represent values > 64-bits and as such would have to be implemented as less-efficient heap allocated objects. There are certain use cases relying on representing more than 64-bits, of course, but we still have to figure out how to support both types without introducing conflicts.

Dynamicness

AssemblyScript intentionally avoids very dynamic JavaScript features that cannot be compiled efficiently, like for example:

  • Assigning any value to any variable.
  • Compare values of incompatible types.
  • Implicitly convert from a non-string to a string without using x.toString().
  • Assign a new property, that has not been statically declared, to a class or object.
  • Assign a class to a variable (e.g. var clazz = MyClass) since classes are static constructs without a runtime representation.
  • Patch class .prototypes since there are none.
  • Access arguments to dynamically obtain function arguments.
  • Dynamically obtain the name of a function at runtime or otherwise use reflection.

Some of these restrictions, like implicit conversion to strings when concatenating with a string, may be lifted in the future, while others, like prototypes, may never be viable in ahead-of-time compilation. For instance, some features would work in an interpreter and may become efficient with a JIT compiler, yet going down that rabbit hole runs counter to WebAssembly's, and by definition AssemblyScript's, goals.

Tooling features

Feature What to expect?
🐤 Functional
Debugging Support for debug information and source maps. (needs DWARF support)
Testing With assertions. Third-party library available.
🐣 Limited
Linting Re-used TypeScript tooling. Third-party tooling available.

Debugging

Debugging of AssemblyScript modules is not as convenient as it should be, but possible with debug information and accompanying source maps. For a better debugging experience, we may eventually want to integrate with the DWARF format used by for example LLVM, ideally through Binaryen.

Testing

The standard library provides the assert built-in, which does not decide on a particular flavor of testing, yet is often sufficient to write basic tests.

Solutions being developed by the community:

Linting

AssemblyScript piggy-backs on top of TypeScript's excellent infrastructure currently, making it trivial to get started, but could need more sophisticated checking (as one types) for where code is valid TypeScript but not valid AssemblyScript or vice-versa with // @ts-ignore. It is still an open question whether a custom language server is needed (potentially with a separate file extension), or if a plugin on top of existing TypeScript tooling would be an equally viable option. The latter would imply having to reinvent fewer wheels, like code navigation and refactoring etc., while the former may feel more polished once all the wheels have been reinvented.

Solutions being developed by the community:


File: src/transforms.md



Transforms

AssemblyScript is compiled statically, so code transformation cannot be done at runtime but must instead be performed at compile-time. To enable this, the compiler frontend (asc) provides a mechanism to hook into the compilation process before, while and after the module is being compiled.

Specifying --transform ./myTransform on the command line will load the node module pointed to by ./myTransform.

js
const { Transform } = require("assemblyscript/cli/transform")
const assemblyscript = require("assemblyscript")
class MyTransform extends Transform {
  ...
}
module.exports = MyTransform

Properties

A transform is an ES6 class/node module with the following inherited properties:

  • ts
    readonly program: Program

    Reference to the Program instance.

  • ts
    readonly baseDir: string

    Base directory used by the compiler.

  • ts
    readonly stdout: OutputStream

    Output stream used by the compiler.

  • ts
    readonly stderr: OutputStream

    Error stream uses by the compiler.

  • ts
    readonly log: typeof console.log

    Logs a message to console.

  • ts
    function writeFile(filename: string, contents: string | Uint8Array, baseDir: string): boolean

    Writes a file to disk.

  • ts
    function readFile(filename: string, baseDir: string): string | null

    Reads a file from disk.

  • ts
    function listFiles(dirname: string, baseDir: string): string[] | null

    Lists all files in a directory.

Hooks

The frontend will call several hooks, if present on the transform, during the compilation process:

  • ts
    function afterParse(parser: Parser): void

    Called when parsing is complete, before a program is initialized from the AST. Note that types are not yet known at this stage and there is no easy way to obtain them.

  • ts
    function afterInitialize(program: Program): void

    Called once the program is initialized, before it is being compiled. Types are known at this stage, respectively can be resolved where necessary.

  • ts
    function afterCompile(module: Module): void

    Called with the resulting module before it is being emitted. Useful to modify the IR before writing any output, for example to replace imports with actual functionality or to add custom sections.

Transforms are a very powerful feature, but may require profound knowledge of the compiler to utilize them to their full extent, so reading through the compiler sources is a plus.


File: src/types.md



Types

Instead of using the number type for all sorts of numeric values, AssemblyScript inherits WebAssembly's more specific integer and floating point types:

AssemblyScript Type WebAssembly type Description
i32 i32 A 32-bit signed integer.
u32 i32 A 32-bit unsigned integer.
i64 i64 A 64-bit signed integer.
u64 i64 A 64-bit unsigned integer.
f32 f32 A 32-bit float.
f64 f64 A 64-bit float.
v128 v128 A 128-bit vector 🦄.
anyref anyref An opaque host reference 🦄.
Small integer types
i8 i32 An 8-bit signed integer.
u8 i32 An 8-bit unsigned integer.
i16 i32 A 16-bit signed integer.
u16 i32 A 16-bit unsigned integer.
bool i32 A 1-bit unsigned integer.
Variable integer types
isize i32 or i64 A 32-bit signed integer in WASM32.A 64-bit signed integer in WASM64 🦄.
usize i32 or i64 A 32-bit unsigned integer in WASM32.A 64-bit unsigned integer in WASM64 🦄.
Special types
void - Indicates no return value.
auto ? Makes an educated guess. Internal only.

Type rules

AssemblyScript will complain when it sees an implicit conversion that might not actually be intended, quite similar to what a C compiler would do.

Casting

In AssemblyScript, the type assertions <T>expression and expression as T known from TypeScript become explicit type conversions, essentially telling the compiler that the conversion is intended. In addition, each of the type names mentioned above, except aliases, also act as portable conversion built-ins that can be used just like i32(expression). Using portable conversions is especially useful where the exact same code is meant to be compiled to JavaScript with the TypeScript compiler (see), that otherwise would require the insertion of asm.js-style type coercions like expression | 0.

Inference

Compared to TypeScript, type inference in AssemblyScript is limited because the type of each expression must be known in advance. This means that variable and parameter declarations must either have their type annotated or have an initializer. Without a type annotation and only an initializer, AssemblyScript will assume i32 at first and only reconsider another type if the value doesn't fit (becomes i64), is a float (becomes f64) or irrefutably has another type than these, like the type of a variable, the return type of a function or a class type. Furthermore, functions must be annotated with a return type to help the compiler make the correct decisions, for example where a literal is returned or multiple return statements are present.

Nullability

Basic types cannot be nullable, but class and function types can. Appending | null declares a nullable type.

Assignability

Assigning a value of one type to a target of another type can be performed without explicit casts where the full range of possible values can be represented in the target type, regardless of interpretation/signedness:

↱ bool i8/u8 i16/u16 i32/u32 i64/u64 f32 f64
bool ✓ ✓ ✓ ✓ ✓ ✓ ✓
i8/u8 ✓ ✓ ✓ ✓ ✓ ✓
i16/u16 ✓ ✓ ✓ ✓ ✓
i32/u32 ✓ ✓ ✓
i64/u64 ✓
f32 ✓ ✓
f64 ✓

Note that isize and usize are aliases of either i32 and u32 in WASM32 respectively i64 and u64 in WASM64 🦄.

ts
var  i8val: i8  = -128  // 0x80
var  u8val: u8  = i8val // becomes 128 (0x80)
var i16val: i16 = i8val // becomes -128 through sign-extension (0xFF80)
var u16val: u16 = i8val // becomes 65408 through masking (0xFF80)
var f32val: f32 = i8val // becomes -128.0

Comparability

Comparing two values of different types can be performed without an explicit cast under the same rules as outlined in assignability above

  1. if the comparison is absolute (==, !=)
  2. if the comparison is relative (>, <, >=, <=) and both types have the same signedness

because WebAssembly has distinct operations for signed and unsigned comparisons. The comparison uses the larger type and returns bool.

Bit shifts

The result of a bit shift (<<, >>) is the left type, with the right type implicitly converted to the left type, performing an arithmetic shift if the left type is signed and a logical shift if the left type is unsigned.

The result of an unsigned right shift (>>>) is the left type (signedness is retained), with the right type implicitly converted to the left type, but always performing a logical shift.

Note that only the log2(sizeof<T>()) least signficant bits of the shift affect the result:

Type Significant bits Example
i8 / u8 3 x & y ≡ x & (y & 7)
i16 / u16 4 x & y ≡ x & (y & 15)
i32 / u32 5 x & y ≡ x & (y & 31)
i64 / u64 6 x & y ≡ x & (y & 63)

If the left type is a float, an error is emitted.

Macro types

The following macro types provide access to related types that would otherwise be impossible to obtain.

Macro type Description
native<T> Obtains the underlying native type of T, e.g. u32 if T is a class (in WASM32).
indexof<T> Obtains the index type of a collection based on the indexed access overload.
valueof<T> Obtains the value type of a collection based on the indexed access overload.
returnof<T> Obtains the return type of a function type.

File: src/examples/arrays.md

Arrays example

Shows how to exchange and work with arrays either created in WebAssembly or in JavaScript.

Contents

  • Using the loader and the full runtime to work with managed objects.
  • Creating arrays in WebAssembly and using them in JavaScript.
  • Creating arrays in JavaScript and using them in WebAssembly.
  • Using both copies of and live views on arrays.
  • Performing unchecked accesses where the length of an array is known.
  • Pinning objects externally to prevent premature garbage collection.

Example

editor
#!optimize=speed&runtime=default&exportRuntime
/** Creates a new array and returns it to JavaScript. */
export function createArray(length: i32): Int32Array {
  return new Int32Array(length)
}

/** Randomizes the specified array's values. */
export function randomizeArray(arr: Int32Array): void {
  for (let i = 0, k = arr.length; i < k; ++i) {
    let value = i32((Math.random() * 2.0 - 1.0) * i32.MAX_VALUE)
    unchecked(arr[i] = value)
  }
}

/** Computes the sum of an array's values and returns the sum to JavaScript. */
export function sumArray(arr: Int32Array): i32 {
  let total = 0
  for (let i = 0, k = arr.length; i < k; ++i) {
    total += unchecked(arr[i])
  }
  return total
}

// We'll need the unique Int32Array id when allocating one in JavaScript
export const Int32Array_ID = idof<Int32Array>()

#!html
<textarea id="output" style="width: 100%; height: 100%" readonly></textarea>
<script>
loader.instantiate(module_wasm).then(({ exports }) => {
  const output = document.getElementById('output')

  /** Logs a message to the textarea. */
  function log(message = '') {
    output.value += `${message}\n`
  }

  // A simple example using an array created in WebAssembly.
  function example1() {
    log('=== Example1 ===')

    // Obtain the necessary runtime helpers
    const { __pin, __unpin, __getArray } = exports

    // Create a new array in WebAssembly and get a reference to it. Note that
    // the array is not reachable from within WebAssembly, only externally, so
    // we should pin it to prevent it from becoming garbage collected too early.
    let arrayPtr = __pin(exports.createArray(5))
    log(`Array pointer: ${arrayPtr}`)

    // Log its elements to make sure these are zero
    log('Initial values: ' + __getArray(arrayPtr).join(', '))

    // Randomize the array in WebAssembly and log it again
    exports.randomizeArray(arrayPtr)
    log('Randomized values: ' + __getArray(arrayPtr).join(', '))

    // Compute the array values' sum and log it. This will overflow i32 range.
    let total = exports.sumArray(arrayPtr)
    log(`Sum (likely overflown): ${total}`)

    // We are done with the array, so __unpin it so it can become collected.
    __unpin(arrayPtr)

    log()
  }
  example1()

  // A slightly more advanced example allocating the array in JavaScript instead
  // of WebAssembly, and utilizing a live view to modify it in WebAssembly memory.
  function example2() {
    log('=== Example2 ===')

    // Obtain the necessary runtime helpers
    const { __pin, __unpin, __newArray, __getArray, __getArrayView } = exports

    // Create a new array, but this time in JavaScript. Note that the array is
    // again not reachable from within WebAssembly, only externally, so we
    // should pin it to prevent it from becoming garbage collected too early.
    let arrayPtr = __pin(__newArray(exports.Int32Array_ID, [
      3, 4, 5, 6, 7, 8, 9
    ]))
    log('Array pointer: ' + arrayPtr)

    // Log its elements to make sure these are the provided values
    log('Initial values: ' + __getArray(arrayPtr).join(', '))

    // Compute the array values' sum and log it
    let total = exports.sumArray(arrayPtr)
    log('Sum: ' + total)

    // Instead of copying, let's obtain a live view on the array and modify its
    // values right in WebAssembly memory.
    let view = __getArrayView(arrayPtr)
    view.reverse()

    // Log the array's elements, now reversed
    log('Reversed values: ' + __getArray(arrayPtr).join(', '))

    // We are done with the array, so __unpin it so it can become collected.
    __unpin(arrayPtr)

    log()
  }
  example2()
})
</script>

::: tip NOTE
This example utilizes the loader to work with managed objects, hence requires --exportRuntime to be set to expose the runtime helpers to JavaScript.
:::

Resources

Further information on using the loader and the runtime helpers is available as part of the loader's and the garbage collection documentation.


File: src/examples/game-of-life.md



Game of Life example

Continuously updates a slightly modified variant of Conway's Game of Life and visualizes its state on a canvas.

Contents

  • Exporting functions from a WebAssembly module.
  • Calling functions exported from WebAssembly.
  • Importing configuration values from JavaScript.
  • Instantiating the module's memory in JavaScript and import it using --importMemory.
  • Speeding up a program by forcing helper functions in a hot path to always @inline.
  • Utilizing JavaScript's Math instead of native libm to reduce module size via --use Math=JSMath.
  • Reacting to user input by directly modifying an input image buffer.
  • Finding out about WebAssembly's unintuitive byte order.
  • And finally: Continuously updating an input to an output image buffer and rendering the output image buffer.
  • Featuring: Clicking and drawing lots of stuff.

Example

editor
#!optimize=speed&runtime=stub&importMemory&use=Math=JSMath
// Configuration imported from JS
declare const BGR_ALIVE: u32;
declare const BGR_DEAD: u32;
declare const BIT_ROT: u32;

var width: i32, height: i32, offset: i32;

/** Gets an input pixel in the range [0, s]. */
@inline
function get(x: u32, y: u32): u32 {
  return load<u32>((y * width + x) << 2);
}

/** Sets an output pixel in the range [s, 2*s]. */
@inline
function set(x: u32, y: u32, v: u32): void {
  store<u32>((offset + y * width + x) << 2, v);
}

/** Sets an output pixel in the range [s, 2*s] while fading it out. */
@inline
function rot(x: u32, y: u32, v: u32): void {
  var alpha = max<i32>((v >> 24) - BIT_ROT, 0);
  set(x, y, (alpha << 24) | (v & 0x00ffffff));
}

/** Initializes width and height. Called once from JS. */
export function init(w: i32, h: i32): void {
  width  = w;
  height = h;
  offset = w * h;

  // Start by filling output with random live cells.
  for (let y = 0; y < h; ++y) {
    for (let x = 0; x < w; ++x) {
      let c = Math.random() > 0.1
        ? BGR_DEAD  & 0x00ffffff
        : BGR_ALIVE | 0xff000000;
      set(x, y, c);
    }
  }
}

/** Performs one step. Called about 30 times a second from JS. */
export function step(): void {
  var w = width,
      h = height;

  var hm1 = h - 1, // h - 1
      wm1 = w - 1; // w - 1

  // The universe of the Game of Life is an infinite two-dimensional orthogonal grid of square
  // "cells", each of which is in one of two possible states, alive or dead.
  for (let y = 0; y < h; ++y) {
    let ym1 = y == 0 ? hm1 : y - 1,
        yp1 = y == hm1 ? 0 : y + 1;
    for (let x = 0; x < w; ++x) {
      let xm1 = x == 0 ? wm1 : x - 1,
          xp1 = x == wm1 ? 0 : x + 1;

      // Every cell interacts with its eight neighbours, which are the cells that are horizontally,
      // vertically, or diagonally adjacent. Least significant bit indicates alive or dead.
      let aliveNeighbors = (
        (get(xm1, ym1) & 1) + (get(x, ym1) & 1) + (get(xp1, ym1) & 1) +
        (get(xm1, y  ) & 1)                     + (get(xp1, y  ) & 1) +
        (get(xm1, yp1) & 1) + (get(x, yp1) & 1) + (get(xp1, yp1) & 1)
      );

      let self = get(x, y);
      if (self & 1) {
        // A live cell with 2 or 3 live neighbors rots on to the next generation.
        if ((aliveNeighbors & 0b1110) == 0b0010) rot(x, y, self);
        // A live cell with fewer than 2 or more than 3 live neighbors dies.
        else set(x, y, BGR_DEAD | 0xff000000);
      } else {
        // A dead cell with exactly 3 live neighbors becomes a live cell.
        if (aliveNeighbors == 3) set(x, y, BGR_ALIVE | 0xff000000);
        // A dead cell with fewer or more than 3 live neighbors just rots.
        else rot(x, y, self);
      }
    }
  }
}

/** Fills the row and column indicated by `x` and `y` with random live cells. */
export function fill(x: u32, y: u32, p: f64): void {
  for (let ix = 0; ix < width; ++ix) {
    if (Math.random() < p) set(ix, y, BGR_ALIVE | 0xff000000);
  }
  for (let iy = 0; iy < height; ++iy) {
    if (Math.random() < p) set(x, iy, BGR_ALIVE | 0xff000000);
  }
}

#!html
<canvas id="canvas" style="width: 100%; height: 100%; background: #000; cursor: crosshair"></canvas>
<script>
// Configuration
const RGB_ALIVE = 0xD392E6;
const RGB_DEAD  = 0xA61B85;
const BIT_ROT   = 10;

// Set up the canvas with a 2D rendering context
const canvas = document.getElementById("canvas");
const boundingRect = canvas.getBoundingClientRect();
const ctx = canvas.getContext("2d");

// Compute the size of the universe (2 pixels per cell)
const width = boundingRect.width >>> 1;
const height = boundingRect.height >>> 1;
const size = width * height;
const byteSize = (size + size) << 2; // input & output (4 bytes per cell)

canvas.width = width;
canvas.height = height;
canvas.style.imageRendering = "pixelated";
ctx.imageSmoothingEnabled = false;

// Compute the size of and instantiate the module's memory
const memory = new WebAssembly.Memory({ initial: ((byteSize + 0xffff) & ~0xffff) >>> 16 });

// Fetch and instantiate the module
loader.instantiate(module_wasm, {
  env: {
    memory
  },
  module: {
    BGR_ALIVE : rgb2bgr(RGB_ALIVE) | 1, // LSB set indicates alive
    BGR_DEAD  : rgb2bgr(RGB_DEAD) & ~1, // LSB not set indicates dead
    BIT_ROT
  }
}).then(({ exports }) => {
  // Initialize the module with the universe's width and height
  exports.init(width, height);

  var buffer = new Uint32Array(memory.buffer);

  // Update about 30 times a second
  (function update() {
    setTimeout(update, 1000 / 30);
    buffer.copyWithin(0, size, size + size);   // copy output to input
    exports.step();                            // perform the next step
  })();

  // Keep rendering the output at [size, 2*size]
  var imageData = ctx.createImageData(width, height);
  var argb = new Uint32Array(imageData.data.buffer);
  (function render() {
    requestAnimationFrame(render);
    argb.set(buffer.subarray(size, size + size)); // copy output to image buffer
    ctx.putImageData(imageData, 0, 0);            // apply image buffer
  })();

  // When clicked or dragged, fill the current row and column with random live cells
  var down = false;
  [ [canvas, "mousedown"],
    [canvas, "touchstart"]
  ].forEach(eh => eh[0].addEventListener(eh[1], e => down = true));
  [ [document, "mouseup"],
    [document, "touchend"]
  ].forEach(eh => eh[0].addEventListener(eh[1], e => down = false));
  [ [canvas, "mousemove"],
    [canvas, "touchmove"],
    [canvas, "mousedown"]
  ].forEach(eh => eh[0].addEventListener(eh[1], e => {
    if (!down) return;
    var loc;
    if (e.touches) {
      if (e.touches.length > 1) return;
      loc = e.touches[0];
    } else {
      loc = e;
    }
    const currentBoundingRect = canvas.getBoundingClientRect();
    exports.fill(
      ((loc.clientX - currentBoundingRect.left) / currentBoundingRect.width * boundingRect.width) >>> 1,
      ((loc.clientY - currentBoundingRect.top) / currentBoundingRect.height * boundingRect.height) >>> 1,
      0.5
    );
  }));
});

/** Bitshifts an RGB color to BGR instead (WebAssembly is little endian). */
function rgb2bgr(rgb) {
  return ((rgb >>> 16) & 0xff) | (rgb & 0xff00) | (rgb & 0xff) << 16;
}
</script>

::: tip NOTE
The example makes a couple assumptions. For instance, using the entire memory of the program as the image buffer as in this example is only possible because we know that no interferring static memory segments will be created, which is achieved by

  • using JavaScript's Math instead of native libm (usually adds lookup tables),
  • not using a more sophisticated runtime (typically adds bookkeeping) and
  • the rest of the example being relatively simple (i.e. no strings or similar).

As soon as these conditions are no longer met, one would instead either reserve some space by specifying a suitable --memoryBase or export a dynamically instantiated chunk of memory, like an Uint32Array, and utilize it as the input and output image buffers both in WebAssembly and in JavaScript.
:::

Running locally

Instructions are identical to those of the Mandelbrot example.


File: src/examples/interference.md



Interference example

Colin Eberhardt's and Ben Smith's WebAssembly interference effect, if it was written in AssemblyScript.

Contents

  • Exporting functions and variables from a WebAssembly module.
  • Calling functions and reading variables exported from WebAssembly.
  • Utilizing 32-bit floating point math to speed up calculations by utilizing Mathf.
  • Keeping an image buffer within the module's memory and copying it to a canvas.
  • Manually growing memory depending on the size of the viewport on the browser side.
  • And finally: Continuously updating and rendering the image buffer.

Example

::: danger EPILEPSY WARNING
A very small percentage of individuals may experience epileptic seizures when exposed to certain light patterns or flashing lights. Exposure to certain patterns or backgrounds on a computer screen may induce an epileptic seizure in these individuals. Certain conditions may induce previously undetected epileptic symptoms even in persons who have no history of prior seizures or epilepsy.

If you experience any of the following symptoms while viewing - dizziness, altered vision, eye or muscle twitches, loss of awareness, disorientation, any involuntary movement, or convulsions - IMMEDIATELY discontinue use and consult your physician.
:::

editor
#!optimize=speed&runtime=stub
var width  = 320;
var height = 200;

// Let's utilize the entire heap as our image buffer
export const offset = __heap_base;

/** Sets a single pixel's color. */
function set(x: i32, y: i32, v: f32): void {
  var vi = <i32>v;
  store<i32>(offset + ((width * y + x) << 2), ~vi << 24 | vi << 8);
}

/** Computes the distance between two pixels. */
function distance(x1: i32, y1: i32, x2: f32, y2: f32): f32 {
  var dx = <f32>x1 - x2;
  var dy = <f32>y1 - y2;
  return Mathf.sqrt(dx * dx + dy * dy);
}

/** Performs one tick. */
export function update(tick: f32): void {
  var w = <f32>width;
  var h = <f32>height;
  var hw = w * 0.5,
      hh = h * 0.5;
  var cx1 = (Mathf.sin(tick * 2) + Mathf.sin(tick      )) * hw * 0.3 + hw,
      cy1 = (Mathf.cos(tick)                            ) * hh * 0.3 + hh,
      cx2 = (Mathf.sin(tick * 4) + Mathf.sin(tick + 1.2)) * hw * 0.3 + hw,
      cy2 = (Mathf.sin(tick * 3) + Mathf.cos(tick + 0.1)) * hh * 0.3 + hh;
  var res = <f32>48 / Mathf.max(w, h);
  var y = 0;
  do {
    let x = 0;
    do {
      set(x, y, Mathf.abs(
        Mathf.sin(distance(x, y, cx1, cy1) * res) +
        Mathf.sin(distance(x, y, cx2, cy2) * res)
      ) * 120);
    } while (++x != width)
  } while (++y != height)
}

/** Recomputes and potentially grows memory on resize of the viewport. */
export function resize(w: i32, h: i32): void {
  width = w; height = h;
  // Pages are 64kb. Rounds up using mask 0xffff before shifting to pages.
  var needed = <i32>((offset + (w * h * sizeof<i32>() + 0xffff)) & ~0xffff) >>> 16;
  var actual = memory.size();
  if (needed > actual) memory.grow(needed - actual);
}

#!html
<canvas id="canvas" style="width: 100%; height: 100%; background: #aff"></canvas>
<script>
var step = 0.012;
loader.instantiate(module_wasm).then(({ exports }) => {
  const canvas = document.getElementById("canvas");
  const context = canvas.getContext("2d");

  // Upscale the image to speed up calculations
  const upscaleFactor = 4;

  var width, height, image;

  // Inform the module about the viewport's size, incl. on resize
  function onresize() {
    width = (canvas.offsetWidth / upscaleFactor) | 0;
    height = (canvas.offsetHeight / upscaleFactor) | 0;
    canvas.width = width;
    canvas.height = height;
    image = context.createImageData(width, height);
    exports.resize(width, height);
  }
  onresize();
  new ResizeObserver(onresize).observe(canvas);

  // Keep updating the image
  var tick = 0.0;
  (function update() {
    requestAnimationFrame(update);
    exports.update(tick += step);
    new Uint32Array(image.data.buffer).set(new Uint32Array(exports.memory.buffer, exports.offset, width * height));
    context.putImageData(image, 0, 0);
  })();
});
</script>

::: tip NOTE
The example makes one important assumption: Since we are not using a sophisticated runtime, we can instead repurpose the entire heap, starting at __heap_base, as our image buffer.

As soon as this condition is no longer met, one would instead either reserve some space by specifying a suitable --memoryBase or export a dynamically instantiated chunk of memory, like an Uint32Array, and utilize it as the image buffer both in WebAssembly and in JavaScript.
:::

Running locally

Set up a new AssemblyScript project as described in Quick start and copy module.ts to assembly/index.ts and index.html to the project's top-level directory. Edit the build commands in package.json to include

text
--runtime none

The example can now be compiled with

sh
npm run asbuild

To view the example, one can modify the instantiation in index.html from

js
loader.instantiate(module_wasm).then(({ exports }) => {

to

js
WebAssembly.instantiateStreaming(fetch('./build/optimized.wasm'), {
  JSMath: Math
}).then(({ exports }) => {

because using the loader is not ultimately necessary here (no managed objects are exchanged). If the loader is used instead, it will automatically provide JSMath.

Some browsers may restrict fetching local resources when just opening index.html now, but one can set up a local server as a workaround:

sh
npm install --save-dev http-server
http-server . -o -c-1

File: src/examples/mandelbrot.md



Mandelbrot example

Renders the Mandelbrot set to a canvas using 2048 discrete color values computed on the JS side.

Contents

  • Exporting functions from a WebAssembly module.
  • Calling functions exported from WebAssembly.
  • Instantiating the module's memory in JavaScript and import it using --importMemory.
  • Utilizing JavaScript's Math instead of native libm to reduce module size via --use Math=JSMath.
  • And finally: Reading and translating data from WebAssembly memory to colors rendered to a canvas.

Example

editor
#!optimize=speed&runtime=stub&importMemory&use=Math=JSMath
/** Number of discrete color values on the JS side. */
const NUM_COLORS = 2048;

/** Updates the rectangle `width` x `height`. */
export function update(width: u32, height: u32, limit: u32): void {
  var translateX = width  * (1.0 / 1.6);
  var translateY = height * (1.0 / 2.0);
  var scale      = 10.0 / min(3 * width, 4 * height);
  var realOffset = translateX * scale;
  var invLimit   = 1.0 / limit;

  var minIterations = min(8, limit);

  for (let y: u32 = 0; y < height; ++y) {
    let imaginary = (y - translateY) * scale;
    let yOffset   = (y * width) << 1;

    for (let x: u32 = 0; x < width; ++x) {
      let real = x * scale - realOffset;

      // Iterate until either the escape radius or iteration limit is exceeded
      let ix = 0.0, iy = 0.0, ixSq: f64, iySq: f64;
      let iteration: u32 = 0;
      while ((ixSq = ix * ix) + (iySq = iy * iy) <= 4.0) {
        iy = 2.0 * ix * iy + imaginary;
        ix = ixSq - iySq + real;
        if (iteration >= limit) break;
        ++iteration;
      }

      // Do a few extra iterations for quick escapes to reduce error margin
      while (iteration < minIterations) {
        let ixNew = ix * ix - iy * iy + real;
        iy = 2.0 * ix * iy + imaginary;
        ix = ixNew;
        ++iteration;
      }

      // Iteration count is a discrete value in the range [0, limit] here, but we'd like it to be
      // normalized in the range [0, 2047] so it maps to the gradient computed in JS.
      // see also: http://linas.org/art-gallery/escape/escape.html
      let colorIndex = NUM_COLORS - 1;
      let distanceSq = ix * ix + iy * iy;
      if (distanceSq > 1.0) {
        let fraction = Math.log2(0.5 * Math.log(distanceSq));
        colorIndex = <u32>((NUM_COLORS - 1) * clamp<f64>((iteration + 1 - fraction) * invLimit, 0.0, 1.0));
      }
      store<u16>(yOffset + (x << 1), colorIndex);
    }
  }
}

/** Clamps a value between the given minimum and maximum. */
function clamp<T>(value: T, minValue: T, maxValue: T): T {
  return min(max(value, minValue), maxValue);
}

#!html
<canvas id="canvas" style="width: 100%; height: 100%"></canvas>
<script>

// Set up the canvas with a 2D rendering context
const canvas = document.getElementById("canvas");
const boundingRect = canvas.getBoundingClientRect();
const ctx = canvas.getContext("2d");

// Compute the size of the viewport
const ratio  = window.devicePixelRatio || 1;
const width  = (boundingRect.width  | 0) * ratio;
const height = (boundingRect.height | 0) * ratio;
const size = width * height;
const byteSize = size << 1; // discrete color indices in range [0, 2047] (2 bytes per pixel)

canvas.width  = width;
canvas.height = height;

ctx.scale(ratio, ratio);

// Compute the size (in pages) of and instantiate the module's memory.
// Pages are 64kb. Rounds up using mask 0xffff before shifting to pages.
const memory = new WebAssembly.Memory({ initial: ((byteSize + 0xffff) & ~0xffff) >>> 16 });
const buffer = new Uint16Array(memory.buffer);
const imageData = ctx.createImageData(width, height);
const argb = new Uint32Array(imageData.data.buffer);

loader.instantiate(module_wasm, {
  env: {
    memory
  }
}).then(({ exports }) => {

  // Update state
  exports.update(width, height, 40);

  // Translate 16-bit color indices to colors
  for (let y = 0; y < height; ++y) {
    const yx = y * width;
    for (let x = 0; x < width; ++x) {
      argb[yx + x] = colors[buffer[yx + x]];
    }
  }

  // Render the image buffer.
  ctx.putImageData(imageData, 0, 0);
});

/** Computes a nice set of colors using a gradient. */
function computeColors() {
  const canvas = document.createElement("canvas");
  canvas.width = 2048;
  canvas.height = 1;
  const ctx = canvas.getContext("2d");
  const grd = ctx.createLinearGradient(0, 0, 2048, 0);
  grd.addColorStop(0.00, "#000764");
  grd.addColorStop(0.16, "#2068CB");
  grd.addColorStop(0.42, "#EDFFFF");
  grd.addColorStop(0.6425, "#FFAA00");
  grd.addColorStop(0.8575, "#000200");
  ctx.fillStyle = grd;
  ctx.fillRect(0, 0, 2048, 1);
  return new Uint32Array(ctx.getImageData(0, 0, 2048, 1).data.buffer);
}

const colors = computeColors();
</script>

::: tip NOTE
The example makes a couple assumptions. For instance, using the entire memory of the program as the image buffer as in this example is only possible because we know that no interferring static memory segments will be created, which is achieved by

  • using JavaScript's Math instead of native libm (usually adds lookup tables),
  • not using a more sophisticated runtime (typically adds bookkeeping) and
  • the rest of the example being relatively simple (i.e. no strings or similar).

As soon as these conditions are no longer met, one would instead either reserve some space by specifying a suitable --memoryBase or export a dynamically instantiated chunk of memory, like an Uint16Array, and utilize it as the color index buffer both in WebAssembly and in JavaScript.
:::

Running locally

Set up a new AssemblyScript project as described in Quick start and copy module.ts to assembly/index.ts and index.html to the project's top-level directory. Edit the build commands in package.json to include

text
--runtime none --use Math=JSMath --importMemory

The example can now be compiled with

sh
npm run asbuild

To view the example, one can modify the instantiation in index.html from

js
loader.instantiate(module_wasm, {
  env: {
    memory
  }
}).then(({ exports }) => {

to

js
WebAssembly.instantiateStreaming(fetch('./build/optimized.wasm'), {
  env: {
    memory
  },
  JSMath: Math
}).then(({ exports }) => {

because using the loader is not ultimately necessary here (no managed objects are exchanged). If the loader is used instead, it will automatically provide JSMath.

Some browsers may restrict fetching local resources when just opening index.html now, but one can set up a local server as a workaround:

sh
npm install --save-dev http-server
http-server . -o -c-1

File: src/examples/snippets.md

Snippets

Small, copy-pastable AssemblyScript examples using common syntax and patterns.

Class

An AssemblyScript snippet on using Classes, their instantiation, and their static properties.

editor
#!runtime=stub
class Animal<T> {
  static ONE: i32 = 1;
  static add(a: i32, b: i32): i32 { return a + b + Animal.ONE; }

  two: i16 = 2; // 6
  instanceSub<T>(a: T, b: T): T { return a - b + <T>Animal.ONE; } // tsc does not allow this
}

export function staticOne(): i32 {
  return Animal.ONE;
}

export function staticAdd(a: i32, b: i32): i32 {
  return Animal.add(a, b);
}

export function instanceTwo(): i32 {
  let animal = new Animal<i32>();
  return animal.two;
}

export function instanceSub(a: f32, b: f32): f32 {
  let animal = new Animal<f32>();
  return animal.instanceSub<f32>(a, b);
}


#!html
<script>
const log = console.log;
console.log = (...args) => {
  log(...args);
  let str = '';
  args.forEach(arg => {
    if (typeof arg == 'object') {
      str += `${JSON.stringify(arg, null, 2)}<br/>`;
    } else {
      str += `${arg}<br/>`;
    }
  });
  document.body.innerHTML += `<div>Log: ${str}</div>`;
}

loader.instantiate(module_wasm, { /* imports */ })
  .then(({ exports }) => {
    console.log(exports.staticOne());
    console.log(exports.staticAdd(1, 2));
    console.log(exports.instanceTwo());
    console.log(exports.instanceSub(3.0, 1.0));
  })
</script>

Extending Classes

Extending classes and general Object Orientated Programming (OOP) in AssemblyScript

editor
#!runtime=stub

class BaseClass {
  static staticProp: i32 = 24;
  instanceProp: i32;

  constructor(value: i32) {
    this.instanceProp = value;
  }

  add(a: i32, b: i32): i32 {
    return a + b;
  }
}

class ExtendedClass extends BaseClass {

  extendedProp: i32;

  constructor(extendedValue: i32) {
    super(1);

    this.extendedProp = extendedValue;
  }

  add(a: i32): i32 {
    return super.add(a, this.extendedProp + super.instanceProp);
  }
}

export function getStaticProp(): i32 {
  return ExtendedClass.staticProp;
}

export function overloadAdd(value: i32): i32 {
  let extendedClass = new ExtendedClass(value);
  return extendedClass.add(24);
}

#!html
<script>
const log = console.log;
console.log = (...args) => {
  log(...args);
  let str = '';
  args.forEach(arg => {
    if (typeof arg === 'object') {
      str += `${JSON.stringify(arg, null, 2)}<br/>`;
    } else {
      str += `${arg}<br/>`;
    }
  });
  document.body.innerHTML += `<div>Log: ${str}</div>`;
}

loader.instantiate(module_wasm, { /* imports */ })
.then(({ exports }) => {
  console.log(`getStaticProp: ${exports.getStaticProp()}`);
  console.log(`overloadAdd: ${exports.overloadAdd(24)}`);
})
</script>

Handling Null

Handling Null as a union type for optional return values and simulating try/catch for errors in AssemblyScript

editor
#!runtime=stub
class MyValue {
  value: i32;

  constructor(value: i32) {
    this.value = value;
  }
}

// Using a class type here, as some types are not nullable
function getMyValue(isAble: boolean): MyValue | null {
  let myValue = new MyValue(24);
  if (isAble) {
    return myValue;
  } else {
    return null;
  }
}

export function positiveAddWithMyValue(a: i32): i32 {
  let myValue = getMyValue(a > 0);
  if (myValue == null) {
    return -1;
  } else {
    return a + myValue.value;
  }
}

#!html
<script>
const log = console.log;
console.log = (...args) => {
  log(...args);
  let str = '';
  args.forEach(arg => {
    if (typeof arg == 'object') {
      str += `${JSON.stringify(arg, null, 2)}<br/>`;
    } else {
      str += `${arg}<br/>`;
    }
  });
  document.body.innerHTML += `<div>Log: ${str}</div>`;
}

loader.instantiate(module_wasm, { /* imports */ })
.then(({ exports }) => {
  if(exports.positiveAddWithMyValue(24) > -1) {
    console.log("Add was successful")
  } else {
    console.log("Could not add 24");
  }

  if(exports.positiveAddWithMyValue(-1) > -1) {
  console.log("Add was successful")
  } else {
  console.log("Could not add -1");
  }
});
</script>

Switch Case

Using switch case statements in AssemblyScript.

::: tip NOTE
Currently, the switch conditions (case values) are implicitly converted to u32, i.e. switching over strings or similar is not yet supported.
:::

editor
#!runtime=stub
export function switchSurprise(a: i32): i32 {
  let response = -1;

  // Using a mix of braces and not using braces
  // to show that both syntaxes are supported here.
  switch (a) {
    case 1:
      response = 100;
      break;
    case 2: {   // Cases can also use braces
      response = 200;
      break;
    }
    case 3:
      // Fall Through to the next case
    case 4:
      response = 400;
      break;
    default: {
      response = 0;
    }
  }

  return response;
}

#!html
<script>
const log = console.log;
console.log = (...args) => {
  log(...args);
  let str = '';
  args.forEach(arg => {
    if (typeof arg == 'object') {
      str += `${JSON.stringify(arg, null, 2)}<br/>`;
    } else {
      str += `${arg}<br/>`;
    }
  });
  document.body.innerHTML += `<div>Log: ${str}</div>`;
}

loader.instantiate(module_wasm, { /* imports */ })
.then(({ exports }) => {
  console.log(`switchSurprise(1) : ${exports.switchSurprise(1)}`);
  console.log(`switchSurprise(2) : ${exports.switchSurprise(2)}`);
  console.log(`switchSurprise(3) : ${exports.switchSurprise(3)}`);
  console.log(`switchSurprise(4) : ${exports.switchSurprise(4)}`);
  console.log(`switchSurprise(57) : ${exports.switchSurprise(57)}`);
});
</script>

Ternary if-else

Using Ternary if-else in AssemblyScript

editor
#!runtime=stub
export function isTrue(a: i32): i32 {
  let response = a > 0 ? 1 : 0;
  return response;
}

#!html
<script>
const log = console.log;
console.log = (...args) => {
  log(...args);
  let str = '';
  args.forEach(arg => {
    if (typeof arg == 'object') {
      str += `${JSON.stringify(arg, null, 2)}<br/>`;
    } else {
      str += `${arg}<br/>`;
    }
  });
  document.body.innerHTML += `<div>Log: ${str}</div>`;
}

loader.instantiate(module_wasm, { /* imports */ })
.then(({ exports }) => {
  console.log(`is 24 greater than 0? ${exports.isTrue(24) > 0 ? true : false}`);
  console.log(`is -3 greater than 0? ${exports.isTrue(-3) > 0 ? true : false}`);
});
</script>

File: src/stdlib/array.md



Array

A randomly accessible sequence of values of a generic type.

The Array API is very similar to JavaScript's (MDN), with the notable difference that one must make sure that there are no null values if T is a non-nullable reference type. Example:

ts
var arr = new Array<string>(10)
// arr[0]; // would error 😢
for (let i = 0; i < arr.length; ++i) {
  arr[i] = ""
}
arr[0]; // now it works 😊

Constructor

  • ts
    new Array<T>(capacity?: i32)
    Constructs a new array.

Static members

  • ts
    function isArray<U>(value: U): bool
    Tests if a value is an array.

Instance members

Fields

  • ts
    var length: i32
    The length of this array. Setting the length to a value larger than internal capacity will automatically grow the array.

Methods

  • ts
    function concat(other: Array<T>): Array<T>

    Concatenates the values of this and the other array to a new array, in this order.

  • ts
    function copyWithin(target: i32, start: i32, end?: i32): this

    Copies a region of an array's values over the respective values starting at the target location.

  • ts
    function every(fn: (value: T, index: i32, array: Array<T>) => bool): bool

    Calls the specified function with every value of the array until it finds the first value for which the function returns false. Returns true if all functions returned true or the array is empty, otherwise false.

  • ts
    function fill(value: T, start?: i32, end?: i32): this

    Replaces the values of the array from start inclusive to end exclusive in place with the specified value, returning the array.

  • ts
    function filter(fn: (value: T, index: i32, array: Array<T>) => bool): Array<T>

    Calls the specified function with every value of the array, returning a new array with all values for which the function returned true.

  • ts
    function findIndex(fn: (value: T, index: i32, array: Array<T>) => bool): i32

    Calls the specified function with every value of the array until it finds the first value for which the function returns true, returning its index. Returns -1 if that's never the case.

  • ts
    function flat(): valueof<T>[]

    Flattens an array of arrays to a one-dimensional array. null entries are ignored.

  • ts
    function forEach(fn: (value: T, index: i32, array: Array<T>) => void): void

    Calls the specified function with every value of the array.

  • ts
    function includes(value: T, fromIndex?: i32): bool

    Tests if the array includes the specified value, optionally providing a starting index.

  • ts
    function indexOf(value: T, fromIndex?: i32): i32

    Gets the first index where the specified value can be found in the array. Returns -1 if not found.

  • ts
    function join(separator?: string): string

    Concatenates all values of the array to a string, separated by the specified separator (default: ,).

  • ts
    function lastIndexOf(value: T, fromIndex?: i32): i32

    Gets the last index where the specified value can be found in the array. Returns -1 if not found.

  • ts
    function map<U>(fn: (value: T, index: i32, array: Array<T>) => U): Array<U>

    Calls the specified function with every value of the array, returning a new array of the function's return values.

  • ts
    function pop(): T

    Removes and returns the last value of the array. Modifies Array#length.

  • ts
    function push(value: T): i32

    Adds one more value to the end of the array and returns the Array's new length. Modifies Array#length.

  • ts
    function reduce<U>(
      fn: (acc: U, cur: T, idx: i32, src: Array) => U,
      initialValue: U
    ): U

    Calls the specified reducer function with each value of the array, resulting in a single return value. The respective previous reducer function's return value is remembered in acc, starting with initialValue, becoming the final return value in the process.

  • ts
    function reduceRight<U>(
      fn: (acc: U, cur: T, idx: i32, src: Array) => U,
      initialValue: U
    ): U

    Calls the specified reducer function with each value of the array, from right to left, resulting in a single return value. See Array#reduce for the reducer function's signature.

  • ts
    function reverse(): this

    Reverses an array's values in place, modifying the array before returning it.

  • ts
    function shift(): T

    Removes and returns the first value of the array. Modifies Array#length.

  • ts
    function slice(start?: i32, end?: i32): Array<T>

    Returns a shallow copy of the array's values from begin inclusive to end exclusive, as a new array. If omitted, end defaults to the end of the array.

  • ts
    function some(fn: (value: T, index: i32, array: Array<T>) => bool): bool

    Calls the specified function with every value of the array until it finds the first value for which the function returns true, returning true. Returns false otherwise or if the array is empty.

  • ts
    function sort(fn?: (a: T, b: T) => i32): this

    Sorts the values of the array in place, using the specified comparator function, modifying the array before returning it. The comparator returning a negative value means a < b, a positive value means a > b and 0 means that both are equal. Unlike in JavaScript, where an implicit conversion to strings is performed, the comparator defaults to compare two values of type T.

  • ts
    function splice(start: i32, deleteCount?: i32): Array<T>

    Removes deleteCount (defaults to all remaining) values from the array, starting at index start, modifying the array in place, returning the removed values.

  • ts
    function toString(): string

    Returns the result of Array#join().

  • ts
    function unshift(value: T): i32

    Adds one more value to the start of the array and returns the Array's new length. Modifies Array#length.


File: src/stdlib/arraybuffer.md



ArrayBuffer

A fixed-length raw binary buffer.

The ArrayBuffer API is exactly as in JavaScript (MDN).

Constructor

  • ts
    new ArrayBuffer(length: i32)
    Constructs a new buffer of the given length in bytes.

Static members

  • ts
    function isView<T>(value: T): bool
    Returns true if value is one of the buffer views, such as one of the typed arrays or a DataView.

Instance members

Fields

  • ts
    readonly byteLength: i32
    The buffer's length, in bytes.

Methods

  • ts
    function slice(begin?: i32, end?: i32): ArrayBuffer

    Returns a copy of this buffer from begin, inclusive, up to end, exclusive.

  • ts
    function toString(): string

    Returns a string representation of this buffer.


File: src/stdlib/console.md



console

An implementation of the console global on top of WASI. Requires a WASI-enabled host and opting into WASI in the entry file with

ts
import "wasi";

Static members

  • ts
    function assert<T>(assertion: T, message: string): void

    Logs message to console if assertion is false-ish.

  • ts
    function log(message: string): void

    Outputs message to the console.

  • ts
    function debug(message: string): void
    function info(message: string): void
    function warn(message: string): void
    function error(message: string): void

    Outputs message to the console, prefixed with "Debug:", "Info:", "Warning:" or "Error:" respectively.

  • ts
    function time(label: string): void

    Starts a new timer using the specified label.

  • ts
    function timeLog(label: string): void

    Logs the current value of a timer previously started with console.time.

  • ts
    function timeEnd(label: string): void

    Logs the current value of a timer previously started with console.time and discards the timer.


File: src/stdlib/crypto.md



crypto

An implementation of the crypto global on top of WASI. Requires a WASI-enabled host and opting into WASI in the entry file with

ts
import "wasi";

Static members

  • ts
    function getRandomValues(array: Uint8Array): void
    Fills array with cryptographically strong random values.

File: src/stdlib/dataview.md



DataView

An interface for working with a raw binary buffer.

The DataView API is exactly as in JavaScript (MDN).

Constructor

  • ts
    new DataView(buffer: ArrayBuffer, byteOffset?: i32, byteLength?: i32)
    Constructs a new DataView on the specified buffer and region.

Instance members

Fields

  • ts
    readonly buffer: ArrayBuffer

    The backing buffer.

  • ts
    readonly byteLength: i32

    The length of this view from the start of its buffer.

  • ts
    readonly byteOffset: i32

    The offset of this view from the start of its buffer.

Methods

  • ts
    function getFloat32(byteOffset: i32, littleEndian?: bool): f32

    Gets the 32-bit float value at the specified offset from the start of the view.

  • ts
    function getFloat64(byteOffset: i32, littleEndian?: bool): f64

    Gets the 64-bit float value at the specified offset from the start of the view.

  • ts
    function getInt8(byteOffset: i32): i8

    Gets the signed 8-bit integer value at the specified offset from the start of the view.

  • ts
    function getInt16(byteOffset: i32, littleEndian?: bool): i16

    Gets the signed 16-bit integer value at the specified offset from the start of the view.

  • ts
    function getInt32(byteOffset: i32, littleEndian?: bool): i32

    Gets the signed 32-bit integer value at the specified offset from the start of the view.

  • ts
    function getInt64(byteOffset: i32, littleEndian?: bool): i64

    Gets the signed 64-bit integer value at the specified offset from the start of the view.

  • ts
    function getUint8(byteOffset: i32, littleEndian?: bool): u8

    Gets the unsigned 8-bit integer value at the specified offset from the start of the view.

  • ts
    function getUint16(byteOffset: i32, littleEndian?: bool): u16

    Gets the unsigned 16-bit integer value at the specified offset from the start of the view.

  • ts
    function getUint32(byteOffset: i32, littleEndian?: bool): u32

    Gets the unsigned 32-bit integer value at the specified offset from the start of the view.

  • ts
    function getUint64(byteOffset: i32, littleEndian?: bool): u64

    Gets the unsigned 64-bit integer value at the specified offset from the start of the view.

  • ts
    function setFloat32(byteOffset: i32, value: f32, littleEndian?: bool): void

    Sets the 32-bit float value at the specified offset from the start of the view.

  • ts
    function setFloat64(byteOffset: i32, value: f64, littleEndian?: bool): void

    Sets the 64-bit float value at the specified offset from the start of the view.

  • ts
    function setInt8(byteOffset: i32, value: i8): void

    Sets the signed 8-bit integer value at the specified offset from the start of the view.

  • ts
    function setInt16(byteOffset: i32, value: i16, littleEndian?: bool): void

    Sets the signed 16-bit integer value at the specified offset from the start of the view.

  • ts
    function setInt32(byteOffset: i32, value: i32, littleEndian?: bool): void

    Sets the signed 32-bit integer value at the specified offset from the start of the view.

  • ts
    function setInt64(byteOffset: i32, value: i64, littleEndian?: bool): void

    Sets the signed 64-bit integer value at the specified offset from the start of the view.

  • ts
    function setUint8(byteOffset: i32, value: u8, littleEndian?: bool): void

    Sets the unsigned 8-bit integer value at the specified offset from the start of the view.

  • ts
    function setUint16(byteOffset: i32, value: u16, littleEndian?: bool): void

    Sets the unsigned 16-bit integer value at the specified offset from the start of the view.

  • ts
    function setUint32(byteOffset: i32, value: u32, littleEndian?: bool): void

    Sets the unsigned 32-bit integer value at the specified offset from the start of the view.

  • ts
    function setUint64(byteOffset: i32, value: u64, littleEndian?: bool): void

    Sets the unsigned 64-bit integer value at the specified offset from the start of the view.

  • ts
    function toString(): string

    Returns a string representation of this object.

Endianness defaults to littleEndian = false.


File: src/stdlib/date.md



Date

Represents timestamps.

The Date API is still preliminary and requires importing the Date object from the host (as Date).

Constructor

  • ts
    new Date(value: i64)
    Constructs a new date object from an UTC timestamp in milliseconds.

Static members

  • ts
    function now(): i64

    Returns the current UTC timestamp in milliseconds.

  • ts
    function UTC(
      year: i32,
      month?: i32,
      day?: i32,
      hour?: i32,
      minute?: i32,
      second?: i32,
      millisecond?: i64
    ): i64

    Returns the UTC timestamp in milliseconds of the specified date.

Instance members

  • ts
    function getTime(): i64

    Gets the UTC timestamp of this date in milliseconds.

  • ts
    function setTime(value: i64): i64

    Sets the UTC timestamp of this date in milliseconds and returns the timestamp.


File: src/stdlib/error.md



Error

Represents a runtime error.

The Error API is very similar to JavaScript's (MDN), with some properties not being implemented yet.

Variants

Variant Description
Error Represents a general error.
RangeError Represents an error where a value is not in the range of allowed values.
TypeError Represents an error where a value is not of the expected type.
SyntaxError Represents an error where the syntax of the input in invalid.

The Errorclass can also be sub-classed by forwarding message and setting the name property in the overloaded constructor.

Constructor

  • ts
    new Error(message?: string)
    Constructs a new error object.

Instance members

Fields

  • ts
    var message: string

    The message of this error.

  • ts
    var name: string

    The name of this error. In case of Error, this is "Error".

  • ts
    var stack: string

    The stack trace of this error. Not supported yet, hence an empty string.

Methods

  • ts
    function toString(): string
    Returns a string representation of this error in the form name: message.

File: src/stdlib/globals.md



Globals

In addition to the general environment with its WebAssembly-focused built-ins, the following global constants and functions are present alongside the standard library's classes.

Constants

  • ts
    const NaN: auto // f32 or f64

    Not a number as a 32-bit or 64-bit float depending on context. Compiles to a constant.

  • ts
    const Infinity: auto // f32 or f64

    Positive infinity as a 32-bit or 64-bit float depending on context. Compiles to a constant.

Functions

  • ts
    function isNaN<T>(value: T): bool

    Tests if a 32-bit or 64-bit float is NaN.

  • ts
    function isFinite<T>(value: T): bool

    Tests if a 32-bit or 64-bit float is finite, that is not NaN or +/-Infinity.

  • ts
    function parseInt(str: string, radix?: i32): i64

    Parses a string to a 64-bit integer. Returns 0 on invalid inputs.

  • ts
    function parseFloat(str: string): f64

    Parses a string to a 64-bit float. Returns NaN on invalid inputs.


File: src/stdlib/map.md



Map

A mapping of generic keys to generic values.

The Map API is very similar to JavaScript's (MDN), with the notable difference that a .get with a key that does not exist results in an error, because undefined cannot be represented. Example:

ts
var map = new Map<i32,string>()

// Because `undefined` cannot be represented if a key is not found, this will error:
var str = map.get(1) // ERROR

// The error can be avoided by first making sure that the key exists, so this works:
var str: string | null = map.has(1) ? map.get(1) : null // OK

Constructor

  • ts
    new Map<K,V>()
    Constructs a new map mapping keys of type K to values of type V.

Instance members

Fields

  • ts
    readonly size: i32
    The current number of key-value pairs in this map.

Methods

  • ts
    function clear(): void

    Clears the map, deleting all key-value pairs.

  • ts
    function delete(key: K): bool

    Deletes the key-value pair for the corresponding key. Returns true if the key did exist, otherwise false.

  • ts
    function get(key: K): V

    Gets the value corresponding to the specified key. Traps if the key does not exist because "not found" cannot be represented in all cases (use Map#has to check).

  • ts
    function has(key: K): bool

    Tests if the specified key exists.

  • ts
    function keys(): Array<K>

    Gets the keys contained in this map as an array, in insertion order. This is preliminary while iterators are not supported.

  • ts
    function set(key: K, value: V): this

    Sets the value for the specified key. Creates a new key-value pair if the key did not exist.

  • ts
    function values(): Array<V>

    Gets the values contained in this map as an array, in insertion order. This is preliminary while iterators are not supported.

  • ts
    function toString(): string

    Returns a string representation of this map.


File: src/stdlib/math.md



Math

Mathematical operations as known from JavaScript.

The Math API is very much like JavaScript's (MDN), with the notable exceptions stated above and rest parameters not being supported yet.

Variants

Math in AssemblyScript is available in multiple variants.

Variant Description
NativeMath WebAssembly implementation for f64
NativeMathf WebAssembly implementation for f32
JSMath JavaScript implementation for f64 (imported from the host)

By default, the global Math object is an alias of NativeMath and Mathf is an alias of NativeMathf .

Using NativeMath

This is the default, so no additional configuration options are required. Note, however, that Math.random needs a way to seed the random number generator in this scenario, which WebAssembly alone cannot do, hence a function env.seed() is imported from the host (see also) that must return an f64 value (the seed). The loader and import "WASI" automatically take care of providing the seed function in this scenario, but one can always implement their own, for example to make the PRNG deterministic by returning a fixed seed value.

Using JSMath

Where small module size is more important than performance, one can opt to override the default by adding --use Math=JSMath on the command line, essentially aliasing Math with JSMath instead, which maps to an import of the browser's math implementation. Naturally, this option requires importing the browser's Math object as a whole, but does not require seeding / is not seedable. The loader automatically takes care of importing the browser's math in this scenario.

Static members

The type T below substitutes either f32 or f64 depending on the implementation used.

Constants

  • ts
    const E: T

    The base of natural logarithms, e, approximately 2.718.

  • ts
    const LN2: T

    The natural logarithm of 2, approximately 0.693.

  • ts
    const LN10: T

    The natural logarithm of 10, approximately 2.302.

  • ts
    const LOG2E: T

    The base 2 logarithm of e, approximately 1.442.

  • ts
    const LOG10E: T

    The base 10 logarithm of e, approximately 0.434.

  • ts
    const PI: T

    The ratio of the circumference of a circle to its diameter, approximately 3.14159.

  • ts
    const SQRT1_2: T

    The square root of 1/2, approximately 0.707.

  • ts
    const SQRT2: T

    The square root of 2, approximately 1.414.

Functions

  • ts
    function abs(x: T): T

    Returns the absolute value of x.

  • ts
    function acos(x: T): T

    Returns the arccosine (in radians) of x.

  • ts
    function acosh(x: T): T

    Returns the hyperbolic arc-cosine of x.

  • ts
    function asin(x: T): T

    Returns the arcsine (in radians) of x.

  • ts
    function asinh(x: T): T

    Returns the hyperbolic arcsine of x.

  • ts
    function atan(x: T): T

    Returns the arctangent (in radians) of x.

  • ts
    function atan2(y: T, x: T): T

    Returns the arctangent of the quotient of its arguments.

  • ts
    function atanh(x: T): T

    Returns the hyperbolic arctangent of x.

  • ts
    function cbrt(x: T): T

    Returns the cube root of x.

  • ts
    function ceil(x: T): T

    Returns the smallest integer greater than or equal to x.

  • ts
    function clz32(x: T): T

    Returns the number of leading zero bits in the 32-bit binary representation of x.

  • ts
    function cos(x: T): T

    Returns the cosine (in radians) of x.

  • ts
    function cosh(x: T): T

    Returns the hyperbolic cosine of x.

  • ts
    function exp(x: T): T

    Returns e to the power of x.

  • ts
    function expm1(x: T): T

    Returns e to the power of x, minus 1.

  • ts
    function floor(x: T): T

    Returns the largest integer less than or equal to x.

  • ts
    function fround(x: T): T

    Returns the nearest 32-bit single precision float representation of x.

  • ts
    function hypot(value1: T, value2: T): T

    Returns the square root of the sum of squares of its arguments.

  • ts
    function imul(a: T, b: T): T

    Returns the result of the C-like 32-bit multiplication of a and b.

  • ts
    function log(x: T): T

    Returns the natural logarithm (base e) of x.

  • ts
    function log10(x: T): T

    Returns the base 10 logarithm of x.

  • ts
    function log1p(x: T): T

    Returns the natural logarithm (base e) of 1 + x.

  • ts
    function log2(x: T): T

    Returns the base 2 logarithm of x.

  • ts
    function max(value1: T, value2: T): T

    Returns the largest-valued number of its arguments.

  • ts
    function min(value1: T, value2: T): T

    Returns the lowest-valued number of its arguments.

  • ts
    function pow(base: T, exponent: T): T

    Returns base to the power of exponent.

  • ts
    function random(): T

    Returns a pseudo-random number in the range from 0.0 inclusive up to but not including 1.0.

    ::: tip
    Seeding happens automatically in common scenarios. See the notes on using NativeMath above if it doesn't.
    :::

  • ts
    function round(x: T): T

    Returns the value of x rounded to the nearest integer.

  • ts
    function sign(x: T): T

    Returns the sign of x, indicating whether the number is positive, negative or zero.

  • ts
    function signbit(x: T): bool

    Returns whether the sign bit of x is set.

  • ts
    function sin(x: T): T

    Returns the sine of x.

  • ts
    function sinh(x: T): T

    Returns the hyperbolic sine of x.

  • ts
    function sqrt(x: T): T

    Returns the square root of x.

  • ts
    function tan(x: T): T

    Returns the tangent of x.

  • ts
    function tanh(x: T): T

    Returns the hyperbolic tangent of x.

  • ts
    function trunc(x: T): T

    Returns the integer part of x by removing any fractional digits.

Considerations

The Math implementations are meant as a drop-in replacement for JavaScript's Math so sometimes mimic special JavaScript semantics, like Math.round always rounding towards +Infinity. Also, functions like Math.fround or Math.imul do not return an f32 respectively an i32 as some might expect for the same reason. Hence, depending on the use case, using WebAssembly's math instructions directly can be a worthwhile alternative where portability is not a concern.


File: src/stdlib/number.md


sidebarDepth: 2

Number

Wrappers for basic numerical values.

The Number object has been split into one class per basic WebAssembly type as well. Unlike in JavaScript, these classes cannot have actual instances, hence it works a bit different than JavaScript's (MDN).

Integers

The name Number below stands for one of the wrappers I8, I16, I32, I64, U8, U16, U32 or U64 representing their respective basic integer type T.

Static members

  • ts
    const MIN_VALUE: T

    The smallest representable value of the respective basic type. Differs from floating point values where it is the smallest representable positive value.

  • ts
    const MAX_VALUE: T

    The largest representable value of the respective basic type.

  • ts
    function parseInt(value: string, radix?: i32): T

    Parses a string to a value of the respective basic type.

Instance members

  • ts
    function toString(radix?: i32): string
    Returns the respective basic value converted to a string.

Floats

The name Number below stands for one of the wrappers F32 or F64 representing their respective basic floating point type T.

Static members

  • ts
    const EPSILON: T

    The difference between 1.0 and the smallest number larger than 1.0.

  • ts
    const MAX_VALUE: T

    The largest representable positive value by the respective basic type.

  • ts
    const MIN_VALUE: T

    The smallest representable positive value by the respective basic type.

  • ts
    const MAX_SAFE_INTEGER: T

    The largest safe integer representable by the respective basic type.

  • ts
    const MIN_SAFE_INTEGER: T

    The smallest safe integer representable by the respective basic type.

  • ts
    const POSITIVE_INFINITY: T

    Positive infinity of the respective basic type.

  • ts
    const NEGATIVE_INFINITY: T

    Negative infinity of the respective basic type.

  • ts
    const NaN: T

    NaN (Not A Number) of the respective basic type.

  • ts
    function isNan(value: T): bool

    Tests if the value is NaN.

  • ts
    function isFinite(value: T): bool

    Tests if the value is finite, that is not NaN , POSITIVE_INFINITY or NEGATIVE_INFINITY.

  • ts
    function isInteger(value: T): bool

    Tests if the value is an integer.

  • ts
    function isSafeInteger(value: T): bool

    Tests if the value is a safe integer.

  • ts
    function parseInt(value: string, radix?: i32): T

    Parses a string to an integer value of the respective basic type.

  • ts
    function parseFloat(value: string): T

    Parses a string to a float value of the respective basic type.

Instance members

  • ts
    function toString(radix?: i32): string
    Returns the respective basic value converted to a string. The radix parameter is currently ignored here.

File: src/stdlib/process.md



process

An implementation of a Node.js-like process global on top of WASI. Requires a WASI-enabled host and opting into WASI in the entry file with

ts
import "wasi";

Static members

  • ts
    const arch: string

    String representing the CPU architecture for which the binary was compiled. Either wasm32 or wasm64.

  • ts
    const platform: string

    String representing the operating system platform for which the binary was compiled. Always wasm.

  • ts
    const argv: string[]

    Array of command line arguments passed to the binary upon instantiation.

  • ts
    const env: Map<string,string>

    Map of variables in the binary's user environment.

  • ts
    var exitCode: i32

    Process exit code to use when the process exits gracefully. Defaults to 0.

  • ts
    function exit(code?: i32): void

    Terminates the process with either the given exit code, or process.exitCode if omitted.

  • ts
    const stdin: ReadableStream
    const stdout: WritableStream
    const stderr: WritableStream

    Streams connected to stdin (fd 0), stdout (fd 1) and stderr (fd 2) respectively.

  • ts
    function time(): i64

    Obtains the system's current time of day, in milliseconds since Unix epoch.

  • ts
    function hrtime(): u64

    Obtains the system's monotonic high resolution time, in nanoseconds since an arbitrary time in the past.


File: src/stdlib/set.md



Set

A set of unique generic values.

The Set API is very similar to JavaScript's (MDN), but iterators are not implemented yet.

Constructor

  • ts
    new Set<T>()
    Constructs a new set of unique value of type T.

Instance members

Fields

  • ts
    readonly size: i32
    The current number of unique values in the set.

Methods

  • ts
    function add(value: T): void

    Adds the specified value to the set. Does nothing if the value already exists.

  • ts
    function delete(value: T): bool

    Deletes the specified value. Returns true if the value was found, otherwise false.

  • ts
    function clear(): void

    Clears the set, deleting all values.

  • ts
    function has(value: T): bool

    Tests if the specified value exists in the set.

  • ts
    function values(): Array<T>

    Gets the values contained in this set as an array, in insertion order. This is preliminary while iterators are not supported.

  • ts
    function toString(): string

    Returns a string representation of this map.


File: src/stdlib/staticarray.md



StaticArray

A randomly accessible sequence of values of a generic type with a fixed length.

The StaticArray API is similar to the Array API, with the important difference that it has a fixed length that cannot change. Unlike a normal Array, a StaticArray does not have a separate backing buffer, so no level of indirection, and as such can have minimal overhead and performance characteristics very similar to arrays in C.

Constructor

  • ts
    new StaticArray<T>(length: i32)
    Constructs a new static array.

Static members

  • ts
    function fromArray<T>(source: T[]): StaticArray<T>

    Creates a static array from a normal array.

  • ts
    function concat<T>(source: StaticArray<T>, other: StaticArray<T>): StaticArray<T>

    Like the instance member concat, but taking and returning a StaticArray.

  • ts
    function slice<T>(source: StaticArray<T>, start?: i32, end?: i32): StaticArray<T>

    Like the instance member slice, but returning a StaticArray.

Instance members

Fields

  • ts
    readonly length: i32
    The fixed length of this static array.

Methods

  • ts
    function concat(other: T[]): T[]

    Concatenates the values of this static and the other normal array to a new normal array, in this order.

  • ts
    function includes(value: T, fromIndex?: i32): bool

    Tests if the array includes the specified value, optionally providing a starting index.

  • ts
    function indexOf(value: T, fromIndex?: i32): i32

    Gets the first index where the specified value can be found in the array. Returns -1 if not found.

  • ts
    function join(separator?: string): string

    Concatenates all values of the array to a string, separated by the specified separator (default: ,).

  • ts
    function lastIndexOf(value: T, fromIndex?: i32): i32

    Gets the last index where the specified value can be found in the array. Returns -1 if not found.

  • ts
    function slice(start?: i32, end?: i32): T[]

    Returns a shallow copy of this static array's values from begin inclusive to end exclusive, as a new normal array. If omitted, end defaults to the end of the array.

  • ts
    function toString(): string

    Returns the result of join().


File: src/stdlib/string.md



String

A fixed-length sequence of UTF-16 code units.

The String API works very much like JavaScript's (MDN), with the notable difference that the string type is an actual alias of String.

Static members

  • ts
    function fromCharCode(unit: i32, surr?: i32): string

    Creates a one character long string from the specified UTF-16 code units.

  • ts
    function fromCharCodes(units: u16[]): string

    Creates a string from a sequence of UTF-16 code units.

  • ts
    function fromCodePoint(code: i32): string

    Creates a one character long string from the specified Unicode code point.

  • ts
    function fromCodePoints(codes: i32[]): string

    Creates a string from a sequence of Unicode code points.

Instance members

Fields

  • ts
    readonly length: i32
    The length of the string in UTF-16 code units.

Methods

  • ts
    function charAt(pos: i32): string

    Gets the UTF-16 code unit at the specified position as a single character string. Returns "" (empty string) if out of bounds.

  • ts
    function charCodeAt(pos: i32): i32

    Gets the UTF-16 code unit at the specified position as a number. Returns -1 if out of bounds.

  • ts
    function codePointAt(pos: i32): i32

    Gets the Unicode code point at the specified (UTF-16 code unit) position as a number, possibly combining multiple successive UTF-16 code units. Returns -1 if out of bounds.

  • ts
    function concat(other: string): string

    Concatenates this string with another string, in this order, and returns the resulting string.

  • ts
    function endsWith(search: string, end?: i32): bool

    Tests if the strings ends with the specified string. If specified, end indicates the position at which to stop searching, acting as if it is the length of the string.

  • ts
    function includes(search: string, start?: i32): bool

    Tests if the string includes the search string. If specified, start indicates the position at which to begin searching.

  • ts
    function indexOf(search: string, start?: i32): i32

    Gets the first index of the specified search string within the string, or -1 if not found. If specified, start indicates the position at which to begin searching.

  • ts
    function lastIndexOf(search: string, start?: i32): i32

    Gets the last index of the specified search string within the string, or -1 if not found. If specified, pos indicates the position at which to begin searching from right to left.

  • ts
    function padStart(length: i32, pad: string): string

    Pads the string with the contents of another string, possibly multiple times, until the resulting string reaches the specified length, returning the resulting string.

  • ts
    function padEnd(length: i32, pad: string): string

    Pads the string with the contents of another string, possibly multiple times, until the resulting string reaches the specified length, returning the resulting string.

  • ts
    function repeat(count?: i32): string

    Repeats the string count times and returns the concatenated result.

  • ts
    function replace(search: string, replacement: string): string

    Replaces the first occurrence of search with replacement.

  • ts
    function replaceAll(search: string, replacement: string): string

    Replaces all occurrences of search with replacement.

  • ts
    function slice(start: i32, end?: i32): string

    Returns the region of the string from start inclusive to end exclusive, as a new string. If omitted, end defaults to the end of the string.

  • ts
    function split(separator?: string, limit?: i32): string[]

    Splits the string at each occurrence of separator and returns the result as a new array of strings that has a maximum of limit values. If limit is omitted, no limit is assumed. If separator is omitted or not present in the string, the string becomes the sole element of the array. If separator is an empty string, the split is performed between code units (not code points), potentially destroying surrogate pairs.

  • ts
    function startsWith(search: string, start?: i32): bool

    Tests if the string starts with the specified string. If specified, pos indicates the position at which to begin searching, acting as the start of the string.

  • ts
    function substring(start: i32, end?: i32): string

    Gets the part of the string in between start inclusive and end exclusive as a new string.

  • ts
    function toString(): this

    Returns the string.

  • ts
    function trim(): string

    Removes white space characters from both the start and the end of the string, returning the resulting string.

  • ts
    function trimStart(): string
    function trimLeft(): string

    Removes white space characters from the start of the string, returning the resulting string.

  • ts
    function trimEnd(): string
    function trimRight(): string

    Removes white space characters from the end of the string, returning the resulting string.

Encoding API

UTF8

When integrating with an environment that uses UTF-8, the following helpers can be used to quickly re-encode String data.

  • ts
    function String.UTF8.byteLength(str: string, nullTerminated?: bool): i32

    Calculates the byte length of the specified string when encoded as UTF-8, optionally null terminated.

  • ts
    function String.UTF8.encode(str: string, nullTerminated?: bool): ArrayBuffer

    Encodes the specified string to UTF-8 bytes, optionally null terminated.

  • ts
    function String.UTF8.encodeUnsafe(str: usize, len: i32, buf: usize, nullTerminated?: bool): usize

    Encodes the specified raw string to UTF-8 bytes, opionally null terminated. Returns the number of bytes written.

  • ts
    function String.UTF8.decode(buf: ArrayBuffer, nullTerminated?: bool): string

    Decodes the specified buffer from UTF-8 bytes to a string, optionally null terminated.

  • ts
    function String.UTF8.decodeUnsafe(
      buf: usize,
      len: usize,
      nullTerminated?: bool
    ): string

    Decodes raw UTF-8 bytes to a string, optionally null terminated.

::: tip
Note that any ArrayBuffer return value is a pointer to the buffer's data internally and thus can be passed to let's say a C-function directly. However, if the pointer is meant to live longer than the immediate external function call, the lifetime of the buffer must be tracked so it doesn't become collected prematurely with the data becoming invalid.
:::

UTF16

The following mostly exist to have a safe way to copy between Strings and ArrayBuffers, but doesn't involve a re-encoding step.

  • ts
    function String.UTF16.byteLength(str: string): i32

    Calculates the byte length of the specified string when encoded as UTF-16.

  • ts
    function String.UTF16.encode(str: string): ArrayBuffer

    Encodes the specified string to UTF-16 bytes.

  • ts
    function String.UTF16.encodeUnsafe(str: usize, len: i32, buf: usize): usize

    Encodes the specified raw string to UTF-16 bytes. Returns the number of bytes written.

  • ts
    function String.UTF16.decode(buf: ArrayBuffer): string

    Decodes the specified buffer from UTF-16 bytes to a string.

  • ts
    function String.UTF16.decodeUnsafe(buf: usize, len: usize): string

    Decodes raw UTF-16 bytes to a string.

Considerations

Also see the notes on string interoperability.


File: src/stdlib/symbol.md



Symbol

A primitive data type representing a unique global symbol.

The symbol type is very similar to JavaScript's (MDN), but is not deeply integrated with the compiler yet.

Construction

  • ts
    function Symbol(description?: string): symbol
    Creates a new unique symbol. Note that this is not a constructor (symbols are primitive data types) so cannot be invoked with new.

Static members

  • ts
    function for(key: string): symbol

    Obtains the existing unique symbol for the specified global key or creates a new unique symbol if the key does not exist.

  • ts
    function keyFor(sym: symbol): string | null

    Obtains the global key of the specified unique symbol, if any.

Instance members

  • ts
    function toString(): string
    Returns a string representation of the symbol of the form "Symbol(key?)".