# 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 (opens new window) 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 integration1 ✔️ 64-bit integer interop
Non-trapping F2I 🏁 nontrapping-f2i Checked and unchecked casts
Sign-extension 🏁 sign-extension Efficient small integer casts
Reference Types 🏁 reference-types Prerequisite for garbage collection
Bulk memory 🏁 bulk-memory Replace memcpy, memset
Multi-value Tuple return values
🏁 Standardize the feature
Fixed-width SIMD 🔨 simd Expose as built-ins; Auto-vectorize?
🔨 Implementation phase
Tail call
Multiple memories
Memory64 🔨 Provide a Wasm64 target
📖 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
Branch Hinting likely(x) / unlikely(x) hints
💡 Feature proposal
Type Imports Web interop?
Garbage collection Reuse host GC; Share objects?
Interface Types Non-web interop?
Feature detection Detect available features
Extended name section 🔨 Debug names for locals etc.
Flexible vectors Expose as built-ins
Call Tags Speed up indirect calls
Module Linking Linking pre-compiled modules
Extended Constant Expressions Inline more global initializers
Relaxed SIMD Expose as built-ins
Chrome   Firefox   Safari   Node.js   Wasmtime   Wasmer   (1 native support in non-JS hosts)

# 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 (opens new window) 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 (opens new window). 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:

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

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

restructure to

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

or to

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<K,V>

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

# Set<T>

  • 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:

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", ...):

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 (opens new window) 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: