AssemblyScript / assemblyscript
- пятница, 8 сентября 2017 г. в 03:14:50
A subset of TypeScript that compiles to WebAssembly.
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!
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.
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. Both Binaryen's s-expression format (.wast) and, with a little help of WABT, official linear text format (.wat) are supported. See also: CLI
The most prominent difference of JavaScript and any strictly typed language is that, in JavaScript, a variable can reference a value of any type. This implies that a JavaScript execution environment has to emit additional runtime 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 (JIT), speeding up execution significantly. Similarily, developers shortcut the overhead of remembering each variable's type by using TypeScript. The combination of both also makes for a good match because it potentially aids the JIT compiler.
Because it has the ability to fall back to dynamic JavaScript features, TypeScript isn't a strictly typed language after all. For example, TypeScript supports omittable (i.e. someParameter?: number
) function parameters resulting in a union type number | undefined
at runtime, just like it also allows declaring union types explicitly. These constructs are incompatible with a strict, ahead of time (AOT) compiled type system unless additional runtime checks are emitted that'd usually execute slower than similar code running in a VM that has the ability to perform optimizations at runtime. Hence...
Instead of reimplementing TypeScript as closely as possible at the expense of performance, AssemblyScript tries to support its features as closely as reasonable while not supporting certain dynamic constructs intentionally:
classType | null
representing a nullable), any
and undefined
are not supported by design&&
/ ||
expressions is always bool
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.
export function add(a: i32, b: i32): i16 {
return (a + (b as i32)) as i16;
}
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 examples repository for more.
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()
});
$> 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 | Aliases | Native type | sizeof | Description |
---|---|---|---|---|
i8 |
int8 , sbyte |
i32 | 1 | An 8-bit signed integer. |
u8 |
uint8 , byte |
i32 | 1 | An 8-bit unsigned integer. |
i16 |
int16 , short |
i32 | 2 | A 16-bit signed integer. |
u16 |
uint16 , ushort |
i32 | 2 | A 16-bit unsigned integer. |
i32 |
int32 , int |
i32 | 4 | A 32-bit signed integer. |
u32 |
uint32 , uint |
i32 | 4 | A 32-bit unsigned integer. |
i64 |
int64 , long |
i64 | 8 | A 64-bit signed integer. |
u64 |
uint64 , ulong |
i64 | 8 | A 64-bit unsigned integer. |
usize |
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. |
f32 |
float32 , float |
f32 | 4 | A 32-bit float. |
f64 |
float64 , double |
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 f64
and bool
respectively.
WebAssembly-specific operations are available as built-in functions that translate to the respective opcode directly:
i32
, shift: i32
): i32
i64
, shift: i64
): i64
i32
, shift: i32
): i32
i64
, shift: i64
): i64
i32
): i32
i64
): i64
i32
): i32
i64
): i64
i32
): i32
i64
): i64
f64
): f64
f32
): f32
f64
): f64
f32
): f32
f64
): f64
f32
): f32
f64
): f64
f32
): f32
f64
): f64
f32
): f32
f64
): f64
f32
): f32
f64
, right: f64
): f64
NaN
, returns NaN
.f32
, right: f32
): f32
NaN
, returns NaN
.f64
, right: f64
): f64
NaN
, returns NaN
.f32
, right: f32
): f32
NaN
, returns NaN
.f64
, y: f64
): f64
x
and the sign of y
.f32
, y: f32
): f32
x
and the sign of y
.f32
): i32
f64
): i64
i32
): f32
i64
): f64
i32
i32
): i32
-1
on failure.void
T
>(offset: usize
): T
T
>(offset: usize
, value: T
): void
The following AssemblyScript-specific operations are implemented as built-ins as well:
T
>(): usize
T1
,T2
>(value: T1
): T2
T1
to a value of type T2
. Useful for casting classes to pointers and vice-versa. Does not perform any checks.f64
): bool
f32
): bool
f64
): bool
f32
): bool
These constants are present as immutable globals (note that optimizers might inline them):
f64
f32
f64
f32
By default, AssemblyScript's memory management runtime will be linked statically:
usize
, src: usize
, size: usize
): usize
usize
, c: i32
, size: usize
): usize
c
. Usually used to reset it to all 0
s.usize
, vr: usize
, n: usize
): i32
0
if both are equal, otherwise vl[i] - vr[i]
at the first difference's byte offset i
.usize
): usize
usize
, size: usize
): usize
usize
): void
Linking in the runtime adds up to 14kb to a module, but the optimizer is able to eliminate unused runtime code. Once WebAssembly exposes the garbage collector natively, there'll be other options as well. If the runtime has been excluded through --noRuntime
, its methods will be imported where referenced (i.e. when using new
). Also note that manually calling grow_memory
where the runtime is present will most likely break it.
Type coercion requires an explicit cast where precision or signage is lost respectively is implicit where it is maintained. For example, to cast a f64
to an i32
:
function example(value: f64): i32 {
return value as i32; // 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;
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
--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.
--noTreeShaking Whether to disable built-in tree-shaking.
--noImplicitConversion Whether to disallow implicit type conversions.
--noRuntime Whether to exclude the runtime.
--exportRuntime, -e Runtime functions to export, defaults to 'malloc' and 'free'. [multiple]
--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.
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. Returns null
on failure.
Compiler.compileString(source: string
, options?: CompilerOptions
): binaryen.Module | null
Compiles the specified entry file source to a WebAssembly module. Returns null
on failure.
Compiler.lastDiagnostics: typescript.Diagnostic[]
Contains the diagnostics generated by the last invocation of compilerFile
or compileString
.
CompilerOptions
AssemblyScript compiler options.
boolean
false
.CompilerTarget | string
CompilerTarget.WASM32
.boolean
false
.boolean
false
.boolean
string[]
CompilerTarget
Compiler target.
import { Compiler, CompilerTarget, CompilerMemoryModel, typescript } from "assemblyscript";
const module = Compiler.compileString(`
export function add(a: i32, b: i32): i32 {
return a + b;
}
`, {
target: CompilerTarget.WASM32,
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.
Clone the GitHub repository including submodules and install the development dependencies:
$> git clone --recursive https://github.com/AssemblyScript/assemblyscript.git
$> cd assemblyscript
$> npm install
Afterwards, to build the distribution files to dist/, run:
$> npm run build
Note that the first invocation of build
also builds the TypeScript submodule (lib/typescript) and may take some time.
To run the tests (ideally on node.js >= 8):
$> npm test
To build the documentation to the website repostory checked out next to this repository, run:
$> npm run docs
License: Apache License, Version 2.0