AssemblyScript defines a subset of TypeScript that it compiles to WebAssembly. The compiler itself is written in TypeScript and no binary dependencies are required to get started. Under the hood, it rewires TypeScript's compiler API to Binaryen's compiler backend.
Be warned that this is an early prototype and that it cannot do anything useful yet.
An initial playground to mess with the current state of the compiler is available at dcode.io/AssemblyScript.
export function add(a: int, b: double): short {
return (a + (b as int)) as short;
}
Compiles to:
(module
(type $iFi (func (param i32 f64) (result i32)))
(memory $0 256)
(export "memory" (memory $0))
(export "add" (func $add))
(func $add (type $iFi) (param $0 i32) (param $1 f64) (result i32)
(return
(i32.shl
(i32.shr_s
(i32.add
(get_local $0)
(i32.trunc_s/f64
(get_local $1)
)
)
(i32.const 16)
)
(i32.const 16)
)
)
)
)
See the pre-configured example project for a quickstart.
The package also provides a convenient stand-alone loader component to run and work with compiled WebAssembly modules:
import load from "assemblyscript/loader"; // JS: var load = require("assemblyscript/loader").load;
load("path/to/module.wasm").then(module => {
...
});
An AssemblyScript program is valid TypeScript syntactically, but not necessarily semantically.
WebAssembly-specific types are obtained by referencing assembly.d.ts:
Type | Alias | Native type | sizeof | Description |
---|---|---|---|---|
sbyte |
int8 |
i32 | 1 | An 8-bit signed integer. |
byte |
uint8 |
i32 | 1 | An 8-bit unsigned integer. |
short |
int16 |
i32 | 2 | A 16-bit signed integer. |
ushort |
uint16 |
i32 | 2 | A 16-bit unsigned integer. |
int |
int32 |
i32 | 4 | A 32-bit signed integer. |
uint |
uint32 |
i32 | 4 | A 32-bit unsigned integer. |
long |
int64 |
i64 | 8 | A 64-bit signed integer. |
ulong |
uint64 |
i64 | 8 | A 64-bit unsigned integer. |
uintptr |
- | i32 / i64 | 4 / 8 | A 32-bit unsigned integer when targeting 32-bit WebAssembly. A 64-bit unsigned integer when targeting 64-bit WebAssembly. |
float |
float32 |
f32 | 4 | A 32-bit float. |
double |
float64 |
f64 | 8 | A 64-bit float. |
bool |
- | i32 | 1 | A 1-bit unsigned integer. |
void |
- | none | - | No return type |
While generating a warning to avoid type confusion, the JavaScript types number
and boolean
resolve to double
and bool
respectively.
WebAssembly-specific operations are available as built-in functions that translate to the respective opcode directly:
- rotl(value:
int
, shift:int
):int
Performs the sign-agnostic rotate left operation on a 32-bit integer. - rotll(value:
long
, shift:long
):long
Performs the sign-agnostic rotate left operation on a 64-bit integer. - rotr(value:
int
, shift:int
):int
Performs the sign-agnostic rotate right operation on a 32-bit integer. - rotrl(value:
long
, shift:long
):long
Performs the sign-agnostic rotate right operation on a 64-bit integer. - clz(value:
int
):int
Performs the sign-agnostic count leading zero bits operation on a 32-bit integer. All zero bits are considered leading if the value is zero. - clzl(value:
long
):long
Performs the sign-agnostic count leading zero bits operation on a 64-bit integer. All zero bits are considered leading if the value is zero. - ctz(value:
int
):int
Performs the sign-agnostic count tailing zero bits operation on a 32-bit integer. All zero bits are considered trailing if the value is zero. - ctzl(value:
long
):long
Performs the sign-agnostic count trailing zero bits operation on a 64-bit integer. All zero bits are considered trailing if the value is zero. - popcnt(value:
int
):int
Performs the sign-agnostic count number of one bits operation on a 32-bit integer. - popcntl(value:
long
):long
Performs the sign-agnostic count number of one bits operation on a 64-bit integer. - abs(value:
double
):double
Computes the absolute value of a 64-bit float. - absf(value:
float
):float
Computes the absolute value of a 32-bit float. - ceil(value:
double
):double
Performs the ceiling operation on a 64-bit float. - ceilf(value:
float
):float
Performs the ceiling operation on a 32-bit float. - floor(value:
double
):double
Performs the floor operation on a 64-bit float. - floorf(value:
float
):float
Performs the floor operation on a 32-bit float. - sqrt(value:
double
):double
Calculates the square root of a 64-bit float. - sqrtf(value:
float
):float
Calculates the square root of a 32-bit float. - trunc(value:
double
):double
Rounds to the nearest integer towards zero of a 64-bit float. - truncf(value:
float
):float
Rounds to the nearest integer towards zero of a 32-bit float. - nearest(value:
double
):double
Rounds to the nearest integer tied to even of a 64-bit float. - nearestf(value:
float
):float
Rounds to the nearest integer tied to even of a 32-bit float. - min(left:
double
, right:double
):double
Determines the minimum of two 64-bit floats. If either operand isNaN
, returnsNaN
. - minf(left:
float
, right:float
):float
Determines the minimum of two 32-bit floats. If either operand isNaN
, returnsNaN
. - max(left:
double
, right:double
):double
Determines the maximum of two 64-bit floats. If either operand isNaN
, returnsNaN
. - maxf(left:
float
, right:float
):float
Determines the maximum of two 32-bit floats. If either operand isNaN
, returnsNaN
. - copysign(x:
double
, y:double
):double
Composes a 64-bit float from the magnitude ofx
and the sign ofy
. - copysignf(x:
float
, y:float
):float
Composes a 32-bit float from the magnitude ofx
and the sign ofy
. - reinterpreti(value:
float
):int
Reinterprets the bits of a 32-bit float as a 32-bit integer. - reinterpretl(value:
double
):long
Reinterprets the bits of a 64-bit float as a 64-bit integer. - reinterpretf(value:
int
):float
Reinterprets the bits of a 32-bit integer as a 32-bit float. - reinterpretd(value:
long
):double
Reinterprets the bits of a 64-bit integer as a 64-bit double. - current_memory():
int
Returns the current memory size in units of pages. One page is 64kb. - grow_memory(value:
uint
):int
Grows linear memory by a given unsigned delta of pages. One page is 64kb. Returns the previous memory size in units of pages or-1
on failure. - sizeof<
T
>():uintptr
Returns the byte size of the specified core or class type. Compiles to a constant.
The following built-in constants are present as immutable globals (note that optimizers might inline them):
- NaN:
double
NaN (not a number) as a 64-bit float. - NaNf:
float
NaN (not a number) as a 32-bit float. - Infinity:
double
Positive infinity as a 64-bit float. - Infinityf:
float
Positive infinity as a 32-bit float.
By default (i.e. if the --nolib
option isn't set), standard memory management routines based on dlmalloc and musl will be linked statically and exported to the embedder:
- malloc(size:
uintptr
):uintptr
Allocates a chunk of memory of the specified size and returns a pointer to it. - free(ptr:
uintptr
):void
Frees a previously allocated chunk of memory by its pointer. - memcpy(dest:
uintptr
, src:uintptr
, size:uintptr
):uintptr
Copies data from one chunk of memory to another. - memset(dest:
uintptr
, c:int
, size:uintptr
):uintptr
Sets a chunk of memory to the provided valuec
. Usually used to reset it to all0
s. - memcmp(vl:
uintptr
, vr:uintptr
, n:uintptr
):int
Compares a chunk of memory to another. Returns0
if both are equal, otherwisevl[i] - vr[i]
at the first difference's byte offseti
.
Linking in memory management routines adds about 11kb to a module. Once WebAssembly exposes the garbage collector natively, there'll be other options as well. Note that the new
operator depends on malloc
and will break when --nolib
is specified (and no other malloc
is present). Also note that calling grow_memory
where malloc
is present will most likely break malloc
as it expects contiguous memory.
Type coercion requires an explicit cast where precision or signage is lost respectively is implicit where it is maintained. For example, to cast a double
to an int
:
function example(value: double): int {
return value as int; // translates to the respective opcode
}
Global WebAssembly imports can be declare
d anywhere while WebAssembly exports are export
ed from the entry file (the file specified when calling asc
or Compiler.compile
). Aside from that, imports and exports work just like in TypeScript.
// entry.ts
import { myOtherExportThatDoesntBecomeAWebAssemblyExport } from "./imported";
declare function myImport(): void;
export function myExport(): void {
myOtherExportThatDoesntBecomeAWebAssemblyExport();
}
Currently, imports can also be pulled from different namespaces by separating the namespace and the function with a $
character.
declare function Math$random(): double;
Naming a function start
with no arguments and a void
return type will automatically make it the start function that is being called on load even before returning to the embedder.
function start(): void {
...
}
-
Compiler.compileFile(filename:
string
, options?:CompilerOptions
):binaryen.Module | null
Compiles the specified entry file to a WebAssembly module. Returnsnull
on failure. -
Compiler.compileString(source:
string
, options?:CompilerOptions
):binaryen.Module | null
Compiles the specified entry file source to a WebAssembly module. Returnsnull
on failure. -
Compiler.lastDiagnostics:
typescript.Diagnostic[]
Contains the diagnostics generated by the last invocation ofcompilerFile
orcompileString
. -
CompilerOptions
AssemblyScript compiler options.- uintptrSize:
number
Specifies the byte size of a pointer. 32-bit WebAssembly expects4
, 64-bit WebAssembly expects8
. Defaults to4
. - noLib:
boolean
Specifies whether the standard library (malloc, free, etc.) shall be excluded. Defaults tofalse
. - silent:
boolean
Specifies that compilation shall be performed in silent mode without writing to stderr. Defaults tofalse
. - treeShaking:
boolean
Disables built-in tree-shaking if set tofalse
. Defaults totrue
.
- uintptrSize:
import { Compiler, typescript } from "assemblyscript";
const module = Compiler.compileString(`
export function add(a: int, b: int): int {
return a + b;
}
`, {
uintptrSize: 4,
silent: true
});
console.error(typescript.formatDiagnostics(Compiler.lastDiagnostics));
if (!module)
throw Error("compilation failed");
module.optimize();
if (!module.validate())
throw Error("validation failed");
const textFile = module.emitText();
const wasmFile = module.emitBinary();
...
The command line compiler asc
works similar to TypeScript's tsc
:
Syntax: asc [options] [file ...]
Options:
-o, --out, --outFile Specifies the output file name.
-v, --validate Validates the module.
-O, --optimize Runs optimizing binaryen IR passes.
-t, --text Emits text format instead of a binary.
That's it for now. Feel free to experiment. PRs welcome!
License: Apache License, Version 2.0