Package Exports
- assemblyscript
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (assemblyscript) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
AssemblyScript defines a subset of TypeScript that it compiles to WebAssembly. It aims to provide everyone with an existing background in TypeScript and standard JavaScript-APIs with a comfortable way to compile to WebAssembly, eliminating the need to switch between languages or to learn new ones just for this purpose.
Try it out in your browser: dcode.io/AssemblyScript
Contents
How it works
A few insights to get an initial idea.What to expect
General remarks on design decisions and trade-offs.Example
Basic examples to get you started.Usage
An introduction to the environment and its provided functionality.Command line
How to use the command line utility.API
How to use the API programmatically.Additional documentation
A list of available documentation resources.Building
How to build the compiler and its components yourself.
How it works
Under the hood, AssemblyScript rewires TypeScript's compiler API to Binaryen's compiler backend. The compiler itself is written in (and based upon) TypeScript and no binary dependencies are required to get started.
Every AssemblyScript program is valid TypeScript syntactically, but not necessarily semantically. The definitions required to start developing in AssemblyScript are provided by assembly.d.ts. See also: Usage
The compiler is able to produce WebAssembly binaries (.wasm) as well as their corresponding text format (.wast). Both Binaryen's s-expression format and, with a little help of WABT, official linear text format are supported. See also: CLI
What to expect
The most prominent difference of JavaScript and any strictly typed language is that, in TypeScript/JavaScript, a variable can reference a value of any type. This implies that a JavaScript VM has to conduct additional book-keeping of a value's type in addition to its value and that it has to perform additional checks whenever a variable is accessed. Modern JavaScript VMs shortcut the overhead introduced by this and similar dynamic features by generating case-specific code based on statistical information collected just in time, effectively reducing the amount of checks to perform implicitly and thus speeding up execution significantly. Similarily, developers shortcut the overhead of remembering each variable's type by using TypeScript. The combination of both also makes a good match because it potentially aids the JIT compiler.
Nonetheless, TypeScript isn't a strictly typed language after all because it allows specific constructs to resort to JavaScript's dynamic features. For example, TypeScript allows annotating function parameters as omittable (i.e. someParameter?: number
), effectively resulting in a union type number | undefined
at runtime, just like it also allows declaring union types explicitly. Conceptionally, these constructs are incompatible with a strict, AOT-compiled type system unless relatively expensive workarounds are introduced. Hence...
TL;DR
Instead of trying to mimic TypeScript/JavaScript as closely as possible at the expense of performance (recap that: slower than similar code running in a JIT-compiling VM), AssemblyScript tries to support TypeScript features as closely as reasonable, not supporting certain JavaScript-specific dynamic constructs intentionally:
- All types must be annotated to avoid possibly unwanted implicit type conversions
- Optional function parameters require an initializer expression
- Union types,
any
andundefined
are not supported by design - The result of logical
&&
/||
expressions is alwaysbool
Also note that AssemblyScript is a rather new and ambitious project developed by one guy and a hand full of occasional contributors. Expect bugs and breaking changes. Prepare to fix stuff yourself and to send a PR for it, unless you like the idea enough to consider sponsoring development.
Example
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.shr_s
(i32.shl
(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.
Running a module
The stand-alone loader component provides an easy way to run and work with compiled WebAssembly modules:
$> npm install assemblyscript-loader
import load from "assemblyscript-loader"; // JS: var load = require("assemblyscript-loader").load;
load("path/to/module.wasm", {
imports: {
...
}
}).then(module => {
...
// i.e. call module.exports.main()
});
Usage
$> npm install assemblyscript --save-dev
The environment is configured by either referencing assembly.d.ts directly or by using a tsconfig.json
that simply extends tsconfig.assembly.json, like so:
{
"extends": "./node_modules/assemblyscript/tsconfig.assembly.json",
"include": [
"./*.ts"
]
}
The tsconfig.json
-approach is recommended to inherit other important settings as well.
Once configured, the following AssemblyScript-specific types become available:
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. - unreachable():
void
Emits an unreachable operation that results in a runtime error when executed.
The following AssemblyScript-specific operations are implemented as built-ins as well:
- sizeof<
T
>():uintptr
Determines the byte size of the specified core or class type. Compiles to a constant. - unsafe_cast<
T1
,T2
>(value:T1
):T2
Casts a value of typeT1
to a value of typeT2
. Useful for casting classes to pointers and vice-versa. Does not perform any checks. - isNaN(value:
double
):bool
Tests if a 64-bit float is a NaN. - isNaNf(value:
float
):bool
Tests if a 32-bit float is a NaN. - isFinite(value:
double
):bool
Tests if a 64-bit float is finite. - isFinitef(value:
float
):bool
Tests if a 32-bit float is finite.
These 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, standard memory management routines based on dlmalloc and musl will be linked statically and can be configured to be 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 --no-malloc
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.compileFile
). 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 {
...
}
Command line
The command line compiler asc
works similar to TypeScript's tsc
:
Syntax: asc [options] entryFile
Options:
--config, -c Specifies a JSON configuration file with command line options.
Will look for 'asconfig.json' in the entry's directory if omitted.
--outFile, -o Specifies the output file name. Emits text format if ending with .wast
(sexpr) or .wat (linear). Prints to stdout if omitted.
--optimize, -O Runs optimizing binaryen IR passes.
--validate, -v Validates the module.
--quiet, -q Runs in quiet mode, not printing anything to console.
--target, -t Specifies the target architecture:
wasm32 Compiles to 32-bit WebAssembly [default]
wasm64 Compiles to 64-bit WebAssembly
--memoryModel, -m Specifies the memory model to use / how to proceed with malloc etc.:
malloc Bundles malloc etc. [default]
exportmalloc Bundles malloc etc. and exports each
importmalloc Imports malloc etc. from 'env'
bare Excludes malloc etc. entirely
--textFormat, -f Specifies the format to use for text output:
sexpr Emits s-expression syntax (.wast) [default]
linear Emits official linear syntax (.wat)
Text format only is emitted when used without --textFile.
--textFile Can be used to save text format alongside a binary in one command.
--help, -h Displays this help message.
A configuration file (usually named asconfig.json
) using the long option keys above plus a special key entryFile
specifying the path to the entry file can be used to reuse options between invocations.
API
It's also possible to use the API programmatically:
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.- silent:
boolean
Whether compilation shall be performed in silent mode without writing to console. Defaults tofalse
. - treeShaking:
boolean
Whether to use built-in tree-shaking. Defaults totrue
. Disable this when building a dynamically linked library. - target:
CompilerTarget | string
Specifies the target architecture. Defaults toCompilerTarget.WASM32
. - memoryModel:
CompilerMemoryModel | string
Specifies the memory model to use. Defaults toCompilerMemoryModel.MALLOC
.
- silent:
CompilerTarget
Compiler target.- WASM32
32-bit WebAssembly target using uint pointers. - WASM64
64-bit WebAssembly target using ulong pointers.
- WASM32
CompilerMemoryModel
Compiler memory model.- BARE
Does not bundle any memory management routines. - MALLOC
Bundles malloc, free, etc. - EXPORT_MALLOC
Bundles malloc, free, etc. and exports each to the embedder. - IMPORT_MALLOC
Imports malloc, free, etc. as provided by the embedder.
- BARE
Example
import { Compiler, CompilerTarget, CompilerMemoryModel, typescript } from "assemblyscript";
const module = Compiler.compileString(`
export function add(a: int, b: int): int {
return a + b;
}
`, {
target: CompilerTarget.WASM32,
memoryModel: CompilerMemoryModel.MALLOC,
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();
...
module.dispose();
Remember to call binaryen.Module#dispose()
once you are done with a module to free its resources. This is necessary because binaryen.js has been compiled from C/C++ and doesn't provide automatic garbage collection.
Additional documentation
AssemblyScript
WebAssembly
Building
Clone the GitHub repository and install the development dependencies:
$> git clone https://github.com/dcodeIO/AssemblyScript.git
$> cd AssemblyScript
$> npm install
Afterwards, to build the distribution files to dist/, run:
$> npm run build
To build the documentation to docs/api/, run:
$> npm run docs
Running the tests (ideally on node.js >= 8):
$> npm test
License: Apache License, Version 2.0