| description | Are we there yet? |
|---|---|
| sidebarDepth | 2 |
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.
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.
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 integration1 | βοΈ | 64-bit integer interop | |
| Sign-extension | βοΈ | Efficient small integer casts | |
| Non-trapping F2I | π nontrapping-f2i |
Checked and unchecked casts | |
| Bulk memory | π bulk-memory |
Replace memcpy, memset |
|
| Fixed-width SIMD | π simd |
Expose as built-ins; Auto-vectorize? | |
| Reference Types | π¨ reference-types |
Prerequisite for garbage collection | |
| Multi-value | Tuple return values | ||
| π Standardize the feature | |||
| π¨ Implementation phase | |||
| Tail call | |||
| Multiple memories | |||
| Memory64 | π¨ | Provide a Wasm64 target | |
| Exception handling | π¨ exception-handling |
Implement exceptions | |
| π Spec text available | |||
| Threads | π¨ threads |
Expose as built-ins; WebWorker? | |
| ESM integration | Natural web interop | ||
| Function references | Implement closures | ||
| Branch Hinting | likely(x) / unlikely(x) hints |
||
| Instrument Tracing | debugger statement? |
||
| π‘ Feature proposal | |||
| Type Imports | Web interop? | ||
| Garbage collection | Reuse host GC; Share objects? | ||
| 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 | ||
| Extended Constant Expressions | Inline more global initializers | ||
| Relaxed SIMD | Expose as built-ins | ||
| Stack Switching | async / await |
||
| Constant Time | Expose as built-ins / hint | ||
| Unsure | |||
| WASI2 | π¨ | Not a good fit (double-polyfill) | |
| Interface Types3 | No DOMString support in MVP | ||
| Component Model | Requires Interface Types | ||
| Module Linking | Requires Interface Types |
Chrome Β Firefox Β Safari Β Node.js Β Wasmtime Β Wasmer Β (1 native support in non-JS hosts)
2 WASI is not a good fit for AssemblyScript's use case currently and we would appreciate more cooperation.
3 The Wasm CG has decided that DOMString support (ours and JS's String) is out of scope of Interface Types.
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 / Reference 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. |
| Object literals | Implemented, but with limitations. |
| JSON | Third-party library available. |
| RegExp | Third-party library available. |
| Date | 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. |
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.
These mostly work, with a a few caveats.
- Access modifiers like
privateandprotectedare 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.
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.
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 Yclauses 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 is currently implemented in linear memory, independent from the host, and is best paired with the loader for interop.
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 Reference Types π¦, Type Imports π¦ and perhaps Garbage collection π¦ to make interop more convenient eventually.
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.
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.
Object literals can be used to create object instances as an alternative to the use of constructors.
class MyClass {
propOne: i32;
propTwo: i32;
}
var myInstance: MyClass = { propOne: 2, propTwo: 3 };Objects created via literals can have methods and default property values, but may not have a constructor.
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:
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:
The JavaScript Date API is pretty poorly constructed, with limited support for timezones. AssemblyScript has a very limited Date implementation that just provides UTC methods and functionality.
A new date, time and calendar API, called Temporal, is being actively developed and is currently at stage 3 of the TC39 process. This is a much better API for AssemblyScript to adopt. A solution is being developed by the community:
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
}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:
-
function keys(): Array<K>
-
function values(): Array<V>
-
function values(): Array<T>
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 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.
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.
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.
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
argumentsto 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.
| 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 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.
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:
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: