1. Project Overview & Quickstart (wasmerio/LICENSE)
LICENSE
Open-source repository wasmerio/LICENSE
Repository Details
- Repository: wasmerio/LICENSE
- Primary Language: Code
Note: High-volume repository documentation is actively indexed and synchronized by YakaAI.
2. Official Technical Reference & Guides (wasmerio/website)
File: README.md
AssemblyScript's Website
The site is built with VuePress and reuses its default theme to ease upgrading to newer versions.
The more important files are:
- src/**/*.md
Documentation and site contents, i.e. what one would typically modify when extending the documentation. - src/.vuepress/sidebar.js
Sidebar contents. - src/.vuepress/nav.js
Top-navigation contents. - src/.vuepress/redirects
Redirect map of (re)moved pages to new locations. - src/.vuepress/public/
Assets folder for images etc. - src/.vuepress/public/sponsors
Sponsor logo overrides (file name is collective name).
Editor component
Markdown files can make use of a custom editor component using the editor language tag, like so:
```editor
#!optimize=size&runtime=none&noAssert&explicitStart&enable=simd,reference-types
export function add(a: i32, b: i32): i32 {
return a + b
}
#!html
<script>
loader.instantiate(module_wasm, { /* imports */ })
.then(({ exports }) => {
console.log(exports.add(1, 2))
})
</script>
```The first line is an optional shebang indicating selected compiler options. Available options are:
- optimize =
string
Optimization preset to use. Valid presets arespeed,sizeandnone - noAssert
Replaces assertions with just their value without trapping. - debug
Enables debug information in emitted binaries. - runtime =
string
Specifies the runtime variant to include in the program. Valid variants arefull,half,stubandnone. - noExportMemory
Does not export the memory to the host. - importMemory
Imports the memory from the host. - exportTable
Exports the function table to the host. - importTable
Imports the function table from the host. - explicitStart
Exports an explicit start function to call. - memoryBase =
integer
Sets the start offset of emitted memory segments. - tableBase =
integer
Sets the start offset of emitted table elements. - use =
string
Comma separated list of global aliases, e.g. to switch the default 'Math' implementation used:Math=JSMath - enable =
string
Comma-separated list of future WebAssembly features to enable. Valid features aresign-extension,bulk-memory,simd,threadsandreference-types.
The current source and associated compiler options can be serialized into a base64 blob this way. For example, when the 🔗 button is clicked, document.location.hash is updated with that blob and the then-sharable link is copied to clipboard.
Building
To work on the site locally, install the dependencies and start a development server serving at localhost:8080:
npm install
npm startTo build the site to dist, i.e. to verify that it works as expected:
npm run buildDistribution files can also be served instead of using the development server with:
npm run serveThe page is automatically deployed on pushes to the repository, plus at least once a day to sync contributors and sponsors.
File: package.json
{
"name": "@assemblyscript/website",
"private": true,
"version": "0.0.0",
"license": "Apache-2.0",
"description": "AssebmlyScript's Website.",
"scripts": {
"start": "vuepress dev src --host localhost --no-cache",
"build": "vuepress build src",
"serve": "vuepress serve src",
"preinstall": "npx npm-force-resolutions"
},
"devDependencies": {
"@vuepress/plugin-html-redirect": "^0.1.2",
"he": "^1.2.0",
"node-fetch": "^2.6.1",
"pngquant": "^3.1.0",
"prism-themes": "^1.5.0",
"sharp": "^0.27.0",
"vuepress": "^1.8.0",
"vuepress-plugin-serve": "^2.0.4",
"vuepress-plugin-sitemap": "^2.3.1"
},
"resolutions": {
"watchpack": "1.6.1"
}
}
File: data/testimonials.json
[
{
"name": "Surma",
"text": "You are now able to write WebAssembly without learning a new language, and harness all these benefits WebAssembly might offer you. I think that is kind of powerful. AssemblyScript is absolutely usable, and very enjoyable!",
"title": "WebAssembly for Web Developers (Google I/O ’19)",
"link": "https://youtu.be/njt-Qzw0mVY",
"date": "May 8th, 2019"
},
{
"name": "Alon Zakai",
"text": "AssemblyScript is designed with WebAssembly and code size in mind. It's not an existing language that we are using for a new purpose but it's a language designed for WebAssembly. It has great wasm-opt integration, in fact it's built with it, and it's very easy to get good code size.",
"title": "Shipping Tiny WebAssembly Builds (WebAssembly Summit)",
"link": "https://www.youtube.com/watch?v=_lLqZR4ufSI",
"date": "Feb 10, 2020"
},
{
"name": "Peter Salomonsen",
"text": "I chose AssemblyScript because it has high-level readability and low-level control. It's like a high-level language but you get that low-level feeling and you can even directly write WebAssembly intrinsics if you want to.",
"title": "WebAssembly Music (WebAssembly Summit)",
"link": "https://www.youtube.com/watch?v=C8j_ieOm4vE",
"date": "Feb 10, 2020"
}
]
File: data/tiers.json
{
"platinum": {
"name": "Platinum Sponsors",
"minAmount": 50000,
"size": 80
},
"gold": {
"name": "Gold Sponsors",
"minAmount": 10000,
"size": 64
},
"silver": {
"name": "Silver Sponsors",
"minAmount": 2000,
"size": 48
},
"bronze": {
"name": "Bronze Sponsors",
"minAmount": 200,
"size": 36
},
"backer": {
"name": "Individual Backers",
"minAmount": 1,
"size": 32
}
}
File: src/basics.md
Basics
WebAssembly is fundamentally different from JavaScript, ultimately enabling entirely new use cases not only on the web. Consequently, AssemblyScript is much more similar to a static compiler than it is to a JavaScript VM. One can think of it as a mix of TypeScript's high level syntax and C's low-level capabilities. This page is dedicated to getting you up to speed in no time.
Strictness
Unlike TypeScript, which targets a JavaScript environment with all of its dynamic features, AssemblyScript targets WebAssembly with all of its static guarantees, hence intentionally avoids the dynamicness of JavaScript where it cannot be compiled ahead of time efficiently.
Limiting ourselves to where WebAssembly excels, for example by assigning or inferring definite types, eliminates the need to ship a JIT doing the heavy lifting at runtime, yielding predictable performance right from the start of execution while guaranteeing that the resulting WebAssembly modules are small.
Static typing
The first peculiarity one is going to notice when writing AssemblyScript is that its basic types are a bit different from TypeScript's in that it uses WebAssembly's more specific integer and floating point types, with JavaScript's number merely an alias of WebAssembly's f64.
A JavaScript JIT would try to figure out the best representation of a numeric value while the code is executing, making assumptions as values flow around, potentially recompiling code multiple times, while AssemblyScript lets the developer specify the ideal type in advance with all its pros and cons. To give a few examples:
No any or undefined
// 😢
function foo(a?) {
var b = a + 1
return b
}
// 😊
function foo(a: i32 = 0): i32 {
var b = a + 1
return b
}No union types
// 😢
function foo(a: i32 | string): void {
}
// 😊
function foo<T>(a: T): void {
}Strictly typed objects
// 😢
var a = {}
a.prop = "hello world"
// 😊
var a = new Map<string,string>()
a.set("prop", "hello world")
// 😊
class A {
constructor(public prop: string) {}
}
var a = new A("hello world")Sandbox
WebAssembly modules execute in a sandbox, providing strong security guarantees for all sorts of use cases not necessarily limited to the browser. As such, a module has no immediate access to the DOM or other external APIs out of the box. Also, WebAssembly does not yet have a concept of objects, making it necessary to translate and exchange objects via pointers to linear memory either through the module's exports and imports or by reading from respectively writing to the module's linear memory. The loader is able to help there.
Quirks
Some patterns behave a little different in AssemblyScript compared to TypeScript. This section covers the more or less obvious ones and shows how to deal with them, so you can keep them in mind and build something great today.
Triple equals
The special semantics of the === operator (true when both the value and type match) don't make a lot of sense in AssemblyScript because comparing two values of incompatible types is illegal anyway. Hence the === operator has been repurposed to perform an identity comparison, evaluating to true if both operands are the exact same object:
var a = "hello"
var b = a
var c = "h" + a.substring(1)
if (a === b) { /* true */ }
if (a === c) { /* false */ }
if (a == c) { /* true */ }This semantical change turned out to be quite useful during implementation of some parts of the standard library where knowing if something is actually exactly the same makes a lot of sense, but it can be confusing for those coming from a TypeScript background where using === for everything unless required otherwise is common good practice. However, the alternative here is to make === a redundanat alias of == by sacrificing an otherwise useful feature, and it's still unclear if that'd be objectively better, so has been postponed multiple times.
Nullability checks
Like TypeScript, the AssemblyScript compiler can propagate whether a value is nullable or not after a check:
function doSomething(something: string | null): void {
if (something) {
something.length // works
}
}This doesn't work on fields, however, because their value can change in between the check and the use of the value:
function doSomething(foo: Foo): void {
if (foo.something) {
// ... some code ...
foo.something.length // fails
}
}For example, foo.something might be a property that sets the underlying field to null when accessed, or code in between may set the field to null. In fact, TypeScript fails in both scenarios, leading to a runtime error. However, there can't be runtime errors like this in AssemblyScript, so one must instead use a local to be safe:
function doSomething(foo: Foo): void {
let something = foo.something
if (something) {
something.length // works
}
}Non-linear compilation
AssemblyScript does not compile a module linearly, but starts at the module's exports and only compiles what's reachable from them, often referred to as tree-shaking. As such, dead code is always validated syntactically, but not necessarily checked for semantic correctness. While this mechanism significantly helps to reduce compile times and feels almost natural to those familiar with executing JavaScript, it may initially feel a little strange not only to those with a background in traditional compilers, for example because emitted diagnostics do not happen linearly, but also to those with a background in TypeScript, because even type annotations remain unchecked as part of dead code. The exception to the rule is top-level code, including top-level variable declarations and their initializers, that must be evaluated as soon as the respective file would first execute.
So far, so good. Shall we continue with a status of WebAssembly and language features?
File: src/built-with-assemblyscript.md
Built with AssemblyScript
A place for all things AssemblyScript. Feel free to add your projects and applications.
Benchmarks
- Wasm Mandelbrot (demo)
A mandelbrot rendered using a variety of WebAssembly tools (emscripten, AssemblyScript, asm.js, etc ...) - WasmBoy Benchmark
Benchmarking WasmBoy emulator. - webassembly-wave (demo)
JavaScript vs WebAssembly performance comparison - wave equation demo (JS, C++, AssemblyScript). - Sort Colors Benchmark (demo)
Bench of sorting random colors by hue component on 2d canvas. - wasm-n-body
WebAssembly N-body simulation benchmark (use 1000 bodies compare to original). - as-benchmarks (demo)
A set of computational benchmarks (JS, AssemblyScript).
Blockchain
- eWasm / Biturbo
Eth1 EE using Turboproofs. - ewasm / scout.ts
Scout is a Ethereum 2.0 Phase 2 execution prototyping engine (AssemblyScript Port). - NearProtocol / near-sdk-as
AssemblyScript tools for writing smart contracts for NearProtocol. - GraphProtocol / graph-ts
TypeScript/AssemblyScript library for writing mappings for The Graph. - ChainSafe / as-sha256
AssemblyScript implementation of SHA256 for Lodestar. - ParityTech / srml-contracts-waterfall
Collection of simple Substrate smart contract examples written in Rust and AssemblyScript and tests for Substrates SRML Contracts module. - CasperLabs / contract-as
AssemblyScript smart contracting library for the CasperLabs protocol.
Build Tools
- Zwitterion
A web dev server that lets you import anything*. - rollup-plugin-assemblyscript
A Rollup plugin that allows you to import AssemblyScript files and compiles them on-the-fly. - visitor-as
Tools for creating compiler transformers.
Editors
- WebAssembly Studio
Learn, Teach, Work and Play in the WebAssembly Studio. - Fastly Terrarium
A compilation toolchain and sandbox to run WebAssembly server-side, with native support for AssemblyScript. - NEAR Protocol Examples
Examples that use GitPod to develop and deploy distributed web apps using NEAR smart contracts authored in AssemblyScript. - Iodide Notebook
Experimental tool for scientific communication and exploration on the web (more).
Emulators
- wasmBoy (demo)
Game Boy / Game Boy Color Emulator Library, written for WebAssembly using AssemblyScript. - Atari 2600
An Atari 2600 emulator written in AssemblyScript compiled to WebAssembly. - as-spectrum-engine
AssemblyScript implementation of a ZX Spectrum emulator engine.
Games
- Chess (play)
A free and open source chess game using AssemblyScript and React. - Gomoku WASM (play)
A Gomoku game implements with WebAssembly using as2d.
Graphics
- as2d
Bring the power of the CanvasRenderingContext2D prototype to AssemblyScript. - as-smallpt
Port of C++ smallpt, a path tracing renderer. - Babylon.Font
Create 3d text in BabylonJS. - seam-carving-as
Seam Carving experiments with AssemblyScript.
Libraries
- AS-LZMA
LZMA Decoder written in AssemblyScript. - assemblyscript-bson
BSON encoder / decoder. - assemblyscript-json
JSON encoder / decoder. - as-bignum (formerly bignum.wasm)
Fixed length big numbers likeu128,i256,fp128and etc for AssemblyScript. - ASTL
Migration project from C++ STL (Standard Template Library) to AssemblyScript. Containers, iterators, algorithms and functors are supported. - Levenshtein Wasm
An experimental lightning-fast Wasm-compiled levenshtein library. - superfasthash
An implementation of the SuperFastHash non-cryptographic hashing algorithm in JavaScript and WebAssembly. - as-wasi
An AssemblyScript API layer for WASI system calls. - WASM-Crypto
An AssemblyScript set of cryptographic primitives for building authentication and key exchange protocols. - rabin-wasm
Rabin fingerprinting implemented in WASM (used by js-ipfs). - galois
Arithmetic and polynomial operations in finite fields. - merkle
Merkle tree and other data structures. - xoroshiro128starstar
An AssemblyScript port of xoroshiro128starstar.c - pixelmatch-asc
Pixel level image comparison library. - as-bind
Isomorphic library to handle passing high-level data structures between AssemblyScript and JavaScript, built on top of the AssemblyScript loader. - wasmer-as
Helpers for dealing with assemblyscript memory inside wasmer-runtime. - wasmbind
Work with AssemblyScript memory and classes from Python. - io-devices-lib
Library for interacting with the Wasmer Experimental IO Devices.
Embedded / IoT
- wasm3-arduino
Usage example with wasm3 which run on Arduino, PlatformIO, Particle. - Wasm3_RGB_Lamp
Animating an RGB lamp, using WebAssembly (AssemblyScript). - wasm-arduino-wifi
Example how update WebAssembly via WiFi on arduino.
Testing
- as-pect
A test framework for AssemblyScript inspired by jest.
Project Templates
- worker-assemblyscript-template
A Cloudflare wrangler template for a AssemblyScript worker.
Other
- wasm-matrix
A Matrix effect in your terminal using AssemblyScript and WASI, deployed to WAPM. - WebAssembly music experiment
A live coding environment in the browser for sequencing music with javascript and synthesizing instruments in AssemblyScript. Demonstrates compiling AssemblyScript directly in the browser, and live hot-swapping of webassembly modules. - WireWorld (demo)
The Wireworld Computer in Javascript/Canvas.
File: src/compiler.md
Compiler
Similar to TypeScript's tsc compiling to JavaScript, AssemblyScript's asc compiles to WebAssembly.
Command line options
Entry file\(s\)
Non-option arguments are treated as the names of entry files. A single program can have multiple entries, with the exports of each entry becoming the exports of the WebAssembly module. Exports of imported files that are not entry files do not become WebAssembly module exports.
asc entryFile.tsGeneral
--version, -v Prints just the compiler's version and exits.
--help, -h Prints this message and exits.
--noColors Disables terminal colors.
--config Configuration file to apply. CLI arguments take precedence.
--target Target configuration to use. Defaults to 'release'.asconfig.json
Instead of providing the options outlined below on the command line, a configuration file typically named asconfig.json can be used. It may look like in the following example, excluding comments:
{
"extends": "./path/to/other/asconfig.json", // (optional) base config
"entries": [
// (optional) entry files, e.g.
"./assembly/index.ts"
],
"options": {
// common options, e.g.
"importTable": true
},
"targets": {
// (optional) targets
"release": {
// additional options for the "release" target, e.g.
"optimize": true,
"binaryFile": "myModule.release.wasm"
},
"debug": {
// additional options for the "debug" target, e.g.
"debug": true,
"binaryFile": "myModule.debug.wasm"
}
}
}Provided command line options override options in the configuration file.
Optimization
The compiler can optimize for both speed and size. --optimizeLevel (0-3) indicates how much the compiler focuses on optimizing the code with --shrinkLevel (0-2, 1=s, 2=z) indicating how much it focuses on keeping the size low during code generation and while optimizing. A convenient shorthand is -O[optimizeLevel][shrinkLevel] , with shrink level indicated by appending the letter s (1) or z (2) to the optimize level.
--optimize, -O Optimizes the module. Typical shorthands are:
Default optimizations -O / -O3s
Make a release build -O --noAssert
Make a debug build --debug
Optimize for speed -O3
Optimize for size -O3z --converge
--optimizeLevel How much to focus on optimizing code. [0-3]
--shrinkLevel How much to focus on shrinking code size. [0-2, s=1, z=2]
--converge Re-optimizes until no further improvements can be made.
--noAssert Replaces assertions with just their value without trapping.Output
Typical output formats are WebAssembly binary (.wasm) and/or text format (.wat). Often, both are used in tandem to run and also inspect generated code.
--outFile, -o Specifies the output file. File extension indicates format.
--binaryFile, -b Specifies the binary output file (.wasm).
--textFile, -t Specifies the text output file (.wat).There are several other output formats as well for tooling purposes with varying levels of maturity.
--jsFile, -j Specifies the JavaScript output file (.js).
--idlFile, -i Specifies the WebIDL output file (.webidl).
--tsdFile, -d Specifies the TypeScript definition output file (.d.ts).Debugging
For easier debugging a source map can be emitted alongside the WebAssembly binary.
--sourceMap Enables source map generation. Optionally takes the URL
used to reference the source map from the binary file.It is also often useful to emit debug information, like function names, alongside the binary.
--debug Enables debug information in emitted binaries.Features
There are several flags that enable or disable specific WebAssembly or compiler features. By default, only the bare minimum is exposed, and fully standardized WebAssembly features will be used.
--importMemory Imports the memory from 'env.memory'.
--noExportMemory Does not export the memory as 'memory'.
--initialMemory Sets the initial memory size in pages.
--maximumMemory Sets the maximum memory size in pages.
--sharedMemory Declare memory as shared. Requires maximumMemory.
--importTable Imports the function table from 'env.table'.
--exportTable Exports the function table as 'table'.
--runtime Specifies the runtime variant to include in the program.
incremental TLSF + incremental GC (default)
minimal TLSF + lightweight GC invoked externally
stub Minimal runtime stub (never frees)
... Path to a custom runtime implementation
--exportRuntime Exports the runtime helpers (__new, __collect etc.).
--explicitStart Exports an explicit '_start' function to call.
--enable Enables WebAssembly features being disabled by default.
sign-extension Sign-extension operations
bulk-memory Bulk memory operations.
simd SIMD types and operations.
threads Threading and atomic operations.
reference-types Reference types and operations.
gc Garbage collection (anyref, WIP).
--disable Disables WebAssembly features being enabled by default.
mutable-globals Mutable global imports and exports.
--use, -u Aliases a global object under another name, e.g., to switch
the default 'Math' implementation used: --use Math=JSMath
Can also be used to introduce an integer constant.
--lowMemoryLimit Enforces very low (<64k) memory constraints.Linking
Specifying the base offsets of compiler-generated memory respectively the table leaves some space for other data in front. In its current form this is mostly useful to link additional data into an AssemblyScript binary after compilation, be it by populating the binary itself or initializing the data externally upon initialization. One good example is leaving some scratch space for a frame buffer.
--memoryBase Sets the start offset of emitted memory segments.
--tableBase Sets the start offset of emitted table elements.API
To integrate with the compiler, for example to post-process the AST, one or multiple custom transforms can be specified.
--transform Specifies the path to a custom transform to 'require'.Other
Other options include those forwarded to Binaryen and various flags useful in certain situations.
Binaryen
--trapMode Sets the trap mode to use.
allow Allow trapping operations. This is the default.
clamp Replace trapping operations with clamping semantics.
js Replace trapping operations with JS semantics.
--runPasses Specifies additional Binaryen passes to run after other
optimizations, if any. See: Binaryen/src/passes/pass.cpp
--noValidate Skips validating the module using Binaryen.And the kitchen sink
--noColors Disables terminal colors.
--baseDir Specifies the base directory of input and output files.
--extension Specifies an alternative file extension to use.
--noUnsafe Disallows the use of unsafe features in user code.
Does not affect library files and external modules.
--noEmit Performs compilation as usual but does not emit code.
--measure Prints measuring information on I/O and compile times.
--pedantic Make yourself sad for no good reason.
--lib Adds one or multiple paths to custom library components and
uses exports of all top-level files at this path as globals.
--path Adds one or multiple paths to package resolution, similar
to node_modules. Prefers an 'ascMain' entry in a package's
package.json and falls back to an inner 'assembly/' folder.
--traceResolution Enables tracing of package resolution.
--listFiles Lists files to be compiled and exits.
-- ... Specifies node.js options (CLI only). See: node --helpAPI
The compiler can also be used programmatically.
File: src/debugging.md
Debugging
Recommendations on how to debug AssemblyScript modules.
Source maps
The compiler supports generating a source map alongside a binary using the --sourceMap option.
Relative source maps
By default, specifying the --sourceMap option will create a source mapping section pointing to the source map with a relative path, defaulting to myModule.wasm.map with myModule being the name of the respective binary. This works when instantiating a module with WebAssembly.instantiateStreaming because the VM can obtain the absolute URL the source map is relative to from the provided Response object, but does not work if a module is instantiated from a buffer or otherwise without a path context.
Absolute source maps
Where relative source maps cannot be used, for example if WebAssembly.instantiate is used to instantiate a module from a binary buffer, it is also possible to specify an absolute path to the source map using --sourceMap absolutePathToSourceMap.
Stack traces
A JavaScript VM is able to display stack traces originating in WebAssembly code. However, optimizing a WebAssembly module will usually drop all the debug information, making these stack traces hard to grasp. Using an unoptimized module or a module otherwise preserving debug information can help here. The AssemblyScript compiler preserves debug information by specifying the --debug compiler option.
Disabling assertions
By default, the compiler will preserve any assert(expression)s in a module, leading to an abort if one of the respective expectations failed. These assertions can be disabled with the --noAssert compiler option, though, essentially replacing them with nops, doing nothing. Doing so can lead to smaller binaries once sufficiently confident that no assertions will be hit anyway, but also introduces the risk that a module explodes for no longer asserted reasons.
Overriding `abort`
As mentioned above, assertions require that an implementation of the abort interface is present, which by default is imported as abort from within the env module, handling aborts externally. This can be overridden by specifying a custom abort handler through --use abort=assembly/index/myAbort (here: a function named myAbort in assembly/index.ts) or the abort interface can be disabled completely (just trapping with an unreachable) through --use abort=. The signature of the abort function, if overridden, is:
function abort(
message: string | null,
fileName: string | null,
lineNumber: u32,
columnNumber: u32
): voidManual tracing
The standard library provides a relatively basic trace utility function that is imported from the host and understood by the loader. For example
trace("HERE", 2, value, otherValue)will, by default, call the trace function imported from the env module with a string message and two arbitrary values that can be anything convertible to an f64. The loader for example will log the output to console. Similar to overriding abort, the implementation can be overridden using the --use compiler option. Signature of the trace function is:
function trace(
message: string,
n: i32 = 0, // number of given parameters a0 to a4
a0?: f64,
a1?: f64,
a2?: f64,
a3?: f64,
a4?: f64
): voidOne thing to note here is that calling trace in top-level statements can lead to situations where memory is accessed during instantiation, hence not being able to read the message without taking the respective precautions.
Breakpoints
Some JavaScript engines also support adding break points when running WebAssembly binaries. Please consult your engine's documentation.
Additional resources
- Making Web Assembly Even Faster: Debugging Web Assembly Performance with AssemblyScript and a Gameboy Emulator (Aaron Turner, March 2018)
File: src/development.md
Development
Instructions for setting up a development environment.
::: tip
Please see our contribution guidelines before submitting your first pull request.
:::
Setting up a development environment
The following sets up a development environment of the compiler where editing the sources applies changes immediately without having to go through a build step:
git clone https://github.com/AssemblyScript/assemblyscript.git
cd assemblyscript
npm install
npm linkNote that a development environment runs the sources directly by default, but will use distributions files once built. Cleaning the distribution files again with npm run clean resets this. Whether asc is running the sources or distribution files can be determined by checking the displayed version number. If it states -dev, it runs the sources directly.
Building distribution files
To build an UMD bundle to dist/assemblyscript.js (depends on binaryen.js), including a browser version of asc to dist/asc.js (depends on assemblyscript.js):
npm run buildCleaning the distribution files (again):
npm run cleanLinting potential changes:
npm run checkRunning the tests:
npm testRunning everything in order (check, clean, test, build, test):
npm run allCreating tests
The test suite is composed of a set of tests for each component, like the parser, the compiler and runtime. For each pull request, all the tests are run automatically and the PR only goes green if it passes all of them. Please see the instructions within the repository for all the details.
File: src/editor-test.md
Editor Test
Text before
#!runtime=stub
// Editor A
export function add(a: i32, b: i32): i32 {
return a + b
}
export function sub(a: i32, b: i32): i32 {
return a - b
}
#!html
<script>
loader.instantiate(module_wasm, { /* imports */ })
.then(({ exports }) => {
const output = document.getElementById('output')
output.value += `add(1, 2) = ${exports.add(1, 2)}\n`
output.value += `sub(1, 2) = ${exports.sub(1, 2)}\n`
})
</script>
<textarea id="output" style="height: 100%; width: 100%" readonly></textarea>Text between
#!runtime=stub
// Editor B
export function sub(a: i32, b: i32): i32 {
return a - b
}
export function add(a: i32, b: i32): i32 {
return a + b
}
#!html
<script>
loader.instantiate(module_wasm, { /* imports */ })
.then(({ exports }) => {
const output = document.getElementById('output')
output.value += `sub(2, 3) = ${exports.sub(2, 3)}\n`
output.value += `add(2, 3) = ${exports.add(2, 3)}\n`
})
</script>
<textarea id="output" style="height: 100%; width: 100%" readonly></textarea>Text after
File: src/environment.md
Environment
A WebAssembly environment is more limited than a usual browser environment, but AssemblyScript tries to fill the gaps by reimplementing commonly known functionality, besides providing direct access to WebAssembly instructions through built-ins.
Standard library
AssemblyScript comes with its own standard library very much resembling what developers became used to when writing JavaScript, with a few specialized classes, like StaticArray, that exist in AssemblyScript only to tackle very specific problems.
Additional rather low-level WebAssembly functionality that the standard library makes extensive use of is described below.
Static type checks
By making use of the following special type checks, especially in generic contexts, untaken branches can be eliminated statically, leading to concrete WebAssembly functions that handle one type specificially.
- ts
function isInteger<T>(value?: T): boolTests if the specified type or expression is of an integer type and not a reference. Compiles to a constant.
- ts
function isFloat<T>(value?: T): boolTests if the specified type or expression is of a float type. Compiles to a constant.
- ts
function isSigned<T>(value?: T): boolTests if the specified type or expression can represent negative numbers. Compiles to a constant.
- ts
function isReference<T>(value?: T): boolTests if the specified type or expression is of a reference type. Compiles to a constant.
- ts
function isString<T>(value?: T): boolTests if the specified type or expression can be used as a string. Compiles to a constant.
- ts
function isArray<T>(value?: T): boolTests if the specified type or expression can be used as an array. Compiles to a constant.
- ts
function isFunction<T>(value?: T): boolTests if the specified type or expression is of a function type. Compiles to a constant.
- ts
function isNullable<T>(value?: T): boolTests if the specified type or expression is of a nullable reference type. Compiles to a constant.
- ts
function isDefined(expression: auto): boolTests if the specified expression resolves to a defined element. Compiles to a constant.
- ts
function isConstant(expression: auto): boolTests if the specified expression evaluates to a constant value. Compiles to a constant.
- ts
function isManaged<T>(expression: auto): boolTests if the specified type or expression is of a managed type. Compiles to a constant. Usually only relevant when implementing custom collection-like classes.
Example
function add<T>(a: T, b: T): T {
return a + b // addition if numeric, string concatenation if a string
}
function add<T>(a: T, b: T): T {
if (isString<T>()) { // eliminated if T is not a string
return parseInt(a) + parseInt(b)
} else { // eliminated if T is a string
return a + b
}
}::: tip
If you are not going to use low-level WebAssembly in the foreseeable future, feel free to come back to the following paragraphs at a later time and continue at the next page right away.
:::
Sizes and alignments
- ts
function sizeof<T>(): usizeDetermines the byte size of the respective basic type. Means: If
Tis a class type, the size ofusizeis returned. To obtain the size of a class in memory, useoffsetof<T>()instead. Compiles to a constant. - ts
function offsetof<T>(fieldName?: string): usizeDetermines the offset of the specified field within the given class type. Returns the class type's end offset (means: where the next field would be located, before alignment) if field name has been omitted. Compiles to a constant. The
fieldNameargument must be a compile-time constantstringbecause there is no information about field names anymore in the final binary. Hence, the field's name must be known at the time the returned constant is computed. - ts
function alignof<T>(): usizeDetermines the alignment (log2) of the specified underlying basic type. Means: If
Tis a class type, the alignment ofusizeis returned. Compiles to a constant.
Utility
- ts
function assert<T>(isTrueish: T, message?: string): TTraps if the specified value is not true-ish, otherwise returns the non-nullable value. Like assertions in C, aborting the entire program if the expectation fails, with the
--noAssertoption to disable all assertions in production. - ts
function instantiate<T>(...args: auto[]): TInstantiates a new instance of
Tusing the specified constructor arguments. - ts
function changetype<T>(value: auto): TChanges the type of a value to another one. Useful for casting class instances to their pointer values and vice-versa.
- ts
function idof<T>(): u32Obtains the computed unique id of a class type. Usually only relevant when allocating objects or dealing with RTTI externally.
- ts
function nameof<T>(value?: T): stringDetermines the name of a given type.
Low-level WebAssembly operations
Math
The following generic built-ins compile to WebAssembly instructions directly.
- ts
function clz<T>(value: T): TPerforms the sign-agnostic count leading zero bits operation on a 32-bit or 64-bit integer. All zero bits are considered leading if the value is zero.
- ts
function ctz<T>(value: T): TPerforms the sign-agnostic count tailing zero bits operation on a 32-bit or 64-bit integer. All zero bits are considered trailing if the value is zero.
- ts
function popcnt<T>(value: T): TPerforms the sign-agnostic count number of one bits operation on a 32-bit or 64-bit integer.
- ts
function rotl<T>(value: T, shift: T): TPerforms the sign-agnostic rotate left operation on a 32-bit or 64-bit integer.
- ts
function rotr<T>(value: T, shift: T): TPerforms the sign-agnostic rotate right operation on a 32-bit or 64-bit integer.
- ts
function abs<T>(value: T): TComputes the absolute value of an integer or float.
- ts
function max<T>(left: T, right: T): TDetermines the maximum of two integers or floats. If either operand is
NaN, returnsNaN. - ts
function min<T>(left: T, right: T): TDetermines the minimum of two integers or floats. If either operand is
NaN, returnsNaN. - ts
function ceil<T>(value: T): TPerforms the ceiling operation on a 32-bit or 64-bit float.
- ts
function floor<T>(value: T): TPerforms the floor operation on a 32-bit or 64-bit float.
- ts
function copysign<T>(x: T , y: T): TComposes a 32-bit or 64-bit float from the magnitude of
xand the sign ofy. - ts
function nearest<T>(value: T): TRounds to the nearest integer tied to even of a 32-bit or 64-bit float.
- ts
function reinterpret<T>(value: auto): TReinterprets the bits of the specified value as type
T. Valid reinterpretations are u32/i32 to/from f32 and u64/i64 to/from f64. - ts
function sqrt<T>(value: T): TCalculates the square root of a 32-bit or 64-bit float.
- ts
function trunc<T>(value: T): TRounds to the nearest integer towards zero of a 32-bit or 64-bit float.
Memory
Similarly, the following built-ins emit WebAssembly instructions accessing or otherwise modifying memory.
- ts
function load<T>(ptr: usize, immOffset?: usize): TLoads a value of the specified type from memory. Equivalent to dereferencing a pointer in other languages.
- ts
function store<T>(ptr: usize, value: auto, immOffset?: usize): voidStores a value of the specified type to memory. Equivalent to dereferencing a pointer in other languages when assigning a value.
- ts
function memory.size(): i32Returns the current size of the memory in units of pages. One page is 64kb.
- ts
function memory.grow(value: i32): i32Grows linear memory by a given unsigned delta of pages. One page is 64kb. Returns the previous size of the memory in units of pages or
-1on failure.::: warning
Callingmemory.growwhere a memory manager is present might break it.
::: - ts
function memory.copy(dst: usize, src: usize, n: usize): voidCopies
nbytes fromsrctodst. Regions may overlap. Emits the respective instruction if bulk-memory is enabled, otherwise ships a polyfill. - ts
function memory.fill(dst: usize, value: u8, n: usize): voidFills
nbytes atdstwith the given bytevalue. Emits the respective instruction if bulk-memory is enabled, otherwise ships a polyfill. - ts
function memory.repeat(dst: usize, src: usize, srcLength: usize, count: usize): voidRepeats a sequence of bytes given as
srcwithsrcLengthcounttimes into destinationdst. - ts
function memory.compare(lhs: usize, rhs: usize, n: usize): i32Compares the first
nbytes ofleftandrigthand returns a value that indicates their relationship:- Negative value if the first differing byte in
lhsis less than the corresponding byte inrhs. - Positive value if the first differing byte in
lhsis greater than the corresponding byte inrhs. - Zero if all
nbytes oflhsandrhsare equal.
- Negative value if the first differing byte in
- ts
function memory.data(size: i32, align?: i32): usizeGets a pointer to a zeroed static chunk of memory of the given size. Alignment defaults to
16. Arguments must be compile-time constants. - ts
function memory.data<T>(values: T[], align?: i32): usizeGets a pointer to a pre-initialized static chunk of memory. Alignment defaults to the size of
T. Arguments must be compile-time constants.
The immOffset argument is a bit special here, because it becomes an actual immediate of the respective WebAssembly instruction instead of a normal operand. Thus it must be provided as a compile time constant value. This can be a literal or the value of a const variable that the compiler can precompute.
**Memory Manager**
An unsafe interface to just the dynamic memory manager, resembling malloc, realloc and free in C.
- ts
function heap.alloc(size: usize): usizeAllocates a chunk of memory of at least the specified size.
- ts
function heap.realloc(ptr: usize, size: usize): usizeReallocates a chunk of memory to have at least the specified size.
- ts
function heap.free(ptr: usize): voidFrees a chunk of memory. Does hardly anything (most recent block only) with the "stub" runtime.
- ts
function heap.reset(): voidDangerously resets the entire heap. Specific to the "stub" runtime.
**Memory Utility**
Sign-agnostic endian conversions (reverse bytes).
- ts
function bswap<T>(value: T): TReverses the byte order of the specified integer.
- ts
function bswap16<T>(value: T): TReverses only the last 2 bytes regardless of the type argument.
Control flow
- ts
function select<T>(ifTrue: T, ifFalse: T, condition: bool): TSelects one of two pre-evaluated values depending on the condition. Differs from an
if/elsein that both arms are always executed and the final value is picked based on the condition afterwards. Performs better than anif/elseonly if the condition is random (means: branch prediction is not going to perform well) and both alternatives are cheap. It is also worth to note that Binaryen will do relevant optimizations like switching to aselectautomatically, so using a ternary? :for example is just fine. - ts
function unreachable(): autoEmits an unreachable instruction that results in a runtime error (trap) when executed. Both a statement and an expression of any type. Beware that trapping in managed code will most likely lead to memory leaks or even break the program because it ends execution prematurely.
Atomics 🦄
The following instructions represent the WebAssembly threads and atomics specification. Must be enabled with --enable threads.
- ts
function atomic.load<T>(ptr: usize, immOffset?: usize): TAtomically loads an integer value from memory and returns it.
- ts
function atomic.store<T>(ptr: usize, value: auto, immOffset?: usize): voidAtomically stores an integer value to memory.
- ts
function atomic.add<T>(ptr: usize, value: T, immOffset?: usize): TAtomically adds an integer value in memory.
- ts
function atomic.sub<T>(ptr: usize, value: T, immOffset?: usize): TAtomically subtracts an integer value in memory.
- ts
function atomic.and<T>(ptr: usize, value: T, immOffset?: usize): TAtomically performs a bitwise AND operation on an integer value in memory.
- ts
function atomic.or<T>(ptr: usize, value: T, immOffset?: usize): TAtomically performs a bitwise OR operation on an integer value in memory.
- ts
function atomic.xor<T>(ptr: usize, value: T, immOffset?: usize): TAtomically performs a bitwise XOR operation on an integer value in memory.
- ts
function atomic.xchg<T>(ptr: usize, value: T, immOffset?: usize): TAtomically exchanges an integer value in memory.
- ts
function atomic.cmpxchg<T>(ptr: usize, expected: T, replacement: T, immOffset?: usize): TAtomically compares and exchanges an integer value in memory if the condition is met.
- ts
function atomic.wait<T>(ptr: usize, expected: T, timeout: i64): AtomicWaitResultPerforms a wait operation on an address in memory suspending this agent if the integer condition is met. Return values are
Value Description 0 OK - Woken by another agent. 1 NOT_EQUAL - Loaded value did not match the expected value. 2 TIMED_OUT - Not woken before the timeout expired. - ts
function atomic.notify(ptr: usize, count: i32): i32Performs a notify operation on an address in memory waking up suspended agents.
- ts
function atomic.fence(): voidPerforms a fence operation, preserving synchronization guarantees of higher level languages.
Again, the immOffset argument must be a compile time constant value.
SIMD 🦄
Likewise, these represent the WebAssembly SIMD specification. Must be enabled with --enable simd.
- ts
function v128(a: i8, ... , p: i8): v128Initializes a 128-bit vector from sixteen 8-bit integer values. Arguments must be compile-time constants.
- ts
function v128.splat<T>(x: T): v128Creates a vector with identical lanes.
- ts
function v128.extract_lane<T>(x: v128, idx: u8): TExtracts one lane as a scalar.
- ts
function v128.replace_lane<T>(x: v128, idx: u8, value: T): v128Replaces one lane.
- ts
function v128.shuffle<T>(a: v128, b: v128, ...lanes: u8[]): v128Selects lanes from either vector according to the specified lane indexes.
- ts
function v128.swizzle(a: v128, s: v128): v128Selects 8-bit lanes from the first vector according to the indexes [0-15] specified by the 8-bit lanes of the second vector.
- ts
function v128.load(ptr: usize, immOffset?: usize, immAlign?: usize): v128Loads a vector from memory.
- ts
function v128.load_splat<T>(ptr: usize, immOffset?: usize, immAlign?: usize): v128Creates a vector with identical lanes by loading the splatted value.
- ts
function v128.load_ext<TFrom>(ptr: usize, immOffset?: usize, immAlign?: usize): v128Creates a vector by loading the lanes of the specified integer type and extending each to the next larger type.
- ts
function v128.store(ptr: usize, value: v128, immOffset?: usize, immAlign?: usize): voidStores a vector to memory.
- ts
function v128.add<T>(a: v128, b: v128): v128Adds each lane.
- ts
function v128.sub<T>(a: v128, b: v128): v128Subtracts each lane.
- ts
function v128.mul<T>(a: v128, b: v128): v128Multiplies each lane.
- ts
function v128.div<T>(a: v128, b: v128): v128Divides each floating point lane.
- ts
function v128.neg<T>(a: v128): v128Negates each lane.
- ts
function v128.add_saturate<T>(a: v128, b: v128): v128Adds each signed small integer lane using saturation.
- ts
function v128.sub_saturate<T>(a: v128, b: v128): v128Subtracts each signed small integer lane using saturation.
- ts
function v128.shl<T>(a: v128, b: i32): v128Performs a bitwise left shift by a scalar on each integer lane.
- ts
function v128.shr<T>(a: v128, b: i32): v128Performs a bitwise right shift by a scalar on each integer lane.
- ts
function v128.and(a: v128, b: v128): v128Performs the bitwise
a & boperation on each lane. - ts
function v128.or(a: v128, b: v128): v128Performs the bitwise
a | boperation on each lane. - ts
function v128.xor(a: v128, b: v128): v128Performs the bitwise
a ^ boperation on each lane. - ts
function v128.andnot(a: v128, b: v128): v128Performs the bitwise
!a & boperation on each lane. - ts
function v128.not(a: v128): v128Performs the bitwise
!aoperation on each lane. - ts
function v128.bitselect(a: v128, b: v128, mask: v128): v128Selects bits of either vector according to the specified mask.
- ts
function v128.any_true<T>(a: v128): boolReduces a vector to a scalar indicating whether any lane is considered
true. - ts
function v128.all_true<T>(a: v128): boolReduces a vector to a scalar indicating whether all lanes are considered
true. - ts
function v128.bitmask<T>(a: v128): boolExtracts the high bit of each integer lane (except 64-bit) and produces a scalar mask with all bits concatenated.
- ts
function v128.max<T>(a: v128, b: v128): v128Computes the maximum of each lane.
- ts
function v128.min<T>(a: v128, b: v128): v128Computes the minimum of each lane.
- ts
function v128.dot<T>(a: v128, b: v128): v128Computes the dot product of two 16-bit integer lanes each, yielding lanes one size wider than the input.
- ts
function v128.avgr<T>(a: v128, b: v128): v128)Computes the rounding average of each unsigned small integer lane.
- ts
function v128.abs<T>(a: v128): v128Computes the absolute value of each lane (except 64-bit integers).
- ts
function v128.sqrt<T>(a: v128): v128Computes the square root of each floating point lane.
- ts
function v128.ceil<T>(a: v128): v128Performs the ceiling operation on each lane.
- ts
function v128.floor<T>(a: v128): v128Performs the floor operation on each lane.
- ts
function v128.trunc<T>(a: v128): v128Rounds to the nearest integer towards zero of each lane.
- ts
function v128.nearest<T>(a: v128): v128Rounds to the nearest integer tied to even of each lane.
- ts
function v128.eq<T>(a: v128, b: v128): v128Computes which lanes are equal.
- ts
function v128.ne<T>(a: v128, b: v128): v128Computes which lanes are not equal.
- ts
function v128.lt<T>(a: v128, b: v128): v128Computes which lanes of the first vector are less than those of the second.
- ts
function v128.le<T>(a: v128, b: v128): v128Computes which lanes of the first vector are less than or equal those of the second.
- ts
function v128.gt<T>(a: v128, b: v128): v128Computes which lanes of the first vector are greater than those of the second.
- ts
function v128.ge<T>(a: v128, b: v128): v128Computes which lanes of the first vector are greater than or equal those of the second.
- ts
function v128.convert<TFrom>(a: v128): v128Converts each lane from integer to floating point.
- ts
function v128.trunc_sat<TTo>(a: v128): v128Truncates each lane from floating point to integer with saturation.
- ts
function v128.narrow<TFrom>(a: v128, b: v128): v128Narrows wider integer lanes to their respective narrower lanes.
- ts
function v128.widen_low<TFrom>(a: v128): v128Widens the low half of narrower integer lanes to their respective wider lanes.
- ts
function v128.widen_high<TFrom>(a: v128): v128Widens the high half of narrower integer lanes to their respective wider lanes.
- ts
function v128.qfma<T>(a: v128, b: v128, c: v128): v128Computes
(a * b) + cfor each floating point lane. - ts
function v128.qfms<T>(a: v128, b: v128, c: v128): v128Computes
(a * b) - cfor each floating point lane.
In addition, the namespaces i8x16, i16x8, i32x4, i64x2 , f32x4 and f64x2 provide their respective non-generic instructions, like i32x4.splat etc. Each of them can also be used to create a literal directly:
- ts
function i8x16(a: i8, ... , p: i8): v128Initializes a 128-bit vector from sixteen 8-bit integer values. Arguments must be compile-time constants.
- ts
function i16x8(a: i16, ..., h: i16): v128Initializes a 128-bit vector from eight 16-bit integer values. Arguments must be compile-time constants.
- ts
function i32x4(a: i32, b: i32, c: i32, d: i32): v128Initializes a 128-bit vector from four 32-bit integer values. Arguments must be compile-time constants.
- ts
function i64x2(a: i64, b: i64): v128Initializes a 128-bit vector from two 64-bit integer values. Arguments must be compile-time constants.
- ts
function f32x4(a: f32, b: f32, c: f32, d: f32): v128Initializes a 128-bit vector from four 32-bit float values. Arguments must be compile-time constants.
- ts
function f64x2(a: f64, b: f64): v128Initializes a 128-bit vector from two 64-bit float values. Arguments must be compile-time constants.
The namespaces v8x16, v16x8, v32x4 and v64x2 provide the respective sign agnostic instructions according to text format.
File: src/examples.md
sidebarDepth: 0
Examples
A collection of AssemblyScript examples and snippets.
Starter examples
Small entertaining programs showcasing low-level WebAssembly capabilities. These compile to less than one or just a few kilobytes so their text format is easy to grasp.
[Mandelbrot](./examples/mandelbrot.md)
Renders the Mandelbrot set to a canvas using 2048 discrete color values computed on the JS side.
[Interference](./examples/interference.md)
Animates and renders an interference pattern to a canvas while keeping the image buffer in WebAssembly.
[Game of Life](./examples/game-of-life.md)
Continuously updates a cellular automaton and visualizes its state on a canvas according to user input.
Advanced examples
[Arrays](./examples/arrays.md)
Shows how to exchange and work with arrays using the loader.
Examples repository
Various more advanced examples are available as part of the examples repository, including a sophisticated example of using the loader, creating (node) libraries, utilizing the browser SDK and hooking into the compiler using transforms.
Additional resources
If you are interested in learning more about specific concepts, also make sure to give Wasm By Example a read.
File: src/exports-and-imports.md
Exports and imports
Setting up the API to talk to your WebAssembly module.
Exports
Exports work very much like in TypeScript, with the notable difference that exports from an entry file also become the exports of the resulting WebAssembly module.
Functions
export function add(a: i32, b: i32): i32 {
return a + b
}Globals
export const foo = 1
export var bar = 2Exporting or importing mutable globals requires that the engine supports the post-MVP and now standardized Import/Export mutable globals proposal, which is enabled by default.
Classes
If an entire class (possibly part of a namespace) is exported from an entry file, its visible members will become distinct module exports using a JSDoc-like naming scheme that the loader understands to make a nice object structure of. For example
export namespace foo {
export class Bar {
a: i32 = 1
getA(): i32 { return this.a }
}
}will yield the raw function exports
- foo.Bar#constructor
- foo.Bar#get:a
- foo.Bar#set:a
- foo.Bar#getA
which the loader combines back to a foo.Bar object:
var bar = new myModule.foo.Bar()
bar.a = 2
console.log(bar.a)Under the hood and without the loader, it'd look like this:
var thisBar = myModule["foo.Bar#constructor"](0)
myModule["foo.Bar#set:a"](thisBar, 2)
console.log(myModule["foo.Bar#get:a"](thisBar))As one can see the this argument must be provided as an additional first argument to the raw functions. No argument or 0 as the first argument to a constructor indicates that the constructor is expected to allocate on its own (not extending a class with existing memory). Of course one usually doesn't have to deal with these internals since the loader will already take care of it.
Imports
With WebAssembly ES Module Integration still in the pipeline, imports utilize the ambient context currently. For example
// env.ts
export declare function doSomething(foo: i32): voidcreates an import of a function named doSomething from the env module, because that's the name of the file it lives is. It is also possible to use namespaces:
// foo.ts
export declare namespace console {
export function logi(i: i32): void
export function logf(f: f64): void
}This will import the functions console.logi and console.logf from the foo module. Bonus: Don't forget exporting namespace members if you'd like to call them from outside the namespace.
A typical pattern is to declare the interface of each external module in its own file, using the name of the module as its file name, so the external module can be imported as if it was just another source file:
import { doSomething } from "./env";
import { console } from "./foo";Custom naming
Where automatic naming is not sufficient, the @external decorator can be used to give an element another external name:
// bar.ts
@external("doSomethingElse")
export declare function doSomething(foo: i32): void
// imports doSomethingElse from bar as doSomething
@external("foo", "baz")
export declare function doSomething(foo: i32): void
// imports baz from foo as doSomethingOn values crossing the boundary
More complex values than the basic integer and floating point types, like class instances or function references, are represented by an index or pointer within the WebAssembly module. The compiler knows how to work with these because it also knows the concrete type associated with the value. On the outside, however, for example in JS-code interacting with a WebAssembly module, all that's seen is the basic value.
- An instance of a class is a pointer to the structure in WebAssembly memory.
- A function reference is the index of the function in the WebAssembly table.
This is true in both directions, hence also applies when providing a value to a WebAssembly import. The most common structures like String, ArrayBuffer and the typed arrays are documented in memory internals, and custom classes adhere to class layout.
Anatomy of a module
WebAssembly modules produced by the AssemblyScript compiler typically have the following set of imports and exports in addition to those defined in user code, with ? indicating conditional presence. Recommended scenarios like using modules together with the loader or utilizing WASI via import "wasi" take care of these automatically.
Exports
- ts
const memory: WebAssembly.MemoryThe module's memory instance. Can be disabled with
--noExportMemory. - ts
const table?: WebAssembly.TableThe module's function table instance. Can be enabled with
--exportTable. - ts
function _start?(): voidExplicit start function to call. Can be enabled with
--explicitStart. Also works with WASI. - ts
function __setArgumentsLength?(numArgs: i32): voidVarargs helper to set what's known as
arguments.lengthin JavaScript. Only present if there is at least one export of a function taking a variable number of arguments. Automatically utilized by the loader. - ts
function __new?(size: usize, id: u32): usize function __pin?(ptr: usize): usize function __unpin?(ptr: usize): void function __collect?(): voidGarbage collection helpers. Only present when compiling with
--exportRuntime.
Imports
- ts
const env.memory?: WebAssembly.MemoryThe module's memory instance. Can be enabled with
--importMemory. - ts
const env.table?: WebAssembly.TableThe module's table instance. Can be enabled with
--importTable. - ts
function env.abort?(message: usize, fileName: usize, line: u32, column: u32): voidCalled on unrecoverable errors. Typically present if assertions are enabled. Automatically implemented by the loader. Not present but otherwise implemented on
import "wasi". - ts
function env.trace?(message: usize, n: i32, a0..4?: f64): voidCalled when
traceis called in user code. Only present if it is. Automatically implemented by the loader. Not present but otherwise implemented onimport "wasi". - ts
function env.seed?(): f64Called when the random number generator needs to be seeded. Only present if it is. Automatically implemented by the loader. Not present but otherwise implemented on
import "wasi".
File: src/frequently-asked-questions.md
sidebarDepth: 0
Frequently asked questions
Questions that may arise before, at or after this point. so if you're wondering about one thing or another, here it is:
Is WebAssembly going to supersede JavaScript?
Probably not, and it is not meant to do so. There are use cases one can handle "better" than the other, with "better" depending on requirements. A requirement can be to make an algorithm more efficient on the one hand, which is where WebAssembly is good at, or to quickly add interactive elements to a web page, which JavaScript is great for. As always, picking the right tool for the job is key, and AssemblyScript just so happens to blur the line a bit.
Is WebAssembly faster than JavaScript?
Not always, but it can be. The execution characteristics of ahead-of-time compiled WebAssembly differ from just-in-time compiled JavaScript in that it is more predictable in a way that enables a WebAssembly program to remain on a reasonably fast path over the entire course of execution, while a JavaScript VM tries hard to do all sorts of smart optimizations before and while the code is executing. This implies that a JavaScript VM can make both very smart decisions, especially for well-written code with a clear intent, but might also have to reconsider its strategy to do something more general if its assumptions did not hold. If you are primarily interested in performance, rules of thumb could be:
| Scenario | Recommendation |
|---|---|
| Compute-heavy algorithm | Use WebAssembly |
| Mostly interacts with the DOM | Mostly use JavaScript |
| Games | Use WebAssembly for CPU-intensive parts |
| WebGL | Depends how much of it is calling APIs. Probably both. |
| Websites, Blogs, ... | JavaScript is your friend |
Or: WebAssembly is great for computational tasks, stuff with numbers, but still needs some time to become more convenient and efficient at the same time where sharing numbers between the module and the host is not enough.
Is AssemblyScript faster than JavaScript?
Not always, but there are use cases especially well-suited for it, like creating a Game Boy emulator by making use of its low-level capabilities, essentially emitting raw WebAssembly using a nicer syntax. But pre-existing TypeScript code doesn't magically become faster just by compiling to WebAssembly, especially when making extensive use of managed objects that require memory management and garbage collection (this has its cost in every language) or talking to the host in structures that WebAssembly isn't currently good at, like strings or more complex objects. Low-level code (just functions, numbers, math and hard work) is always the best choice when all you care about is raw performance.
How does AssemblyScript compare/relate to C++/Rust?
First and foremost: Both Emscripten (C++) and Rust have very mature tooling to compile to WebAssembly and are made by the smartest people in this field. Also, both can make use of compiler infrastructure that has been created by many individuals and corporations over years. In contrast, AssemblyScript is a relatively young project with limited resources that strives to create a viable alternative from another perspective.
More precisely: AssemblyScript is putting anything web - from APIs to syntax to WebAssembly - first and then glues it all together, while others lift an existing ecosystem to the web. We believe that both approaches are fantastic, and fortunately for us, there is Binaryen, a compiler infrastructure and toolchain library for WebAssembly primarily created by the main author of Emscripten, that we can utilize to considerably close the gap. We are very thankful for it. Binaryen is not as optimal for AssemblyScript-generated code as it is for LLVM-generated code in a few cases, but it's already pretty good and continuously becoming better. It's also noteworthy that AssemblyScript is still behind in specific language features, but we are working on that.
In short: AssemblyScript differs in that it is new and tries another approach. It's not as mature as Emscripten and Rust, but there is something about the idea that is definitely appealing. If you find it appealing as well, AssemblyScript is for you.
Will AssemblyScript support all of TypeScript eventually?
It very likely won't. While TypeScript adds some static typing to JavaScript, its type system also tries to describe any possible dynamic feature of JavaScript. TypeScript is a superset of JavaScript after all. As such there is a point where it becomes infeasible to support JavaScript features that TypeScript can describe without adding an interpreter mode to AssemblyScript, which is a non-goal.
Will interop between AssemblyScript and JavaScript become better?
This is an open question. While there are WebAssembly proposals specifically aiming at improving interop, not all of them consider excellent interoperability between WebAssembly and the existing web platform as important as we do.
One point of disagreement for example stems from AssemblyScript's decision to chose JavaScript's 16-bit string encoding as a basis, initially exactly to aid compatibility and interop on the web, while others primarily interested in systems languages are advocating for just UTF-8. The UTF-8 everywhere sentiment may appear noble on paper, yet the reality we live in is that it would unnecessarily handicap AssemblyScript or other languages with most of their APIs designed for 16-bit Unicode, due to always having to re-encode and/or copy or alternatively having to break their APIs to be efficient. Our hopes are, however, since Java, .NET and many more languages are facing a similar challenge, that there'll ultimately be a solution that covers more languages.
Similarly, WebAssembly has primarily been designed with systems languages in mind so far and it did an exceptional job at bringing these to the web, yet it appears that the now predominant low-level mindset may eventually run counter to designing features that work well with the existing web platform, which is rather high-level. As such it is still subject to discussion whether for example WebAssembly GC strings, arrays and other objects will have good interop with JavaScript, or if JavaScript strings, arrays and other objects will have good interop with WebAssembly GC respectively. It is certainly important to design these APIs carefully, yet, in our opinion, WebAssembly may be missing the opportunity of its lifetime if it is not designed with excellent interop with JavaScript and the web in mind.
If you are interested in these aspects, feel free to join us in the respective spec discussions to inform the process.
How can I help?
There are various ways to help. AssemblyScript is an open source project, and everyone is welcome to contribute code, documentation, or time. We also have an OpenCollective for those preferring to help the project out with a sponsorship.
File: src/garbage-collection.md
sidebarDepth: 2
Garbage Collection
::: tip NOTE
This section covers the new runtime added in AssemblyScript 0.18. The previous runtime documentation including migration guidelines can be found here.
:::
AssemblyScript implements garbage collection on top of linear memory while the WebAssembly GC 🦄 proposal is still in the works. More precisely it implements multiple runtime variants that are useful for different use cases, with the default being your typical memory manager and GC. The interface of the different variants is always the same, so these are interchangeable, yet differ in their level of sophistication. In case of doubt, starting out with the default is probably just fine.
Runtime interface
Using the --exportRuntime compiler option, an interface to the runtime can be exported from the module to the host, so it becomes possible to allocate new objects and invoke the garbage collector externally.
- ts
function __new(size: usize, id: u32): usizeAllocates a new garbage collected instance of the object represented by the specified class id, of at least the specified size. Returns the pointer to the object, typically pointing at the object's data respectively its first field.
Note that the loader provides more convenient wrappers like
__newStringand__newArrayas well.__newis just the underlying implementation. The respective class id for theidargument can be obtained within WebAssembly viaidof<AClass>(), typically exporting it to the host as a constant to use with__newor__newArray. - ts
function __pin(ptr: usize): usizePins the object pointed to by
ptrexternally so it and its directly reachable members and indirectly reachable objects do not become garbage collected. - ts
function __unpin(ptr: usize): voidUnpins the object pointed to by
ptrexternally so it can become garbage collected. - ts
function __collect(): voidPerforms a full garbage collection.
To interface with the runtime externally, for example to allocate objects in linear memory, you'll probably want to use the loader as well.
Runtime variants
With the --runtime compiler option, one of multiple runtime variants can be selected, which determines the memory allocator and garbage collector included in a program:
--runtime Specifies the runtime variant to include in the program.
incremental TLSF + incremental GC (default)
minimal TLSF + lightweight GC invoked externally
stub Minimal runtime stub (never frees)
... Path to a custom runtime implementation
--exportRuntime Exports the runtime helpers (__new, __collect etc.).Incremental runtime
This is the default runtime variant included in an AssemblyScript program, combining an Incremental Tri-Color Mark & Sweep (ITCMS) garbage collector with a Two-Level Segregate Fit (TLSF) memory manager. It is fully automated as one would expect from a typical GC, but its implementation has to go to lengths, involving some overhead, to polyfill functionality that is not yet provided by WebAssembly itself. It needs to maintain a shadow stack for example, and instruments binaries with it.
The incremental runtime is most suitable for non-trivial programs where short pause times are important, like in games, or where manually invoking the GC is not an option, like in WASI applications.
Usage instructions:
- When a managed object is exclusively referenced externally,
exports.__pin(objPtr)it so it doesn't become collected when WebAssembly code executes. - When the external object is not needed anymore,
exports.__unpin(objPtr)it so it can become collected again. - Note that a pinned object also keeps its directly reachable members and any indirectly reachable objects alive.
- It is safe to omit pinning an object externally if it is known to be referenced from within WebAssembly anyhow.
- It is possible to force a full collection (finishes current cycle, does another full cycle) using
exports.__collect(), if so desired.
Example usage:
function run() {
let gameEnded = exports.updateGameProducingGarbage() // incrementally performs GC
if (gameEnded) {
exports.renderEndgameScreenProducingGarbage()
exports.__collect() // optionally clean up the remains
} else {
requestAnimationFrame(run)
exports.renderGameProducingGarbage() // incrementally performs GC
}
}
run()Minimal runtime
The minimal runtime is similar to the incremental runtime, except that it doesn't go to lengths to polyfill WebAssembly features that are not yet available. It features a simpler (non-incremental) Two-Color Mark & Sweep (TCMS) garbage collector on top of TLSF, making it a reasonable compromise.
The minimal runtime works best for use-cases where the GC can be invoked externally at appropriate places, that is when the WebAssembly execution stack is known to be fully unwound. The execution stack is typically unwound when exports.__collect() is invoked neither directly nor indirectly from WebAssembly, for example when invoking it from JavaScript only. The AssemblyScript compiler itself is a good candidate for the minimal runtime for example, as it does bounded amounts of work in bounded amounts of time, so it is sufficient to call exports.__collect() occasionally when specific phases of compilation are complete.
Unlike the incremental runtime, the minimal runtime does not run interleaved with the program but always does a full garbage collection cycle when invoked externally, stopping the program while doing so. It is important to not overdo invoking the GC to avoid marking a lot but sweeping little for that reason. However, when used wisely, it typically has better throughput than the incremental runtime.
Usage instructions:
- Whenever you see fit, invoke
exports.__collect()externally to collect all garbage. Note that this will mark & sweep all objects, so don't overdo calling it to avoid unnecessary overhead from marking a lot but sweeping little. In case of doubt, benchmark to find a reasonable sweet spot. - When a managed object is exclusively referenced externally,
exports.__pin(objPtr)it so it doesn't become collected whenexports.__collect()is being invoked. - When the external object is not needed anymore,
exports.__unpin(objPtr)it so it can become collected again. - Note that a pinned object also keeps its directly reachable members and any indirectly reachable objects alive.
- It is safe to omit pinning an object externally if
- it is known to be referenced from within WebAssembly anyhow, directly or indirectly reachable from any root, like a global.
- it is solely used as an argument to a WebAssembly function and not used externally anymore afterwards.
- it is guaranteed that
exports.__collect()will not be called while it is alive.
- In case of doubt, pin.
Example usage:
function compute(arg) {
exports.doSomeHeavyWorkProducingGarbage(arg)
...
exports.__collect() // clean up all garbage
}
compute(1)
compute(2)
compute(3)Stub runtime
The stub is a maximally minimal runtime substitute, consisting of a simple and fast bump allocator with no means of freeing up memory again, except when freeing the respective most recently allocated object on top of the bump. As such, the stub runtime is useful where dynamic allocation, or garbage for that matter, is not a concern, for example because the program doesn't produce any or only executes a single time with a bounded limit of garbage before the entire module is terminated and collected by the host again.
Usage instructions:
- Since the stub runtime never frees memory, there is nothing special to take care of except that it leaks memory by design.
Example usage:
function compute() {
exports.doSomeHeavyWorkProducingGarbage()
}
compute()
// throw away the entire module instanceCustom runtime
Even though of limited usefulness, it is also possible to create a new runtime (essentially a memory manager and a garbage collector) from scratch and include it instead of one of the options provided by AssemblyScript itself. To do so, a custom runtime needs to provide the implementations of the externally visible interfaces like __new, plus the underlying interfaces like __alloc and __free, on top of compiler-generated functionality like __visit_members, as described in more detail in the runtime's README. Looking at the implementation of the relatively simple stub runtime before diving into the more sophisticated ones may be a good starting point.
- Runtimes typically include a general purpose memory manager to also drive
heap.allocandheap.free - Garbage collection is precise, with compiler-generated visitors aware of what's a pointer and what's not
- The interface is tailored for tracing GCs, so attempts to implement other forms of GC may not be viable
The future: WebAssembly GC
The WebAssembly GC 🦄 proposal is still in the works but our focus is to switch to it as soon as possible, hopefully making most of the above redundant and the overall experience more convenient.
File: src/index.md
metaTitle: AssemblyScript navbar: false sidebar: false editLink: false pageClass: frontpage
<svg viewBox="0 0 768 256"></svg>A language made for WebAssembly.
<a href="/introduction.html" class="docs">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"></svg>
Get Started
</a>
<a href="https://github.com/AssemblyScript" target="_blank" rel="noopener" class="github">
<svg viewBox="0 0 24 24"></svg>
GitHub
</a>
<a href="https://www.npmjs.com/package/assemblyscript" target="_blank" rel="noopener" class="npm">
<svg viewBox="0 0 24 24"></svg>
npm
</a>
<h2>Designed for WebAssembly</h2>
AssemblyScript targets WebAssembly's feature set specifically, giving developers low-level control over their code.
<h2>Familiar TypeScript syntax</h2>
Being a variant of TypeScript makes it easy to compile to WebAssembly without learning a new language.
<h2>Right at your fingertips</h2>
Integrates with the existing Web ecosystem - no heavy toolchains to set up. Simply `npm install` it!#!runtime=stub
/** Calculates the n-th Fibonacci number. */
export function fib(n: i32): i32 {
var a = 0, b = 1
if (n > 0) {
while (--n) {
let t = a + b
a = b
b = t
}
return b
}
return a
}
#!html
<textarea id="output" style="height: 100%; width: 100%" readonly></textarea>
<script>
loader.instantiate(module_wasm, { /* imports */ })
.then(({ exports }) => {
const output = document.getElementById('output')
for (let i = 0; i <= 10; ++i) {
output.value += `fib(${i}) = ${exports.fib(i)}\n`
}
})
</script> AssemblyScript is free and open source software released under the Apache License, Version 2.0, builds upon Binaryen and is based on the WebAssembly specification. It is brought to you by the following awesome people:
Why AssemblyScript?
Thanks to our sponsors!
Most of the core team members and most contributors do this open source work in their free time. If you use AssemblyScript for a serious task or plan to do so, and you'd like us to invest more time on it, [please donate](https://opencollective.com/assemblyscript/donate) to our [OpenCollective](https://opencollective.com/assemblyscript). By sponsoring this project, your logo will show up below. Thank you so much for your support!Join our Discord
If you have questions only a human can answer, would like to show others what you are working on or just want to hang out with other AssemblyScript folks, make sure to [join our Discord server](https://discord.gg/assemblyscript)! There you'll find channels for **#announcements**, **#help**, and more.File: src/interoperability.md
Interoperability
How to talk to AssemblyScript from C or other languages.
Strings
Strings in AssemblyScript are stored in WTF-16 encoding, closely matching JavaScript and its string APIs. When embedding AssemblyScript into a C or Rust environment, conversion between WTF-16 and UTF-8 is typically necessary (API).
Details to keep in mind: In UTF-16, certain Unicode code points are represented by two UTF-16 code units called surrogate pairs. However, just like in JavaScript, strings are not sanitized so lone surrogates can appear in a string, which are technically ill-formed UTF-16. This is done to mimic JavaScript as closely as possible, and avoids string re-encoding when calling JavaScript APIs from WebAssembly.
We hope that future WebAssembly features will be designed with both C/Rust and JS/WebAPIs in mind without penalizing the respective other. (1, 2)
Class layout
Layout of objects in memory resembles that of non-packed C structs, and @unmanaged classes (not tracked by GC, essentially C structs) can be used to describe them:
struct Foo {
uint32_t bar;
uint16_t baz;
uint64_t qux;
}@unmanaged class Foo {
bar: u32; // aligned to 4 bytes
baz: u16; // aligned to 2 bytes
qux: u64; // aligned to 8 bytes
}
offsetof<Foo>("bar") == 0
offsetof<Foo>("baz") == 4
offsetof<Foo>("qux") == 8
offsetof<Foo>() == 16
var foo = changetype<Foo>(ptrToFoo)
foo.bar // -> i32.load(ptrToFoo, 0)
foo.baz // -> i32.load16_u(ptrToFoo, 4)
foo.qux // -> i64.load(ptrToFoo, 8)More complex structures usually require manual offset calculation, mostly for the reason that it is not feasible to implement something more specific into AssemblyScript as it does not use such structures on its own:
struct Foo {
int32_t bar;
uint32_t baz[10];
}@unmanaged class Foo {
bar: i32
getBaz(i: i32): u32 {
return load<u32>(
changetype<usize>(this) + (<usize>i << alignof<u32>()),
4 // or use offsetof/sizeof to calculate the base offset plus alignment
)
}
}Calling convention
Exported functions will properly wrap return values, but functions not visible externally do not guarantee proper wrapping.
export function getU8(): u8 {
return someU8 // will wrap to someU8 & 0xff
}On the other hand, calling any AssemblyScript function does not require wrapping of arguments.
Optional arguments
If a function exported from AssemblyScript accepts optional arguments, the exports.__setArgumentsLength(numArguments) helper must be called with the number of actual arguments (while zeroing all the others) to inform the varargs stub to fill in the default values of omitted arguments. The loader does this automatically.
Garbage collection
Also see the dedicated page on garbage collection.
File: src/introduction.md
sidebarDepth: 0
Introduction
AssemblyScript compiles a strict variant of TypeScript (a typed superset of JavaScript) to WebAssembly using Binaryen, looking like:
export function fib(n: i32): i32 {
var a = 0, b = 1
if (n > 0) {
while (--n) {
let t = a + b
a = b
b = t
}
return b
}
return a
}asc fib.ts -b fib.wasm -O3Its architecture differs from a JavaScript VM in that it compiles a program ahead of time, quite similar to your typical C compiler. One can think of it as TypeScript syntax on top of WebAssembly instructions, statically compiled, to produce lean and mean WebAssembly binaries.
In a nutshell
On top of WebAssembly types, AssemblyScript not only provides low-level built-ins to access WebAssembly features directly, making it suitable as a thin layer on top of Wasm, but also comes with its own JavaScript-like standard library and a relatively small memory management and garbage collection runtime, making it suitable as a general purpose higher-level language for WebAssembly today.
For example, on the lowest level, memory can be accessed using the load<T>(offset) and store<T>(offset, value) built-ins that compile to WebAssembly instructions directly
store<i32>(8, load<i32>(0) + load<i32>(4))while the standard library provides higher-level implementations of Array, ArrayBuffer, DataView, Math (double and single precision), Uint8Array, String, Map, Set etc.:
var view = new Int32Array(12)
view[2] = view[0] + view[1]It is worth to note, however, that AssemblyScript still has its quirks, and we are patiently waiting for future WebAssembly features (marked as 🦄 throughout the documentation) to become available for us to use.
Sounds appealing to you? Read on!
File: src/loader.md
sidebarDepth: 2
Loader
AssemblyScript provides a tiny module loader that makes working with AssemblyScript modules as convenient as it gets without sacrificing efficiency. It about mirrors the relevant parts of the WebAssembly API while also providing utility to allocate and read strings, arrays and classes.
import loader from "@assemblyscript/loader"; // or require
loader.instantiate(
// Binary to instantiate
fetch("optimized.wasm"), // or fs.readFileSync
// or fs.promises.readFile
// or just a buffer
// Additional imports
{ ... }
).then(({ exports }) => {
...
})The loader basically instantiates the module using WebAssembly APIs, but also adds additional utility to it while evaluating export names and making a nice object structure of them. For example, if one exports an entire class Foo from the module, there will be an exports.Foo constructor etc. available when using the loader.
::: tip NOTE
Allocating objects or invoking the GC externally requires compiling with --exportRuntime. See: Garbage collection.
:::
Installation
For each version of the AssemblyScript compiler, there is a standalone version of the loader that can be installed from npm:
npm install --save @assemblyscript/loaderOn the web:
<!-- ESM -->
<script type="module" src="https://cdn.jsdelivr.net/npm/@assemblyscript/loader/index.js"></script>
<!-- UMD -->
<script src="https://cdn.jsdelivr.net/npm/@assemblyscript/loader/umd/index.js"></script>::: tip
If you need a specific version of the loader, append the respective version number as usual.
:::
Usage
One task the loader does not perform is to implicitly translate between WebAssembly pointers and JavaScript objects, and that's where the mixed in utility comes into play. For example, if one has
// AssemblyScript
export function concat(a: string, b: string): string {
return a + b
}and then wants to call concat externally, the string arguments cannot just be JavaScript strings but must first be allocated in the module's memory with their lifetime tracked, like so:
// JavaScript
const { concat } = myModule.exports
const { __newString, __getString } = myModule.exports
function doConcat(aStr, bStr) {
let aPtr = __newString(aStr)
let bPtr = __newString(bStr)
let cPtr = concat(aPtr, bPtr)
let cStr = __getString(cPtr)
return cStr
}
console.log(doConcat("Hello ", "world!"))Creating arrays
Arrays (or more advanced classes for that matter) require a bit more cooperation because we need to know their value type in order to work with them properly. To achieve this, every class has a unique id internally, and a chunk of runtime type information (RTTI) is shipped with the module to evaluate class types. Here's an example of working with an Int32Array:
// AssemblyScript
export function sum(arr: Int32Array): i32 {
let sum = 0
for (let i = 0, k = arr.length; i < k; ++i) {
sum += unchecked(arr[i])
}
return sum
}
export const Int32Array_ID = idof<Int32Array>()// JavaScript
const { sum, Int32Array_ID } = myModule.exports
const { __newArray } = myModule.exports
function doSum(values) {
const arrPtr = __newArray(Int32Array_ID, values)
return sum(arrPtr)
}
console.log(doSum([1, 2, 3]))This works with all kinds of arrays, except that ids are different and values are interpreted differently, of course.
Reading arrays
If one is instead interested in the values of an array being returned by the module, there are two approaches to this. Let's say we have the following module:
// AssemblyScript
export function getRandomArray(len: i32): Int32Array {
const arr = new Int32Array(len)
// fill with random values
return arr
}The first is, obviously, to read the array's values from the module's memory by essentially copying them to a JS array
// JavaScript
const { getRandomArray } = myModule.exports
const { __getArray } = myModule.exports
function doGetRandomArray(len) {
const arrPtr = getRandomArray(len)
const values = __getArray(arrPtr)
return values
}
console.log(doGetRandomArray(10))which is always safe, while the second is to create a live view on the array, enabling two-way modification of its values:
// JavaScript
const { getRandomArray } = myModule.exports
const { __getArrayView, __pin, __unpin } = myModule.exports
function doGetRandomArrayView(len) {
const arrPtr = __pin(getRandomArray(len)) // pin if necessary
const view = __getArrayView(arrPtr)
return { ptr, view }
}
const randomArray = doGetRandomArrayView(10)
console.log(randomArray.view)
__unpin(randomArray.ptr) // unpin if necessaryThe latter variant can be more efficient (and useful) but is a little dangerous because the view may become detached from the module's memory when memory automatically grows. Also, the viewed array can grow automatically when pushed to, with the view then referencing random memory. Pushing to an array can be avoided quite easily, yet it is notoriously hard to predict when module memory grows - but one can try to set a sufficiently large size of --initialMemory or defensively trigger a sufficiently large dynamic allocation being freed immediately before dealing with potentially problematic views.
Custom classes
As mentioned earlier, the loader understands how to make a nice object structure of a module's exports, and it is possible to utilize it to work with classes in a more natural way. For example, when calling the following function externally
// AssemblyScript
export class Foo {
constructor(public str: string) {}
getString(): string {
return this.str
}
}
export function getFoo(): Foo { // this one
return new Foo("Hello world!")
}one can wrap the received pointer in a myModule.exports.Foo instance:
// JavaScript
const { Foo, getFoo } = myModule.exports
const { __getString, __pin, __unpin } = myModule.exports
const fooPtr = __pin(getFoo()) // pin if necessary
const foo = Foo.wrap(fooPtr)
const strPtr = foo.getString()
console.log(__getString(strPtr))
__unpin(fooPtr) // unpin if necessaryAPI
For reference, here comes the full API provided by the loader.
::: tip
Copying from and extending the examples above is typically sufficient.
:::
Static members
- ts
function instantiate<T>( moduleOrBuffer: WasmInstantiable, imports?: WasmImports ): Promise<ASUtil & T>Asynchronously instantiates an AssemblyScript module from anything that can be instantiated.
- ts
function instantiateSync<T>( moduleOrBuffer: WasmInstantiable, imports?: WasmImports ): ASUtil & TSynchronously instantiates an AssemblyScript module from a WebAssembly.Module or binary buffer. Not recommended.
- ts
function instantiateStreaming<T>( response: Response | PromiseLike<Response>, imports?: WasmImports ): Promise<ASUtil & T>Asynchronously instantiates an AssemblyScript module from a response, i.e. as obtained by fetch.
- ts
function demangle<T>( exports: WasmExports, baseModule?: Object ): TDemangles an AssemblyScript module's exports to a friendly object structure. You usually don't have to call this manually as instantiation does this implicitly.
Note that T above can either be omitted if the shape of the module is unknown, or can reference a .d.ts (i.e. typeof MyModule) as produced by the compiler with the -d option.
Module instance utility
The following utility functions are mixed into the module's exports.
- ts
function __newString(str: string): numberAllocates a new string in the module's memory and returns a pointer to it. Requires
--exportRuntimefor access to__new. - ts
function __newArray(id: number, values: number[]): numberAllocates a new array in the module's memory and returns a pointer to it. The
idis the unique runtime id of the respective array class. If you are usingInt32Arrayfor example, the best way to know the id is anexport const Int32Array_ID = idof(). Requires--exportRuntimefor access to__new. - ts
function __getString(ptr: number): stringCopies a string's value from the module's memory to a JavaScript string.
ptrmust not be zero. - ts
function __getArrayBuffer(ptr: number): ArrayBufferCopies an ArrayBuffer's value from the module's memory to a JavaScript buffer.
ptrmust not be zero. - ts
function __getArray(ptr: number): number[]Copies an array's values from the module's memory to a JavaScript array. Infers the array type from RTTI.
ptrmust not be zero. - ts
function __getArrayView(ptr: number): TypedArrayGets a live view on the values of an array in the module's memory. Infers the array type from RTTI.
ptrmust not be zero.This differs from
__getArrayin that the data isn't copied but remains live in both directions. That's faster but also unsafe because if the array grows or becomes garbage collected, the view will no longer represent the correct memory region and modifying its values in this state will most likely corrupt memory or otherwise explode. Use, but use with care. - ts
function __getInt8ArrayView(ptr: number): Int8Array function __getUint8ArrayView(ptr: number): Uint8Array function __getUint8ClampedArrayView(ptr: number): Uint8ClampedArray function __getInt16ArrayView(ptr: number): Int16Array function __getUint16ArrayView(ptr: number): Uint16Array function __getInt32ArrayView(ptr: number): Int32Array function __getUint32ArrayView(ptr: number): Uint32Array function __getInt64ArrayView(ptr: number): BigInt64Array function __getUint64ArrayView(ptr: number): BigUint64Array function __getFloat32ArrayView(ptr: number): Float32Array function __getFloat64ArrayView(ptr: number): Float64ArraySlightly more efficient variants of
__getArrayViewwhere the type of the array is know beforehand. Doesn't try to infer the type.
Module instance runtime interface
When compiling with --exportRuntime, the loader will expose the runtime interface (__new, __pin, __unpin, __collect) as well. See: Garbage collection
Convenience vs. efficiency
Making the loader's API any more convenient has its tradeoffs. One would either have to include extended type information with the module itself or generate an additional JavaScript file of glue code that does (and hides) all the lifting. As such, one can consider the loader as a small and efficient building block that can do it all, yet does not sacrifice efficiency. If that's not exactly what you are looking for, take a look at more convenient tools below. Just remember that these have tradeoffs.
More convenient tools
- as-bind is a library, built on top of the loader, to make passing high-level data structures between AssemblyScript and JavaScript more convenient.
Advanced usage
Direct memory access
All of the above can be mixed with direct memory accesses on myModule.exports.memory.buffer, for instance by adhering to class layout.
TypeScript definitions
The compiler is able to emit definitions using the -d command line option that are compatible with modules demangled by the loader, and these can be used for proper typings in development:
// TypeScript
import MyModule from "myModule"; // pointing at the generated d.ts
loader.instantiate<typeof MyModule>(
fetch("myModule.wasm"),
{ ... }
).then(({ exports }) => {
...
})File: src/memory.md
Memory
Similar to other languages that use linear memory, and until the GC 🦄 proposal becomes available, all data in AssemblyScript is stored at a specific offset in linear memory so other parts of the program can read and modify it.
Importing memory
The WebAssembly.Memory instance used by your program can be imported from the host by using the --importMemory command line option. The module will then expect an import named memory within the env module to be provided upon instantiation.
An imported memory becomes the singleton memory of the module and can be accessed in the same way as a non-imported memory. Note that if the module defines data segments, it will place the data into the imported memory upon instantiation of the module, which must be taken into account when pre-populating the memory externally (e.g. when utilizing --memoryBase to reserve some space as described in Memory Regions below).
Exporting memory
A module exports its memory as memory by default, unless disabled with --noExportMemory.
Accessing memory during instantiation
AssemblyScript runs any top-level statements as part of the emitted WebAssembly module's implicit start function by default. Since the implicit start function executes immediately upon instantiation, this leads to a special case because control flow has not yet yielded back to the host, so the module's exported memory only becomes available externally after the implicit start function completes. The implicit start function may however already trigger external code that requires to read from memory, like when printing a string to console.
There are two solutions to this:
Exporting an explicit start function
The
--explicitStartcommand line argument can be used to force exporting the start function as_startinstead of making it the module's implicit start function. When doing so it is important to callexports._start()once before calling any other exports to properly initialize the module first.Preparing memory pre-instantiation and importing it
An alternative is to create the
WebAssembly.Memoryobject before instantiating the module, and using--importMemoryto import it. This way around the memory is already known when the implicit start function executes.
Memory regions
Internally, there are two regions of memory the compiler is aware of:
Static memory
Memory starts with static data, like strings and arrays (of constant values) the compiler encountered while translating the program. Unlike in other languages, there is no concept of a stack in AssemblyScript and it instead relies on WebAssembly's execution stack exclusively.
A custom region of memory can be reserved using the --memoryBase option. For example, if one needs an image buffer of exactly N bytes, instead of allocating it one could reserve that space, telling the compiler to place its own static data afterwards, partitioning memory in this order:
| Region | Description |
|---|---|
| Reserved memory | As specified with --memoryBase |
| Static memory | Starting right after reserved memory, ends at __heap_base |
| Dynamic memory | Starting right after static memory, starts at __heap_base |
Dynamic memory
Dynamic memory, commonly known as the heap, is managed by the garbage collector, at runtime. When space for a new object is requested by the program, the runtime's memory manager reserves a suitable region and returns a pointer to it to the program. Once an object is not needed anymore and becomes unreachable, the garbage collector returns the object's memory to the memory manager for reuse.
The memory manager guarantees an alignment of 16 bytes, so an ArrayBuffer, which can be the backing buffer of multiple views, always fits up to v128 values with natural alignment.
Internals
WebAssembly is specified to use little-endian and as a result, so is AssemblyScript.
Objects in AssemblyScript have a common hidden header used by the runtime to keep track of them. The header includes information about the block used by the memory manager, state information used by the garbage collector, a unique id per concrete class and the data's actual size. The length of a String (id = 1) is computed from that size for example. The header is "hidden" in that the reference to an object points right after it, at the first byte of the object's actual data.
Common header layout
| Name | Offset | Type | Description |
|---|---|---|---|
| #mmInfo | -20 | usize | Memory manager info |
| #gcInfo | -16 | usize | Garbage collector info |
| #gcInfo2 | -12 | usize | Garbage collector info |
| #rtId | -8 | u32 | Unique id of the concrete class |
| #rtSize | -4 | u32 | Size of the data following the header |
| 0 | Payload starts here |
In Wasm32, the header is aligned to 16 bytes right after mmInfo and rtSize, with 16 bytes of managed information in between. The object's payload starts at 16 bytes alignment again.
The most basic objects using the common header are ArrayBuffer and String. These do not have any fields but are just data:
ArrayBuffer layout
| Name | Offset | Type | Description |
|---|---|---|---|
| -20 | See above | Common header | |
| [0] | 0 | 8-bit untyped | The first byte |
| [1] | 1 | 8-bit untyped | The second byte |
| ... | |||
| [N] | N | 8-bit untyped | The N-th byte |
String layout
| Name | Offset | Type | Description |
|---|---|---|---|
| -20 | See above | Common header | |
| [0] | 0 | u16 | The first character |
| [1] | 2 | u16 | The second character |
| ... | |||
| [N] | N << 1 | u16 | The N-th character |
Collections
Collections like Array, Map and Set use one or multiple ArrayBuffers to store their data, but their backing buffers are only exposed for typed arrays, as it is the case in JavaScript. This is because collections can grow automatically, which would otherwise lead to no longer valid references to buffers sticking around.
ArrayBufferView layout
This is not an actual class but a helper to simplify the implementation of the typed array views. Each typed array view like Uint8Array is like a subclass of ArrayBufferView with a static value type attached.
| Name | Offset | Type | Description |
|---|---|---|---|
| -20 | See above | Common header | |
| #data | 0 | ArrayBuffer | Backing buffer reference |
| #dataStart | 4 | usize | Start of the data within .data |
| #dataLength | 8 | u32 | Length of the data from .dataStart |
Array layout
| Name | Offset | Type | Description |
|---|---|---|---|
| -20 | See above | Common header | |
| 0 | See above | ArrayBufferView | |
| .length | 12 | i32 | Mutable length of the data the user is interested in |
The layouts of Set and Map are a bit more sophisticated and therefore not mentioned here, but feel free to take a look at their sources.
File: src/peculiarities.md
Peculiarities
Special features that exist for internal implementation purposes or are otherwise nice to have.
Annotations
Decorators work more like actual compiler annotations in AssemblyScript.
| Annotation | Description |
|---|---|
@inline |
Requests inlining of a constant or function. |
@lazy |
Requests lazy compilation of a variable. Useful to avoid unnecessary globals. |
@global |
Registers an element to be part of the global scope. |
@external |
Changes the external name of an imported element. @external(module, name) changes both the module and element name, @external(name) changes the element name only. |
@operator |
Annotates a method as a binary operator overload. See below. |
@operator.binary |
Alias of @operator. |
@operator.prefix |
Annotates a method as a unary prefix operator overload. See below. |
@operator.postfix |
Annotates a method as a unary postfix operator overload. See below. |
Custom decorators can be given meaning by using a transform.
Operator overloads
Operator overloads can only be used on class methods. The respective argument types must match the class type.
Binary operations
@operator(OP)
static __op(left: T, right :T): T { ... }
@operator(OP)
__op(right: T): T { ... }| OP | Description |
|---|---|
"[]" |
Checked indexed get |
"[]=" |
Checked indexed set |
"{}" |
Unchecked indexed get |
"{}=" |
Unchecked indexed set |
"==" |
Equality |
"!=" |
Inequality |
">" |
Greater than |
">=" |
Greater than or equal |
"<" |
Less than |
"<=" |
Less than or equal |
">>" |
Arithmetic right shift |
">>>" |
Logical right shift |
"<<" |
Left shift |
"&" |
Bitwise AND |
| `" | "` |
"^" |
Bitwise XOR |
"+" |
Addition |
"-" |
Subtraction |
"*" |
Multiplication |
"/" |
Division |
"**" |
Exponentiation |
"%" |
Remainder |
The === operation cannot be overloaded (is identity equality).
Unary prefix operations
@operator.prefix(OP)
static __op(self: T): T { ... }
@operator.prefix(OP)
__op(): T { ... }| OP | Description | Notes |
|---|---|---|
"!" |
Logical NOT | |
"~" |
Bitwise NOT | |
"+" |
Unary plus | |
"-" |
Unary negation | |
"++" |
Prefix increment | Instance overload reassigns |
"--" |
Prefix decrement | Instance overload reassigns |
::: tip
Note that increment and decrement overloads can have slightly different semantics. If the overload is declared as an instance method, on ++a the compiler does emit code that reassigns the resulting value to a while if the overload is declared static, the overload behaves like any other overload, skipping the otherwise implicit assignment.
:::
Unary postfix operations
@operator.postfix(OP)
static __op(self: T): T { ... }
@operator.postfix(OP)
__op(): T { ... }| OP | Description | Notes |
|---|---|---|
"++" |
Postfix increment | Instance overload reassigns |
"--" |
Postfix decrement | Instance overload reassigns |
::: tip
Overloaded postfix operations do not preserve the original value automatically.
:::
Range limits
The following range limits are present as global constants for convenience:
- ts
const i8.MIN_VALUE: i8 = -128 const i8.MAX_VALUE: i8 = 127 - ts
const i16.MIN_VALUE: i16 = -32768 const i16.MAX_VALUE: i16 = 32767 - ts
const i32.MIN_VALUE: i32 = -2147483648 const i32.MAX_VALUE: i32 = 2147483647 - ts
const i64.MIN_VALUE: i64 = -9223372036854775808 const i64.MAX_VALUE: i64 = 9223372036854775807 - ts
const isize.MIN_VALUE: isize // WASM32: i32.MIN_VALUE, WASM64: i64.MIN_VALUE const isize.MAX_VALUE: isize // WASM32: i32.MAX_VALUE, WASM64: i64.MAX_VALUE - ts
const u8.MIN_VALUE: u8 = 0 const u8.MAX_VALUE: u8 = 255 - ts
const u16.MIN_VALUE: u16 = 0 const u16.MAX_VALUE: u16 = 65535 - ts
const u32.MIN_VALUE: u32 = 0 const u32.MAX_VALUE: u32 = 4294967295 - ts
const u64.MIN_VALUE: u64 = 0 const u64.MAX_VALUE: u64 = 18446744073709551615 - ts
const usize.MIN_VALUE: usize = 0 const usize.MAX_VALUE: usize // WASM32: u32.MAX_VALUE, WASM64: u64.MAX_VALUE - ts
const bool.MIN_VALUE: bool = 0 const bool.MAX_VALUE: bool = 1 - ts
const f32.MIN_VALUE: f32 = -3.40282347e+38 const f32.MAX_VALUE: f32 = 3.40282347e+38 const f32.MIN_SAFE_INTEGER: f32 = -16777215 const f32.MAX_SAFE_INTEGER: f32 = 16777215 const f32.EPSILON: f32 = 1.19209290e-07 - ts
const f64.MIN_VALUE: f64 = -1.7976931348623157e+308 const f64.MAX_VALUE: f64 = 1.7976931348623157e+308 const f64.MIN_SAFE_INTEGER: f64 = -9007199254740991 const f64.MAX_SAFE_INTEGER: f64 = 9007199254740991 const f64.EPSILON: f64 = 2.2204460492503131e-16
Tree-shaking
Module-level
The compiler only compiles elements reachable from the entry file and ignores everything that remains unused. This is very similar to a JavaScript VM's behavior when running a JavaScript file, but unlike in TypeScript it also affects checking of types. Helps to reduce compilation times and binary sizes significantly.
Branch-level
In addition to module-level tree-shaking, the compiler ignores branches that it can prove won't be taken. Works with constants, built-ins that compile to a constant, expressions that can be precomputed to a constant, plus the following globals to detect specific compiler flags or features:
- ts
const ASC_TARGET: i32Indicates the compilation target. Possible values are 0 = JS (portable), 1 = WASM32, 2 = WASM64.
- ts
const ASC_NO_ASSERT: boolWhether
--noAsserthas been set. - ts
const ASC_MEMORY_BASE: usizeThe value of
--memoryBase. - ts
const ASC_OPTIMIZE_LEVEL: i32The value of
--optimizeLevel. Possible values are 0, 1, 2 and 3. - ts
const ASC_SHRINK_LEVEL: i32The value of
--shrinkLevel. Possible values are 0, 1 and 2. - ts
const ASC_LOW_MEMORY_LIMIT: i32The value of
--lowMemoryLimit. - ts
const ASC_EXPORT_RUNTIME: i32Whether
--exportRuntimehas been set. - ts
const ASC_FEATURE_SIGN_EXTENSION: bool const ASC_FEATURE_MUTABLE_GLOBALS: bool const ASC_FEATURE_NONTRAPPING_F2I: bool const ASC_FEATURE_BULK_MEMORY: bool const ASC_FEATURE_SIMD: bool const ASC_FEATURE_THREADS: bool const ASC_FEATURE_EXCEPTION_HANDLING: bool const ASC_FEATURE_TAIL_CALLS: bool const ASC_FEATURE_REFERENCE_TYPES: bool const ASC_FEATURE_MULTI_VALUE: bool const ASC_FEATURE_GC: bool const ASC_FEATURE_MEMORY64: boolWhether the respective feature is enabled.
For example, if a library supports SIMD but also wants to provide a fallback when being compiled without SIMD support:
tsif (ASC_FEATURE_SIMD) { // compute with SIMD operations } else { // fallback without SIMD operations } - ts
const ASC_WASI: i32A constant only being defined when WASI bindings are used via
import "wasi". Can be checked withisDefined(ASC_WASI).
File: src/portability.md
Portability
With AssemblyScript using a stricter variant of TypeScript's syntax with a few semantic differences, there comes the opportunity to compile the same code to JavaScript with tsc and WebAssembly with asc. The AssemblyScript compiler itself is portable code.
Portable Stdlib
Besides the full standard library, AssemblyScript provides a portable variant of the functionality that is present in both JavaScript and WebAssembly. In addition to that, the portable library lifts some of the functionality that is only available with asc to JavaScript, like the portable conversions mentioned below.
To use the portable library, add the following somewhere along your build step so the portable features are present in the environment
require("assemblyscript/std/portable")and extend assemblyscript/std/portable.json instead of assemblyscript/std/assembly.json within your tsconfig.json. The AssemblyScript compiler itself is an example of how this can be done.
::: tip
The portable standard library is still pretty much a work in progress and we are extending it as we go while working on the compiler. If you need something specific, feel free to improve its definitions and feature set.
:::
Differences
There are a few semantics differences that must be taken care of.
Conversions
While asc understands the meaning of
// non-portable
let someFloat: f32 = 1.5
let someInt: i32 = <i32>someFloatand then inserts the correct conversion steps, tsc does not because all numeric types are just aliases of number. Hence, when targeting JavaScript with tsc, the above will result in
var someFloat = 1.5
var someInt = someFloatwhich is obviously wrong. To account for this, portable conversions can be used, resulting in actually portable code. For example
// portable
let someFloat: f32 = 1.5
let someInt: i32 = i32(someFloat)will essentially result in
var someFloat = 1.5
var someInt = someFloat | 0which is correct. The best way of dealing with this is asking yourself the question: What would this code do when compiled to JavaScript?
Overflows
Likewise, again because asc knows the meaning but tsc does not, overflows must be handled explicitly:
// non-portable
let someU8: u8 = 255
let someOtherU8: u8 = someU8 + 1// portable
let someU8: u8 = 255
let someOtherU8: u8 = u8(someU8 + 1)essentially resulting in
let someU8 = 255
let someOtherU8 = (someU8 + 1) & 0xffAPI features
In JavaScript, some parts of the standard library function a little more loosely than how they would when compiling to WebAssembly. While the portable definitions try to take care of this, one example where this can happen is Map#get returning undefined when a key cannot be found in JavaScript, while resulting in an abort in WebAssembly, where it is necessary to first check that the key exists using Map#has. The best way of dealing with this is asking yourself the question: What would this code do when compiled to WebAssembly?
Limitations
In JavaScript, all numeric values are IEEE754 doubles that cannot represent the full range of values fitting in a 64-bit integer (max. safe integer is 2^53 - 1). Hence i64 and u64 are not portable and not present in std/portable. There are several ways to deal with this. One is to use an i64 polyfill like in this example.
Other than that, portable code (JavaScript) does not have a concept of memory, so there are no load and store implementations. Technically this can be polyfilled in various ways, but no default is provided since actual implementations are expected to be relatively specific (for instance: the portable compiler accesses Binaryen's memory).
File: src/quick-start.md
sidebarDepth: 0
Quick Start
Paving the way to what might be your first AssemblyScript module.
Prerequisites
The following assumes that a recent version of Node.js and its package manager npm (comes with Node.js) are installed, with the commands below executed in a command prompt. Basic knowledge about writing and working with TypeScript modules, which is very similar, is a plus.
Setting up a new project
To get started with AssemblyScript, switch to a new directory and initialize a new node module:
npm initNow install both the loader and the compiler using npm. Let's assume that the compiler is not required in production and make it a development dependency:
npm install --save @assemblyscript/loader
npm install --save-dev assemblyscript::: tip
If you need a specific version of the loader and/or the compiler, append the respective version number as usual.
:::
Once installed, the compiler provides a handy scaffolding utility to quickly set up a new AssemblyScript project, for example in the directory of the just initialized node module:
npx asinit .It automatically creates the recommended directory structure and configuration files:
This command will make sure that the following files exist in the project
directory '/path/to/mymodule':
./assembly
Directory holding the AssemblyScript sources being compiled to WebAssembly.
./assembly/tsconfig.json
TypeScript configuration inheriting recommended AssemblyScript settings.
./assembly/index.ts
Example entry file being compiled to WebAssembly to get you started.
./build
Build artifact directory where compiled WebAssembly files are stored.
./build/.gitignore
Git configuration that excludes compiled binaries from source control.
./index.js
Main file loading the WebAssembly module and exporting its exports.
./tests/index.js
Example test to check that your module is indeed working.
./asconfig.json
Configuration file defining both a 'debug' and a 'release' target.
./package.json
Package info containing the necessary commands to compile to WebAssembly.
Do you want to proceed? [Y/n]Once initialized, edit the sources in assembly/, tweak compiler options in asconfig.json to fit your needs, and run the build command to compile your module to WebAssembly:
npm run asbuildNext steps
Once compiled, you may run the tests in tests/index.js:
npm testadd imports to the generated index.js (instantiates the module and re-exports it):
...
const imports = {
"assembly/index": {
declaredImportedFunction: function(...) { ... }
}
};
...and ultimately use your WebAssembly module like a normal node module:
const myModule = require("path/to/mymodule");
myModule.add(1, 2);::: tip NOTE
Your module's exports only understand integers and floats for now, with strings and objects being passed as pointers, but we'll get into that later when covering the loader.
:::
Read on to learn more!
File: src/runtime.md
Runtime
::: danger WARNING
This section is OUTDATED and covers version 0.17 of the AssemblyScript compiler. It has been superseded by the new runtime introduced in 0.18 that is now described under Garbage collection.
Migration from AssemblyScript 0.17 to 0.18 requires re-evaluating uses of __retain and __release, since these have been replaced with __pin and __unpin with slightly different semantics. An object can now be exactly pinned or not pinned, but cannot be pinned multiple times, and WebAssembly exports do not have a special case of pre-retaining objects for the caller anymore. Also, runtime names have changed, and instead of distinguishing between full (runtime exported) and half (runtime not exported), there's now an option --exportRuntime to achieve the effect with any runtime variant.
:::
Documentation for AssemblyScript 0.17:
AssemblyScript's runtime takes care of all the ins and outs of memory management and garbage collection, yet the compiler lets a developer choose the ideal runtime variant for their use case.
Variants
| Variant | Description |
|---|---|
| full | A proper memory manager and reference-counting based garbage collector, with runtime interfaces being exported to the host for being able to create managed objects externally. |
| half | The same as full but without any exports, i.e. where creating objects externally is not required. This allows the optimizer to eliminate parts of the runtime that are not needed. |
| stub | A minimalist arena memory manager without any means of freeing up memory again, but the same external interface as full. Useful for very short-lived programs or programs with hardly any memory footprint, while keeping the option to switch to full without any further changes. No garbage collection. |
| none | The same as stub but without any exports, for the same reasons as explained in half. Essentially evaporates entirely after optimizations. |
The default runtime included in a program is the full runtime, but deciding for another variant using the --runtime option can help to reduce binary size significantly. The full runtime adds about 2KB of additional optimized code to your module, which is not that much considering what it brings to the table, but might still be overkill for simple programs.
::: tip
Previous versions of the compiler (pre 0.7) did not include any runtime functionality by default but instead required importing an allocator and potentially an experimental tracing collector. This is not supported anymore by recent versions of the compiler.
:::
Interface
The following paragraphs are relevant in low-level code respectively when working with objects externally only. In normal high-level code, the compiler utilizes these mechanisms automatically.
Allocating managed objects
When allocating a managed object, it is necessary to also provide its unique class id so the runtime can properly recognize it. The unique id of any managed type can be obtained via idof(). Each concrete class (like String, Array, Array<f32>) has its own id. The ids of ArrayBuffer (id=0), String (id=1) and ArrayBufferView (id=2) are always the same, while all other ids are generated sequentially on first use of a class and differ between modules. Hence, it is usually necessary to export Uint8Array_ID = idof() for example when allocating one externally. The relevant interface is:
- Dynamically allocates an object represented by the specified id of at least the given size in bytes and returns its address. Alignment is guaranteed to be 16 bytes to fit up to v128 values naturally. Does not zero memory.ts
function __new(size: usize, id: u32): usize
The loader provides some additional functionality for convenience, like __newString.
Managing lifetimes
The concept is simple: If a reference to an object is established, the reference count of the object is increased by 1 (a reference is retained), and when a reference to an object is deleted, the reference count of the object is decreased by 1 (a reference is released). If the reference count of an object reaches 0, it is considered for collection and its memory ultimately returned to the memory manager for reuse. The relevant interface is:
- ts
function __retain(ptr: usize): usizeRetains a reference to the object pointed to by
ptr. The object doesn't become collected as long as there's at least one retained reference to it. Returns the pointer. - ts
function __release(ptr: usize): voidReleases a reference to the object pointer to by
ptr. The object is considered for collection once all references to it have been released.
The compiler inserts retain and release calls automatically and this is opaque to a user on a higher level. On a lower level, for instance when dealing with managed objects externally, it is necessary to understand and adhere to the rules the compiler applies.
{% hint style="info" %}
Technically, both __retain and __release are nops when using the stubruntime, so one can either decide to skip the following for good or spend a little extra time to account for the possibility of upgrading to full later on.
{% endhint %}
Rules
- A reference to an object is retained when assigning it to a target (local, global, field or otherwise inserting it into a structure) of a reference type, with the exceptions stated in (3).
- A reference to an object is released when assigning another object to a target of a reference type previously retaining a reference to it, or if the lifetime of the local or structure currently retaining a reference to an object ends.
- A reference to an object is not released but remains retained when returning a reference typed object from a call (function, getter, constructor or operator overload). Instead, the caller is expected to perform the release. This also means:
- If a reference to the object would be immediately retained by assigning the object to a target of a reference type, the compiler will not retain it twice, but skip retaining it on assignment.
- If a reference to the object will not be immediately retained, the compiler will insert a temporary local into the current scope that autoreleases the reference to the object at the end of the scope.
::: tip
Objects created by calling __new start with a reference count of 0. This is not the case for constructors, these behave like calls. Built-ins like store<T> emit instructions directly and don't behave like calls.
:::
Working with references externally
Working with objects through imports and exports, like when using the loader, is relatively straight-forward. However, if not handled properly, the program will either leak memory, free objects prematurely or even break. So here's some advice:
- Always
__retaina reference to manually__new'ed objects and__releasethe reference again when done with the object. - Always
__releasethe reference to an object that was a return value of a call (see above) when done with it. It is not necessary to__retaina reference to returned objects. - Always
__retaina reference to an object read from memory, and__releasethe reference again when done with the object.
Working with references internally
Working with objects internally, like when creating custom standard library components or otherwise writing low-level code, requires special care because switching between pointers and reference types can become quite tricky. The internal interface also provides additional utility that is only relevant in very specific cases.
::: tip
One common point of confusion here is that the rules above operate on types, not values. Means: If the target is of a reference-type, the rules apply, but if the target is of an usize type, the rules do not apply, even if the value is a changetype(..)'d object.
:::
{
let ref = new ArrayBuffer(10) // retains (reference type)
let buf = changetype<usize>(ref) // does not retain (usize)
...
// compiler will automatically __release(ref)
}{
let buf = changetype<usize>(new ArrayBuffer(10)) // does not retain (usize)
// inserts a temporary, because _the object_ is not immediately assigned
...
// compiler will automatically __release(theTemp)
}{
let ref = changetype<ArrayBuffer>(__new(10, idof<ArrayBuffer>())
// retains on ref, because after changetype an object is assigned
...
// compiler will automatically __release(ref)
}Collecting garbage
By default, the full and half runtime will automatically try to collect cyclic garbage when memory must be grown. This behavior can be disabled by setting gc.auto = false in performance critical code. Likewise, if there is a good opportunity to collect cyclic garbage at a given point in time, like if the application is idle, gc.collect() can be called to force a full garbage collection cycle. Protip: If no cyclic structures are used, no garbage must be collected.
Implementation
The memory manager used by AssemblyScript is a variant of TLSF (Two-Level Segregate Fit memory allocator) and it is accompanied by PureRC (a variant of A Pure Reference Counting Garbage Collector) with slight modifications of assumptions to avoid unnecessary work. Essentially, TLSF is responsible for partitioning dynamic memory into chunks that can be used by the various objects, while PureRC keeps track of their lifetimes.
Performance considerations
Both the TLSF memory manager and the concept of reference counting are a good fit for predictable performance scenarios. Remember: WebAssembly is about exactly that. It is not necessarily the fastest choice in every possible scenario, though.
It is also likely that our implementations are not as optimized yet as ultimately possible. If you are smarter than us, please let us know of your thoughts and findings.
Internals
If you are interested in the inner workings, the internal APIs are explained in the runtime's README file and of course in its sources - feel free to take a look!
Runtime type information \(RTTI\)
Every module using managed objects contains a memory segment with basic type information, that the loader for example uses when allocating new arrays. Internally, RTTI is used to perform dynamic instanceof checks and to determine whether a class is inherently acyclic. The memory offset of RTTI can be obtained by reading the __rtti_base global. Essentially, the compiler maps every concrete class to a unique id, starting with 0 (=ArrayBuffer), 1 (=String) and 2 (=ArrayBufferView) . For each such class, the compiler remembers the id of the respective base class, if any, and a set of flags describing the class. Flags for example contain information about key and value alignments, whether a class is managed and so on. Structure is like this:
| Name | Offset | Type | Description |
|---|---|---|---|
| #count | 0 | u32 | Number of concrete classes |
| #flags[0] | 4 | u32 | Flags describing the class with id=0 |
| #base[0] | 8 | u32 | Base class id of the class with id=0 |
| #flags[1] | 12 | u32 | ... etc. ... |
Flag values are currently still in flux, but if you are interested in these, feel free to take a look at the sources.
Notes
Unlike other reference counting implementions, allocating an object starts with a reference count of 0. This is useful in standard library code where memory is first allocated and then assigned to a variable, establishing the first reference.
The compiler does some extra work to evaluate whether an object can potentially become part of a reference cycle. Any object that cannot directly or indirectly reference another object of its kind is considered inherently acyclic, so it does not have to be added to the cycle buffer for further evaluation by deferred garbage collection.
Due to the lack of random access to WebAssembly's execution stack (remember: AssemblyScript does not have a stack on its own), values returned from a function become pre-retained for the caller, expecting that it will release the reference at the end, or give the reference to its own caller. This also leads to situations where sometimes a reference must be retained on assignment, and sometimes it must not. It also leads to situations where two branches must be unified, for example if there is a pre-retained value in one arm and one that is not in the other. The compiler is taking care of these situations, but it also requires special care when dealing with the reference counting internals within the standard library.
The compiler also utilizes a concept of so called autorelease locals. Essentially, if a reference enters a block of code from somewhere else, is pre-retained but not immediately assigned, such a local is added to the current scope to postpone the necessary release until the scope is exited.
One thing not particularly ideal at this point is that each function is expected to retain the reference-type arguments it is given, and release them again at the end. This is necessary because the compiler does just a single pass and doesn't know whether a variable becomes reassigned beforehand (no SSA or similar, that's left to Binaryen in an attempt to avoid duplicating its code). It does already skip this for certain variables like this that cannot be reassigned, but there are definitely more optimization opportunities along the way. Due to AssemblyScript essentially being a compiler on top of another compiler, it is somewhat unclear however where to perform such optimizations.
File: src/status.md
sidebarDepth: 2
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 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 integration | ✔️ | 64-bit integer interop | |
| Non-trapping F2I | ⏳ | Checked and unchecked casts | |
| Sign-extension | ⏳ sign-extension |
Efficient small integer casts | |
| Multi-value | Tuple return values? | ||
| 🏁 Standardize the feature | |||
| Reference Types | ⏳ reference-types |
Prerequisite for garbage collection | |
| Bulk memory | ⏳ bulk-memory |
Replace memcpy, memset |
|
| 🔨 Implementation phase | |||
| Tail call | |||
| Fixed-width SIMD | ⏳ simd |
Expose as built-ins; Auto-vectorize? | |
| Multiple memories | |||
| Custom annotations | |||
| 📖 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 | ||
| Memory64 | ⏳ | Provide a Wasm64 target | |
| 💡 Feature proposal | |||
| Type Imports | Web interop? | ||
| Garbage collection | Reuse host GC; Share objects? | ||
| Interface Types | Non-web interop? | ||
| Feature detection | |||
| Extended name section | ⏳ | Debug names for locals etc. | |
| Flexible vectors | Expose as built-ins | ||
| Call Tags | |||
| Module Linking | Linking pre-compiled modules | ||
| Branch hinting | likely(x), unlikely(x)? |
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
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.
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 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. 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
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\
- ts
function keys(): Array<K> - ts
function values(): Array<V>
Set\
- ts
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
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.
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 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:
File: src/transforms.md
Transforms
AssemblyScript is compiled statically, so code transformation cannot be done at runtime but must instead be performed at compile-time. To enable this, the compiler frontend (asc) provides a mechanism to hook into the compilation process before, while and after the module is being compiled.
Specifying --transform ./myTransform on the command line will load the node module pointed to by ./myTransform.
const { Transform } = require("assemblyscript/cli/transform")
const assemblyscript = require("assemblyscript")
class MyTransform extends Transform {
...
}
module.exports = MyTransformProperties
A transform is an ES6 class/node module with the following inherited properties:
- ts
readonly program: ProgramReference to the
Programinstance. - ts
readonly baseDir: stringBase directory used by the compiler.
- ts
readonly stdout: OutputStreamOutput stream used by the compiler.
- ts
readonly stderr: OutputStreamError stream uses by the compiler.
- ts
readonly log: typeof console.logLogs a message to console.
- ts
function writeFile(filename: string, contents: string | Uint8Array, baseDir: string): booleanWrites a file to disk.
- ts
function readFile(filename: string, baseDir: string): string | nullReads a file from disk.
- ts
function listFiles(dirname: string, baseDir: string): string[] | nullLists all files in a directory.
Hooks
The frontend will call several hooks, if present on the transform, during the compilation process:
- ts
function afterParse(parser: Parser): voidCalled when parsing is complete, before a program is initialized from the AST. Note that types are not yet known at this stage and there is no easy way to obtain them.
- ts
function afterInitialize(program: Program): voidCalled once the program is initialized, before it is being compiled. Types are known at this stage, respectively can be resolved where necessary.
- ts
function afterCompile(module: Module): voidCalled with the resulting module before it is being emitted. Useful to modify the IR before writing any output, for example to replace imports with actual functionality or to add custom sections.
Transforms are a very powerful feature, but may require profound knowledge of the compiler to utilize them to their full extent, so reading through the compiler sources is a plus.
File: src/types.md
Types
Instead of using the number type for all sorts of numeric values, AssemblyScript inherits WebAssembly's more specific integer and floating point types:
| AssemblyScript Type | WebAssembly type | Description |
|---|---|---|
i32 |
i32 | A 32-bit signed integer. |
u32 |
i32 | A 32-bit unsigned integer. |
i64 |
i64 | A 64-bit signed integer. |
u64 |
i64 | A 64-bit unsigned integer. |
f32 |
f32 | A 32-bit float. |
f64 |
f64 | A 64-bit float. |
v128 |
v128 | A 128-bit vector 🦄. |
anyref |
anyref | An opaque host reference 🦄. |
| Small integer types | ||
i8 |
i32 | An 8-bit signed integer. |
u8 |
i32 | An 8-bit unsigned integer. |
i16 |
i32 | A 16-bit signed integer. |
u16 |
i32 | A 16-bit unsigned integer. |
bool |
i32 | A 1-bit unsigned integer. |
| Variable integer types | ||
isize |
i32 or i64 | A 32-bit signed integer in WASM32.A 64-bit signed integer in WASM64 🦄. |
usize |
i32 or i64 | A 32-bit unsigned integer in WASM32.A 64-bit unsigned integer in WASM64 🦄. |
| Special types | ||
void |
- | Indicates no return value. |
auto |
? | Makes an educated guess. Internal only. |
Type rules
AssemblyScript will complain when it sees an implicit conversion that might not actually be intended, quite similar to what a C compiler would do.
Casting
In AssemblyScript, the type assertions <T>expression and expression as T known from TypeScript become explicit type conversions, essentially telling the compiler that the conversion is intended. In addition, each of the type names mentioned above, except aliases, also act as portable conversion built-ins that can be used just like i32(expression). Using portable conversions is especially useful where the exact same code is meant to be compiled to JavaScript with the TypeScript compiler (see), that otherwise would require the insertion of asm.js-style type coercions like expression | 0.
Inference
Compared to TypeScript, type inference in AssemblyScript is limited because the type of each expression must be known in advance. This means that variable and parameter declarations must either have their type annotated or have an initializer. Without a type annotation and only an initializer, AssemblyScript will assume i32 at first and only reconsider another type if the value doesn't fit (becomes i64), is a float (becomes f64) or irrefutably has another type than these, like the type of a variable, the return type of a function or a class type. Furthermore, functions must be annotated with a return type to help the compiler make the correct decisions, for example where a literal is returned or multiple return statements are present.
Nullability
Basic types cannot be nullable, but class and function types can. Appending | null declares a nullable type.
Assignability
Assigning a value of one type to a target of another type can be performed without explicit casts where the full range of possible values can be represented in the target type, regardless of interpretation/signedness:
| ↱ | bool | i8/u8 | i16/u16 | i32/u32 | i64/u64 | f32 | f64 |
|---|---|---|---|---|---|---|---|
| bool | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| i8/u8 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
| i16/u16 | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| i32/u32 | ✓ | ✓ | ✓ | ||||
| i64/u64 | ✓ | ||||||
| f32 | ✓ | ✓ | |||||
| f64 | ✓ |
Note that isize and usize are aliases of either i32 and u32 in WASM32 respectively i64 and u64 in WASM64 🦄.
var i8val: i8 = -128 // 0x80
var u8val: u8 = i8val // becomes 128 (0x80)
var i16val: i16 = i8val // becomes -128 through sign-extension (0xFF80)
var u16val: u16 = i8val // becomes 65408 through masking (0xFF80)
var f32val: f32 = i8val // becomes -128.0Comparability
Comparing two values of different types can be performed without an explicit cast under the same rules as outlined in assignability above
- if the comparison is absolute (
==,!=) - if the comparison is relative (
>,<,>=,<=) and both types have the same signedness
because WebAssembly has distinct operations for signed and unsigned comparisons. The comparison uses the larger type and returns bool.
Bit shifts
The result of a bit shift (<<, >>) is the left type, with the right type implicitly converted to the left type, performing an arithmetic shift if the left type is signed and a logical shift if the left type is unsigned.
The result of an unsigned right shift (>>>) is the left type (signedness is retained), with the right type implicitly converted to the left type, but always performing a logical shift.
Note that only the log2(sizeof<T>()) least signficant bits of the shift affect the result:
| Type | Significant bits | Example |
|---|---|---|
| i8 / u8 | 3 | x & y ≡ x & (y & 7) |
| i16 / u16 | 4 | x & y ≡ x & (y & 15) |
| i32 / u32 | 5 | x & y ≡ x & (y & 31) |
| i64 / u64 | 6 | x & y ≡ x & (y & 63) |
If the left type is a float, an error is emitted.
Macro types
The following macro types provide access to related types that would otherwise be impossible to obtain.
| Macro type | Description |
|---|---|
native<T> |
Obtains the underlying native type of T, e.g. u32 if T is a class (in WASM32). |
indexof<T> |
Obtains the index type of a collection based on the indexed access overload. |
valueof<T> |
Obtains the value type of a collection based on the indexed access overload. |
returnof<T> |
Obtains the return type of a function type. |
File: src/examples/arrays.md
Arrays example
Shows how to exchange and work with arrays either created in WebAssembly or in JavaScript.
Contents
- Using the loader and the full runtime to work with managed objects.
- Creating arrays in WebAssembly and using them in JavaScript.
- Creating arrays in JavaScript and using them in WebAssembly.
- Using both copies of and live views on arrays.
- Performing
uncheckedaccesses where the length of an array is known. - Pinning objects externally to prevent premature garbage collection.
Example
#!optimize=speed&runtime=default&exportRuntime
/** Creates a new array and returns it to JavaScript. */
export function createArray(length: i32): Int32Array {
return new Int32Array(length)
}
/** Randomizes the specified array's values. */
export function randomizeArray(arr: Int32Array): void {
for (let i = 0, k = arr.length; i < k; ++i) {
let value = i32((Math.random() * 2.0 - 1.0) * i32.MAX_VALUE)
unchecked(arr[i] = value)
}
}
/** Computes the sum of an array's values and returns the sum to JavaScript. */
export function sumArray(arr: Int32Array): i32 {
let total = 0
for (let i = 0, k = arr.length; i < k; ++i) {
total += unchecked(arr[i])
}
return total
}
// We'll need the unique Int32Array id when allocating one in JavaScript
export const Int32Array_ID = idof<Int32Array>()
#!html
<textarea id="output" style="width: 100%; height: 100%" readonly></textarea>
<script>
loader.instantiate(module_wasm).then(({ exports }) => {
const output = document.getElementById('output')
/** Logs a message to the textarea. */
function log(message = '') {
output.value += `${message}\n`
}
// A simple example using an array created in WebAssembly.
function example1() {
log('=== Example1 ===')
// Obtain the necessary runtime helpers
const { __pin, __unpin, __getArray } = exports
// Create a new array in WebAssembly and get a reference to it. Note that
// the array is not reachable from within WebAssembly, only externally, so
// we should pin it to prevent it from becoming garbage collected too early.
let arrayPtr = __pin(exports.createArray(5))
log(`Array pointer: ${arrayPtr}`)
// Log its elements to make sure these are zero
log('Initial values: ' + __getArray(arrayPtr).join(', '))
// Randomize the array in WebAssembly and log it again
exports.randomizeArray(arrayPtr)
log('Randomized values: ' + __getArray(arrayPtr).join(', '))
// Compute the array values' sum and log it. This will overflow i32 range.
let total = exports.sumArray(arrayPtr)
log(`Sum (likely overflown): ${total}`)
// We are done with the array, so __unpin it so it can become collected.
__unpin(arrayPtr)
log()
}
example1()
// A slightly more advanced example allocating the array in JavaScript instead
// of WebAssembly, and utilizing a live view to modify it in WebAssembly memory.
function example2() {
log('=== Example2 ===')
// Obtain the necessary runtime helpers
const { __pin, __unpin, __newArray, __getArray, __getArrayView } = exports
// Create a new array, but this time in JavaScript. Note that the array is
// again not reachable from within WebAssembly, only externally, so we
// should pin it to prevent it from becoming garbage collected too early.
let arrayPtr = __pin(__newArray(exports.Int32Array_ID, [
3, 4, 5, 6, 7, 8, 9
]))
log('Array pointer: ' + arrayPtr)
// Log its elements to make sure these are the provided values
log('Initial values: ' + __getArray(arrayPtr).join(', '))
// Compute the array values' sum and log it
let total = exports.sumArray(arrayPtr)
log('Sum: ' + total)
// Instead of copying, let's obtain a live view on the array and modify its
// values right in WebAssembly memory.
let view = __getArrayView(arrayPtr)
view.reverse()
// Log the array's elements, now reversed
log('Reversed values: ' + __getArray(arrayPtr).join(', '))
// We are done with the array, so __unpin it so it can become collected.
__unpin(arrayPtr)
log()
}
example2()
})
</script>::: tip NOTE
This example utilizes the loader to work with managed objects, hence requires --exportRuntime to be set to expose the runtime helpers to JavaScript.
:::
Resources
Further information on using the loader and the runtime helpers is available as part of the loader's and the garbage collection documentation.
File: src/examples/game-of-life.md
Game of Life example
Continuously updates a slightly modified variant of Conway's Game of Life and visualizes its state on a canvas.
Contents
- Exporting functions from a WebAssembly module.
- Calling functions exported from WebAssembly.
- Importing configuration values from JavaScript.
- Instantiating the module's memory in JavaScript and import it using
--importMemory. - Speeding up a program by forcing helper functions in a hot path to always
@inline. - Utilizing JavaScript's
Mathinstead of native libm to reduce module size via--use Math=JSMath. - Reacting to user input by directly modifying an input image buffer.
- Finding out about WebAssembly's unintuitive byte order.
- And finally: Continuously updating an input to an output image buffer and rendering the output image buffer.
- Featuring: Clicking and drawing lots of stuff.
Example
#!optimize=speed&runtime=stub&importMemory&use=Math=JSMath
// Configuration imported from JS
declare const BGR_ALIVE: u32;
declare const BGR_DEAD: u32;
declare const BIT_ROT: u32;
var width: i32, height: i32, offset: i32;
/** Gets an input pixel in the range [0, s]. */
@inline
function get(x: u32, y: u32): u32 {
return load<u32>((y * width + x) << 2);
}
/** Sets an output pixel in the range [s, 2*s]. */
@inline
function set(x: u32, y: u32, v: u32): void {
store<u32>((offset + y * width + x) << 2, v);
}
/** Sets an output pixel in the range [s, 2*s] while fading it out. */
@inline
function rot(x: u32, y: u32, v: u32): void {
var alpha = max<i32>((v >> 24) - BIT_ROT, 0);
set(x, y, (alpha << 24) | (v & 0x00ffffff));
}
/** Initializes width and height. Called once from JS. */
export function init(w: i32, h: i32): void {
width = w;
height = h;
offset = w * h;
// Start by filling output with random live cells.
for (let y = 0; y < h; ++y) {
for (let x = 0; x < w; ++x) {
let c = Math.random() > 0.1
? BGR_DEAD & 0x00ffffff
: BGR_ALIVE | 0xff000000;
set(x, y, c);
}
}
}
/** Performs one step. Called about 30 times a second from JS. */
export function step(): void {
var w = width,
h = height;
var hm1 = h - 1, // h - 1
wm1 = w - 1; // w - 1
// The universe of the Game of Life is an infinite two-dimensional orthogonal grid of square
// "cells", each of which is in one of two possible states, alive or dead.
for (let y = 0; y < h; ++y) {
let ym1 = y == 0 ? hm1 : y - 1,
yp1 = y == hm1 ? 0 : y + 1;
for (let x = 0; x < w; ++x) {
let xm1 = x == 0 ? wm1 : x - 1,
xp1 = x == wm1 ? 0 : x + 1;
// Every cell interacts with its eight neighbours, which are the cells that are horizontally,
// vertically, or diagonally adjacent. Least significant bit indicates alive or dead.
let aliveNeighbors = (
(get(xm1, ym1) & 1) + (get(x, ym1) & 1) + (get(xp1, ym1) & 1) +
(get(xm1, y ) & 1) + (get(xp1, y ) & 1) +
(get(xm1, yp1) & 1) + (get(x, yp1) & 1) + (get(xp1, yp1) & 1)
);
let self = get(x, y);
if (self & 1) {
// A live cell with 2 or 3 live neighbors rots on to the next generation.
if ((aliveNeighbors & 0b1110) == 0b0010) rot(x, y, self);
// A live cell with fewer than 2 or more than 3 live neighbors dies.
else set(x, y, BGR_DEAD | 0xff000000);
} else {
// A dead cell with exactly 3 live neighbors becomes a live cell.
if (aliveNeighbors == 3) set(x, y, BGR_ALIVE | 0xff000000);
// A dead cell with fewer or more than 3 live neighbors just rots.
else rot(x, y, self);
}
}
}
}
/** Fills the row and column indicated by `x` and `y` with random live cells. */
export function fill(x: u32, y: u32, p: f64): void {
for (let ix = 0; ix < width; ++ix) {
if (Math.random() < p) set(ix, y, BGR_ALIVE | 0xff000000);
}
for (let iy = 0; iy < height; ++iy) {
if (Math.random() < p) set(x, iy, BGR_ALIVE | 0xff000000);
}
}
#!html
<canvas id="canvas" style="width: 100%; height: 100%; background: #000; cursor: crosshair"></canvas>
<script>
// Configuration
const RGB_ALIVE = 0xD392E6;
const RGB_DEAD = 0xA61B85;
const BIT_ROT = 10;
// Set up the canvas with a 2D rendering context
const canvas = document.getElementById("canvas");
const boundingRect = canvas.getBoundingClientRect();
const ctx = canvas.getContext("2d");
// Compute the size of the universe (2 pixels per cell)
const width = boundingRect.width >>> 1;
const height = boundingRect.height >>> 1;
const size = width * height;
const byteSize = (size + size) << 2; // input & output (4 bytes per cell)
canvas.width = width;
canvas.height = height;
canvas.style.imageRendering = "pixelated";
ctx.imageSmoothingEnabled = false;
// Compute the size of and instantiate the module's memory
const memory = new WebAssembly.Memory({ initial: ((byteSize + 0xffff) & ~0xffff) >>> 16 });
// Fetch and instantiate the module
loader.instantiate(module_wasm, {
env: {
memory
},
module: {
BGR_ALIVE : rgb2bgr(RGB_ALIVE) | 1, // LSB set indicates alive
BGR_DEAD : rgb2bgr(RGB_DEAD) & ~1, // LSB not set indicates dead
BIT_ROT
}
}).then(({ exports }) => {
// Initialize the module with the universe's width and height
exports.init(width, height);
var buffer = new Uint32Array(memory.buffer);
// Update about 30 times a second
(function update() {
setTimeout(update, 1000 / 30);
buffer.copyWithin(0, size, size + size); // copy output to input
exports.step(); // perform the next step
})();
// Keep rendering the output at [size, 2*size]
var imageData = ctx.createImageData(width, height);
var argb = new Uint32Array(imageData.data.buffer);
(function render() {
requestAnimationFrame(render);
argb.set(buffer.subarray(size, size + size)); // copy output to image buffer
ctx.putImageData(imageData, 0, 0); // apply image buffer
})();
// When clicked or dragged, fill the current row and column with random live cells
var down = false;
[ [canvas, "mousedown"],
[canvas, "touchstart"]
].forEach(eh => eh[0].addEventListener(eh[1], e => down = true));
[ [document, "mouseup"],
[document, "touchend"]
].forEach(eh => eh[0].addEventListener(eh[1], e => down = false));
[ [canvas, "mousemove"],
[canvas, "touchmove"],
[canvas, "mousedown"]
].forEach(eh => eh[0].addEventListener(eh[1], e => {
if (!down) return;
var loc;
if (e.touches) {
if (e.touches.length > 1) return;
loc = e.touches[0];
} else {
loc = e;
}
const currentBoundingRect = canvas.getBoundingClientRect();
exports.fill(
((loc.clientX - currentBoundingRect.left) / currentBoundingRect.width * boundingRect.width) >>> 1,
((loc.clientY - currentBoundingRect.top) / currentBoundingRect.height * boundingRect.height) >>> 1,
0.5
);
}));
});
/** Bitshifts an RGB color to BGR instead (WebAssembly is little endian). */
function rgb2bgr(rgb) {
return ((rgb >>> 16) & 0xff) | (rgb & 0xff00) | (rgb & 0xff) << 16;
}
</script>::: tip NOTE
The example makes a couple assumptions. For instance, using the entire memory of the program as the image buffer as in this example is only possible because we know that no interferring static memory segments will be created, which is achieved by
- using JavaScript's Math instead of native libm (usually adds lookup tables),
- not using a more sophisticated runtime (typically adds bookkeeping) and
- the rest of the example being relatively simple (i.e. no strings or similar).
As soon as these conditions are no longer met, one would instead either reserve some space by specifying a suitable --memoryBase or export a dynamically instantiated chunk of memory, like an Uint32Array, and utilize it as the input and output image buffers both in WebAssembly and in JavaScript.
:::
Running locally
Instructions are identical to those of the Mandelbrot example.
File: src/examples/interference.md
Interference example
Colin Eberhardt's and Ben Smith's WebAssembly interference effect, if it was written in AssemblyScript.
Contents
- Exporting functions and variables from a WebAssembly module.
- Calling functions and reading variables exported from WebAssembly.
- Utilizing 32-bit floating point math to speed up calculations by utilizing
Mathf. - Keeping an image buffer within the module's memory and copying it to a canvas.
- Manually growing memory depending on the size of the viewport on the browser side.
- And finally: Continuously updating and rendering the image buffer.
Example
::: danger EPILEPSY WARNING
A very small percentage of individuals may experience epileptic seizures when exposed to certain light patterns or flashing lights. Exposure to certain patterns or backgrounds on a computer screen may induce an epileptic seizure in these individuals. Certain conditions may induce previously undetected epileptic symptoms even in persons who have no history of prior seizures or epilepsy.
If you experience any of the following symptoms while viewing - dizziness, altered vision, eye or muscle twitches, loss of awareness, disorientation, any involuntary movement, or convulsions - IMMEDIATELY discontinue use and consult your physician.
:::
#!optimize=speed&runtime=stub
var width = 320;
var height = 200;
// Let's utilize the entire heap as our image buffer
export const offset = __heap_base;
/** Sets a single pixel's color. */
function set(x: i32, y: i32, v: f32): void {
var vi = <i32>v;
store<i32>(offset + ((width * y + x) << 2), ~vi << 24 | vi << 8);
}
/** Computes the distance between two pixels. */
function distance(x1: i32, y1: i32, x2: f32, y2: f32): f32 {
var dx = <f32>x1 - x2;
var dy = <f32>y1 - y2;
return Mathf.sqrt(dx * dx + dy * dy);
}
/** Performs one tick. */
export function update(tick: f32): void {
var w = <f32>width;
var h = <f32>height;
var hw = w * 0.5,
hh = h * 0.5;
var cx1 = (Mathf.sin(tick * 2) + Mathf.sin(tick )) * hw * 0.3 + hw,
cy1 = (Mathf.cos(tick) ) * hh * 0.3 + hh,
cx2 = (Mathf.sin(tick * 4) + Mathf.sin(tick + 1.2)) * hw * 0.3 + hw,
cy2 = (Mathf.sin(tick * 3) + Mathf.cos(tick + 0.1)) * hh * 0.3 + hh;
var res = <f32>48 / Mathf.max(w, h);
var y = 0;
do {
let x = 0;
do {
set(x, y, Mathf.abs(
Mathf.sin(distance(x, y, cx1, cy1) * res) +
Mathf.sin(distance(x, y, cx2, cy2) * res)
) * 120);
} while (++x != width)
} while (++y != height)
}
/** Recomputes and potentially grows memory on resize of the viewport. */
export function resize(w: i32, h: i32): void {
width = w; height = h;
// Pages are 64kb. Rounds up using mask 0xffff before shifting to pages.
var needed = <i32>((offset + (w * h * sizeof<i32>() + 0xffff)) & ~0xffff) >>> 16;
var actual = memory.size();
if (needed > actual) memory.grow(needed - actual);
}
#!html
<canvas id="canvas" style="width: 100%; height: 100%; background: #aff"></canvas>
<script>
var step = 0.012;
loader.instantiate(module_wasm).then(({ exports }) => {
const canvas = document.getElementById("canvas");
const context = canvas.getContext("2d");
// Upscale the image to speed up calculations
const upscaleFactor = 4;
var width, height, image;
// Inform the module about the viewport's size, incl. on resize
function onresize() {
width = (canvas.offsetWidth / upscaleFactor) | 0;
height = (canvas.offsetHeight / upscaleFactor) | 0;
canvas.width = width;
canvas.height = height;
image = context.createImageData(width, height);
exports.resize(width, height);
}
onresize();
new ResizeObserver(onresize).observe(canvas);
// Keep updating the image
var tick = 0.0;
(function update() {
requestAnimationFrame(update);
exports.update(tick += step);
new Uint32Array(image.data.buffer).set(new Uint32Array(exports.memory.buffer, exports.offset, width * height));
context.putImageData(image, 0, 0);
})();
});
</script>::: tip NOTE
The example makes one important assumption: Since we are not using a sophisticated runtime, we can instead repurpose the entire heap, starting at __heap_base, as our image buffer.
As soon as this condition is no longer met, one would instead either reserve some space by specifying a suitable --memoryBase or export a dynamically instantiated chunk of memory, like an Uint32Array, and utilize it as the image buffer both in WebAssembly and in JavaScript.
:::
Running locally
Set up a new AssemblyScript project as described in Quick start and copy module.ts to assembly/index.ts and index.html to the project's top-level directory. Edit the build commands in package.json to include
--runtime noneThe example can now be compiled with
npm run asbuildTo view the example, one can modify the instantiation in index.html from
loader.instantiate(module_wasm).then(({ exports }) => {to
WebAssembly.instantiateStreaming(fetch('./build/optimized.wasm'), {
JSMath: Math
}).then(({ exports }) => {because using the loader is not ultimately necessary here (no managed objects are exchanged). If the loader is used instead, it will automatically provide JSMath.
Some browsers may restrict fetching local resources when just opening index.html now, but one can set up a local server as a workaround:
npm install --save-dev http-server
http-server . -o -c-1File: src/examples/mandelbrot.md
Mandelbrot example
Renders the Mandelbrot set to a canvas using 2048 discrete color values computed on the JS side.
Contents
- Exporting functions from a WebAssembly module.
- Calling functions exported from WebAssembly.
- Instantiating the module's memory in JavaScript and import it using
--importMemory. - Utilizing JavaScript's
Mathinstead of native libm to reduce module size via--use Math=JSMath. - And finally: Reading and translating data from WebAssembly memory to colors rendered to a canvas.
Example
#!optimize=speed&runtime=stub&importMemory&use=Math=JSMath
/** Number of discrete color values on the JS side. */
const NUM_COLORS = 2048;
/** Updates the rectangle `width` x `height`. */
export function update(width: u32, height: u32, limit: u32): void {
var translateX = width * (1.0 / 1.6);
var translateY = height * (1.0 / 2.0);
var scale = 10.0 / min(3 * width, 4 * height);
var realOffset = translateX * scale;
var invLimit = 1.0 / limit;
var minIterations = min(8, limit);
for (let y: u32 = 0; y < height; ++y) {
let imaginary = (y - translateY) * scale;
let yOffset = (y * width) << 1;
for (let x: u32 = 0; x < width; ++x) {
let real = x * scale - realOffset;
// Iterate until either the escape radius or iteration limit is exceeded
let ix = 0.0, iy = 0.0, ixSq: f64, iySq: f64;
let iteration: u32 = 0;
while ((ixSq = ix * ix) + (iySq = iy * iy) <= 4.0) {
iy = 2.0 * ix * iy + imaginary;
ix = ixSq - iySq + real;
if (iteration >= limit) break;
++iteration;
}
// Do a few extra iterations for quick escapes to reduce error margin
while (iteration < minIterations) {
let ixNew = ix * ix - iy * iy + real;
iy = 2.0 * ix * iy + imaginary;
ix = ixNew;
++iteration;
}
// Iteration count is a discrete value in the range [0, limit] here, but we'd like it to be
// normalized in the range [0, 2047] so it maps to the gradient computed in JS.
// see also: http://linas.org/art-gallery/escape/escape.html
let colorIndex = NUM_COLORS - 1;
let distanceSq = ix * ix + iy * iy;
if (distanceSq > 1.0) {
let fraction = Math.log2(0.5 * Math.log(distanceSq));
colorIndex = <u32>((NUM_COLORS - 1) * clamp<f64>((iteration + 1 - fraction) * invLimit, 0.0, 1.0));
}
store<u16>(yOffset + (x << 1), colorIndex);
}
}
}
/** Clamps a value between the given minimum and maximum. */
function clamp<T>(value: T, minValue: T, maxValue: T): T {
return min(max(value, minValue), maxValue);
}
#!html
<canvas id="canvas" style="width: 100%; height: 100%"></canvas>
<script>
// Set up the canvas with a 2D rendering context
const canvas = document.getElementById("canvas");
const boundingRect = canvas.getBoundingClientRect();
const ctx = canvas.getContext("2d");
// Compute the size of the viewport
const ratio = window.devicePixelRatio || 1;
const width = (boundingRect.width | 0) * ratio;
const height = (boundingRect.height | 0) * ratio;
const size = width * height;
const byteSize = size << 1; // discrete color indices in range [0, 2047] (2 bytes per pixel)
canvas.width = width;
canvas.height = height;
ctx.scale(ratio, ratio);
// Compute the size (in pages) of and instantiate the module's memory.
// Pages are 64kb. Rounds up using mask 0xffff before shifting to pages.
const memory = new WebAssembly.Memory({ initial: ((byteSize + 0xffff) & ~0xffff) >>> 16 });
const buffer = new Uint16Array(memory.buffer);
const imageData = ctx.createImageData(width, height);
const argb = new Uint32Array(imageData.data.buffer);
loader.instantiate(module_wasm, {
env: {
memory
}
}).then(({ exports }) => {
// Update state
exports.update(width, height, 40);
// Translate 16-bit color indices to colors
for (let y = 0; y < height; ++y) {
const yx = y * width;
for (let x = 0; x < width; ++x) {
argb[yx + x] = colors[buffer[yx + x]];
}
}
// Render the image buffer.
ctx.putImageData(imageData, 0, 0);
});
/** Computes a nice set of colors using a gradient. */
function computeColors() {
const canvas = document.createElement("canvas");
canvas.width = 2048;
canvas.height = 1;
const ctx = canvas.getContext("2d");
const grd = ctx.createLinearGradient(0, 0, 2048, 0);
grd.addColorStop(0.00, "#000764");
grd.addColorStop(0.16, "#2068CB");
grd.addColorStop(0.42, "#EDFFFF");
grd.addColorStop(0.6425, "#FFAA00");
grd.addColorStop(0.8575, "#000200");
ctx.fillStyle = grd;
ctx.fillRect(0, 0, 2048, 1);
return new Uint32Array(ctx.getImageData(0, 0, 2048, 1).data.buffer);
}
const colors = computeColors();
</script>::: tip NOTE
The example makes a couple assumptions. For instance, using the entire memory of the program as the image buffer as in this example is only possible because we know that no interferring static memory segments will be created, which is achieved by
- using JavaScript's Math instead of native libm (usually adds lookup tables),
- not using a more sophisticated runtime (typically adds bookkeeping) and
- the rest of the example being relatively simple (i.e. no strings or similar).
As soon as these conditions are no longer met, one would instead either reserve some space by specifying a suitable --memoryBase or export a dynamically instantiated chunk of memory, like an Uint16Array, and utilize it as the color index buffer both in WebAssembly and in JavaScript.
:::
Running locally
Set up a new AssemblyScript project as described in Quick start and copy module.ts to assembly/index.ts and index.html to the project's top-level directory. Edit the build commands in package.json to include
--runtime none --use Math=JSMath --importMemoryThe example can now be compiled with
npm run asbuildTo view the example, one can modify the instantiation in index.html from
loader.instantiate(module_wasm, {
env: {
memory
}
}).then(({ exports }) => {to
WebAssembly.instantiateStreaming(fetch('./build/optimized.wasm'), {
env: {
memory
},
JSMath: Math
}).then(({ exports }) => {because using the loader is not ultimately necessary here (no managed objects are exchanged). If the loader is used instead, it will automatically provide JSMath.
Some browsers may restrict fetching local resources when just opening index.html now, but one can set up a local server as a workaround:
npm install --save-dev http-server
http-server . -o -c-1File: src/examples/snippets.md
Snippets
Small, copy-pastable AssemblyScript examples using common syntax and patterns.
Class
An AssemblyScript snippet on using Classes, their instantiation, and their static properties.
#!runtime=stub
class Animal<T> {
static ONE: i32 = 1;
static add(a: i32, b: i32): i32 { return a + b + Animal.ONE; }
two: i16 = 2; // 6
instanceSub<T>(a: T, b: T): T { return a - b + <T>Animal.ONE; } // tsc does not allow this
}
export function staticOne(): i32 {
return Animal.ONE;
}
export function staticAdd(a: i32, b: i32): i32 {
return Animal.add(a, b);
}
export function instanceTwo(): i32 {
let animal = new Animal<i32>();
return animal.two;
}
export function instanceSub(a: f32, b: f32): f32 {
let animal = new Animal<f32>();
return animal.instanceSub<f32>(a, b);
}
#!html
<script>
const log = console.log;
console.log = (...args) => {
log(...args);
let str = '';
args.forEach(arg => {
if (typeof arg == 'object') {
str += `${JSON.stringify(arg, null, 2)}<br/>`;
} else {
str += `${arg}<br/>`;
}
});
document.body.innerHTML += `<div>Log: ${str}</div>`;
}
loader.instantiate(module_wasm, { /* imports */ })
.then(({ exports }) => {
console.log(exports.staticOne());
console.log(exports.staticAdd(1, 2));
console.log(exports.instanceTwo());
console.log(exports.instanceSub(3.0, 1.0));
})
</script>Extending Classes
Extending classes and general Object Orientated Programming (OOP) in AssemblyScript
#!runtime=stub
class BaseClass {
static staticProp: i32 = 24;
instanceProp: i32;
constructor(value: i32) {
this.instanceProp = value;
}
add(a: i32, b: i32): i32 {
return a + b;
}
}
class ExtendedClass extends BaseClass {
extendedProp: i32;
constructor(extendedValue: i32) {
super(1);
this.extendedProp = extendedValue;
}
add(a: i32): i32 {
return super.add(a, this.extendedProp + super.instanceProp);
}
}
export function getStaticProp(): i32 {
return ExtendedClass.staticProp;
}
export function overloadAdd(value: i32): i32 {
let extendedClass = new ExtendedClass(value);
return extendedClass.add(24);
}
#!html
<script>
const log = console.log;
console.log = (...args) => {
log(...args);
let str = '';
args.forEach(arg => {
if (typeof arg === 'object') {
str += `${JSON.stringify(arg, null, 2)}<br/>`;
} else {
str += `${arg}<br/>`;
}
});
document.body.innerHTML += `<div>Log: ${str}</div>`;
}
loader.instantiate(module_wasm, { /* imports */ })
.then(({ exports }) => {
console.log(`getStaticProp: ${exports.getStaticProp()}`);
console.log(`overloadAdd: ${exports.overloadAdd(24)}`);
})
</script>Handling Null
Handling Null as a union type for optional return values and simulating try/catch for errors in AssemblyScript
#!runtime=stub
class MyValue {
value: i32;
constructor(value: i32) {
this.value = value;
}
}
// Using a class type here, as some types are not nullable
function getMyValue(isAble: boolean): MyValue | null {
let myValue = new MyValue(24);
if (isAble) {
return myValue;
} else {
return null;
}
}
export function positiveAddWithMyValue(a: i32): i32 {
let myValue = getMyValue(a > 0);
if (myValue == null) {
return -1;
} else {
return a + myValue.value;
}
}
#!html
<script>
const log = console.log;
console.log = (...args) => {
log(...args);
let str = '';
args.forEach(arg => {
if (typeof arg == 'object') {
str += `${JSON.stringify(arg, null, 2)}<br/>`;
} else {
str += `${arg}<br/>`;
}
});
document.body.innerHTML += `<div>Log: ${str}</div>`;
}
loader.instantiate(module_wasm, { /* imports */ })
.then(({ exports }) => {
if(exports.positiveAddWithMyValue(24) > -1) {
console.log("Add was successful")
} else {
console.log("Could not add 24");
}
if(exports.positiveAddWithMyValue(-1) > -1) {
console.log("Add was successful")
} else {
console.log("Could not add -1");
}
});
</script>Switch Case
Using switch case statements in AssemblyScript.
::: tip NOTE
Currently, the switch conditions (case values) are implicitly converted to u32, i.e. switching over strings or similar is not yet supported.
:::
#!runtime=stub
export function switchSurprise(a: i32): i32 {
let response = -1;
// Using a mix of braces and not using braces
// to show that both syntaxes are supported here.
switch (a) {
case 1:
response = 100;
break;
case 2: { // Cases can also use braces
response = 200;
break;
}
case 3:
// Fall Through to the next case
case 4:
response = 400;
break;
default: {
response = 0;
}
}
return response;
}
#!html
<script>
const log = console.log;
console.log = (...args) => {
log(...args);
let str = '';
args.forEach(arg => {
if (typeof arg == 'object') {
str += `${JSON.stringify(arg, null, 2)}<br/>`;
} else {
str += `${arg}<br/>`;
}
});
document.body.innerHTML += `<div>Log: ${str}</div>`;
}
loader.instantiate(module_wasm, { /* imports */ })
.then(({ exports }) => {
console.log(`switchSurprise(1) : ${exports.switchSurprise(1)}`);
console.log(`switchSurprise(2) : ${exports.switchSurprise(2)}`);
console.log(`switchSurprise(3) : ${exports.switchSurprise(3)}`);
console.log(`switchSurprise(4) : ${exports.switchSurprise(4)}`);
console.log(`switchSurprise(57) : ${exports.switchSurprise(57)}`);
});
</script>Ternary if-else
Using Ternary if-else in AssemblyScript
#!runtime=stub
export function isTrue(a: i32): i32 {
let response = a > 0 ? 1 : 0;
return response;
}
#!html
<script>
const log = console.log;
console.log = (...args) => {
log(...args);
let str = '';
args.forEach(arg => {
if (typeof arg == 'object') {
str += `${JSON.stringify(arg, null, 2)}<br/>`;
} else {
str += `${arg}<br/>`;
}
});
document.body.innerHTML += `<div>Log: ${str}</div>`;
}
loader.instantiate(module_wasm, { /* imports */ })
.then(({ exports }) => {
console.log(`is 24 greater than 0? ${exports.isTrue(24) > 0 ? true : false}`);
console.log(`is -3 greater than 0? ${exports.isTrue(-3) > 0 ? true : false}`);
});
</script>File: src/stdlib/array.md
Array
A randomly accessible sequence of values of a generic type.
The Array API is very similar to JavaScript's (MDN), with the notable difference that one must make sure that there are no null values if T is a non-nullable reference type. Example:
var arr = new Array<string>(10)
// arr[0]; // would error 😢
for (let i = 0; i < arr.length; ++i) {
arr[i] = ""
}
arr[0]; // now it works 😊Constructor
- Constructs a new array.ts
new Array<T>(capacity?: i32)
Static members
- Tests if a value is an array.ts
function isArray<U>(value: U): bool
Instance members
Fields
- The length of this array. Setting the length to a value larger than internal capacity will automatically grow the array.ts
var length: i32
Methods
- ts
function concat(other: Array<T>): Array<T>Concatenates the values of this and the other array to a new array, in this order.
- ts
function copyWithin(target: i32, start: i32, end?: i32): thisCopies a region of an array's values over the respective values starting at the target location.
- ts
function every(fn: (value: T, index: i32, array: Array<T>) => bool): boolCalls the specified function with every value of the array until it finds the first value for which the function returns
false. Returnstrueif all functions returnedtrueor the array is empty, otherwisefalse. - ts
function fill(value: T, start?: i32, end?: i32): thisReplaces the values of the array from
startinclusive toendexclusive in place with the specified value, returning the array. - ts
function filter(fn: (value: T, index: i32, array: Array<T>) => bool): Array<T>Calls the specified function with every value of the array, returning a new array with all values for which the function returned
true. - ts
function findIndex(fn: (value: T, index: i32, array: Array<T>) => bool): i32Calls the specified function with every value of the array until it finds the first value for which the function returns
true, returning its index. Returns-1if that's never the case. - ts
function flat(): valueof<T>[]Flattens an array of arrays to a one-dimensional array.
nullentries are ignored. - ts
function forEach(fn: (value: T, index: i32, array: Array<T>) => void): voidCalls the specified function with every value of the array.
- ts
function includes(value: T, fromIndex?: i32): boolTests if the array includes the specified value, optionally providing a starting index.
- ts
function indexOf(value: T, fromIndex?: i32): i32Gets the first index where the specified value can be found in the array. Returns
-1if not found. - ts
function join(separator?: string): stringConcatenates all values of the array to a string, separated by the specified separator (default:
,). - ts
function lastIndexOf(value: T, fromIndex?: i32): i32Gets the last index where the specified value can be found in the array. Returns
-1if not found. - ts
function map<U>(fn: (value: T, index: i32, array: Array<T>) => U): Array<U>Calls the specified function with every value of the array, returning a new array of the function's return values.
- ts
function pop(): TRemoves and returns the last value of the array. Modifies
Array#length. - ts
function push(value: T): i32Adds one more value to the end of the array and returns the Array's new length. Modifies
Array#length. - ts
function reduce<U>( fn: (acc: U, cur: T, idx: i32, src: Array) => U, initialValue: U ): UCalls the specified reducer function with each value of the array, resulting in a single return value. The respective previous reducer function's return value is remembered in
acc, starting withinitialValue, becoming the final return value in the process. - ts
function reduceRight<U>( fn: (acc: U, cur: T, idx: i32, src: Array) => U, initialValue: U ): UCalls the specified reducer function with each value of the array, from right to left, resulting in a single return value. See
Array#reducefor the reducer function's signature. - ts
function reverse(): thisReverses an array's values in place, modifying the array before returning it.
- ts
function shift(): TRemoves and returns the first value of the array. Modifies
Array#length. - ts
function slice(start?: i32, end?: i32): Array<T>Returns a shallow copy of the array's values from
begininclusive toendexclusive, as a new array. If omitted,enddefaults to the end of the array. - ts
function some(fn: (value: T, index: i32, array: Array<T>) => bool): boolCalls the specified function with every value of the array until it finds the first value for which the function returns
true, returningtrue. Returnsfalseotherwise or if the array is empty. - ts
function sort(fn?: (a: T, b: T) => i32): thisSorts the values of the array in place, using the specified comparator function, modifying the array before returning it. The comparator returning a negative value means
a < b, a positive value meansa > band0means that both are equal. Unlike in JavaScript, where an implicit conversion to strings is performed, the comparator defaults to compare two values of typeT. - ts
function splice(start: i32, deleteCount?: i32): Array<T>Removes
deleteCount(defaults to all remaining) values from the array, starting at indexstart, modifying the array in place, returning the removed values. - ts
function toString(): stringReturns the result of
Array#join(). - ts
function unshift(value: T): i32Adds one more value to the start of the array and returns the Array's new length. Modifies
Array#length.
File: src/stdlib/arraybuffer.md
ArrayBuffer
A fixed-length raw binary buffer.
The ArrayBuffer API is exactly as in JavaScript (MDN).
Constructor
- Constructs a new buffer of the given length in bytes.ts
new ArrayBuffer(length: i32)
Static members
- Returns true ifts
function isView<T>(value: T): boolvalueis one of the buffer views, such as one of the typed arrays or a DataView.
Instance members
Fields
- The buffer's length, in bytes.ts
readonly byteLength: i32
Methods
- ts
function slice(begin?: i32, end?: i32): ArrayBufferReturns a copy of this buffer from begin, inclusive, up to end, exclusive.
- ts
function toString(): stringReturns a string representation of this buffer.
File: src/stdlib/console.md
console
An implementation of the console global on top of WASI. Requires a WASI-enabled host and opting into WASI in the entry file with
import "wasi";Static members
- ts
function assert<T>(assertion: T, message: string): voidLogs
messageto console ifassertionis false-ish. - ts
function log(message: string): voidOutputs
messageto the console. - ts
function debug(message: string): void function info(message: string): void function warn(message: string): void function error(message: string): voidOutputs
messageto the console, prefixed with "Debug:", "Info:", "Warning:" or "Error:" respectively. - ts
function time(label: string): voidStarts a new timer using the specified
label. - ts
function timeLog(label: string): voidLogs the current value of a timer previously started with
console.time. - ts
function timeEnd(label: string): voidLogs the current value of a timer previously started with
console.timeand discards the timer.
File: src/stdlib/crypto.md
crypto
An implementation of the crypto global on top of WASI. Requires a WASI-enabled host and opting into WASI in the entry file with
import "wasi";Static members
- Fillsts
function getRandomValues(array: Uint8Array): voidarraywith cryptographically strong random values.
File: src/stdlib/dataview.md
DataView
An interface for working with a raw binary buffer.
The DataView API is exactly as in JavaScript (MDN).
Constructor
- Constructs a new DataView on the specified buffer and region.ts
new DataView(buffer: ArrayBuffer, byteOffset?: i32, byteLength?: i32)
Instance members
Fields
- ts
readonly buffer: ArrayBufferThe backing buffer.
- ts
readonly byteLength: i32The length of this view from the start of its buffer.
- ts
readonly byteOffset: i32The offset of this view from the start of its buffer.
Methods
- ts
function getFloat32(byteOffset: i32, littleEndian?: bool): f32Gets the 32-bit float value at the specified offset from the start of the view.
- ts
function getFloat64(byteOffset: i32, littleEndian?: bool): f64Gets the 64-bit float value at the specified offset from the start of the view.
- ts
function getInt8(byteOffset: i32): i8Gets the signed 8-bit integer value at the specified offset from the start of the view.
- ts
function getInt16(byteOffset: i32, littleEndian?: bool): i16Gets the signed 16-bit integer value at the specified offset from the start of the view.
- ts
function getInt32(byteOffset: i32, littleEndian?: bool): i32Gets the signed 32-bit integer value at the specified offset from the start of the view.
- ts
function getInt64(byteOffset: i32, littleEndian?: bool): i64Gets the signed 64-bit integer value at the specified offset from the start of the view.
- ts
function getUint8(byteOffset: i32, littleEndian?: bool): u8Gets the unsigned 8-bit integer value at the specified offset from the start of the view.
- ts
function getUint16(byteOffset: i32, littleEndian?: bool): u16Gets the unsigned 16-bit integer value at the specified offset from the start of the view.
- ts
function getUint32(byteOffset: i32, littleEndian?: bool): u32Gets the unsigned 32-bit integer value at the specified offset from the start of the view.
- ts
function getUint64(byteOffset: i32, littleEndian?: bool): u64Gets the unsigned 64-bit integer value at the specified offset from the start of the view.
- ts
function setFloat32(byteOffset: i32, value: f32, littleEndian?: bool): voidSets the 32-bit float value at the specified offset from the start of the view.
- ts
function setFloat64(byteOffset: i32, value: f64, littleEndian?: bool): voidSets the 64-bit float value at the specified offset from the start of the view.
- ts
function setInt8(byteOffset: i32, value: i8): voidSets the signed 8-bit integer value at the specified offset from the start of the view.
- ts
function setInt16(byteOffset: i32, value: i16, littleEndian?: bool): voidSets the signed 16-bit integer value at the specified offset from the start of the view.
- ts
function setInt32(byteOffset: i32, value: i32, littleEndian?: bool): voidSets the signed 32-bit integer value at the specified offset from the start of the view.
- ts
function setInt64(byteOffset: i32, value: i64, littleEndian?: bool): voidSets the signed 64-bit integer value at the specified offset from the start of the view.
- ts
function setUint8(byteOffset: i32, value: u8, littleEndian?: bool): voidSets the unsigned 8-bit integer value at the specified offset from the start of the view.
- ts
function setUint16(byteOffset: i32, value: u16, littleEndian?: bool): voidSets the unsigned 16-bit integer value at the specified offset from the start of the view.
- ts
function setUint32(byteOffset: i32, value: u32, littleEndian?: bool): voidSets the unsigned 32-bit integer value at the specified offset from the start of the view.
- ts
function setUint64(byteOffset: i32, value: u64, littleEndian?: bool): voidSets the unsigned 64-bit integer value at the specified offset from the start of the view.
- ts
function toString(): stringReturns a string representation of this object.
Endianness defaults to littleEndian = false.
File: src/stdlib/date.md
Date
Represents timestamps.
The Date API is still preliminary and requires importing the Date object from the host (as Date).
Constructor
- Constructs a new date object from an UTC timestamp in milliseconds.ts
new Date(value: i64)
Static members
- ts
function now(): i64Returns the current UTC timestamp in milliseconds.
- ts
function UTC( year: i32, month?: i32, day?: i32, hour?: i32, minute?: i32, second?: i32, millisecond?: i64 ): i64Returns the UTC timestamp in milliseconds of the specified date.
Instance members
- ts
function getTime(): i64Gets the UTC timestamp of this date in milliseconds.
- ts
function setTime(value: i64): i64Sets the UTC timestamp of this date in milliseconds and returns the timestamp.
File: src/stdlib/error.md
Error
Represents a runtime error.
The Error API is very similar to JavaScript's (MDN), with some properties not being implemented yet.
Variants
| Variant | Description |
|---|---|
| Error | Represents a general error. |
| RangeError | Represents an error where a value is not in the range of allowed values. |
| TypeError | Represents an error where a value is not of the expected type. |
| SyntaxError | Represents an error where the syntax of the input in invalid. |
The Errorclass can also be sub-classed by forwarding message and setting the name property in the overloaded constructor.
Constructor
- Constructs a new error object.ts
new Error(message?: string)
Instance members
Fields
- ts
var message: stringThe message of this error.
- ts
var name: stringThe name of this error. In case of
Error, this is"Error". - ts
var stack: stringThe stack trace of this error. Not supported yet, hence an empty string.
Methods
- Returns a string representation of this error in the formts
function toString(): stringname: message.
File: src/stdlib/globals.md
Globals
In addition to the general environment with its WebAssembly-focused built-ins, the following global constants and functions are present alongside the standard library's classes.
Constants
- ts
const NaN: auto // f32 or f64Not a number as a 32-bit or 64-bit float depending on context. Compiles to a constant.
- ts
const Infinity: auto // f32 or f64Positive infinity as a 32-bit or 64-bit float depending on context. Compiles to a constant.
Functions
- ts
function isNaN<T>(value: T): boolTests if a 32-bit or 64-bit float is
NaN. - ts
function isFinite<T>(value: T): boolTests if a 32-bit or 64-bit float is finite, that is not
NaNor +/-Infinity. - ts
function parseInt(str: string, radix?: i32): i64Parses a string to a 64-bit integer. Returns
0on invalid inputs. - ts
function parseFloat(str: string): f64Parses a string to a 64-bit float. Returns
NaNon invalid inputs.
File: src/stdlib/map.md
Map
A mapping of generic keys to generic values.
The Map API is very similar to JavaScript's (MDN), with the notable difference that a .get with a key that does not exist results in an error, because undefined cannot be represented. Example:
var map = new Map<i32,string>()
// Because `undefined` cannot be represented if a key is not found, this will error:
var str = map.get(1) // ERROR
// The error can be avoided by first making sure that the key exists, so this works:
var str: string | null = map.has(1) ? map.get(1) : null // OKConstructor
- Constructs a new map mapping keys of typets
new Map<K,V>()Kto values of typeV.
Instance members
Fields
- The current number of key-value pairs in this map.ts
readonly size: i32
Methods
- ts
function clear(): voidClears the map, deleting all key-value pairs.
- ts
function delete(key: K): boolDeletes the key-value pair for the corresponding key. Returns
trueif the key did exist, otherwisefalse. - ts
function get(key: K): VGets the value corresponding to the specified key. Traps if the key does not exist because "not found" cannot be represented in all cases (use
Map#hasto check). - ts
function has(key: K): boolTests if the specified key exists.
- ts
function keys(): Array<K>Gets the keys contained in this map as an array, in insertion order. This is preliminary while iterators are not supported.
- ts
function set(key: K, value: V): thisSets the value for the specified key. Creates a new key-value pair if the key did not exist.
- ts
function values(): Array<V>Gets the values contained in this map as an array, in insertion order. This is preliminary while iterators are not supported.
- ts
function toString(): stringReturns a string representation of this map.
File: src/stdlib/math.md
Math
Mathematical operations as known from JavaScript.
The Math API is very much like JavaScript's (MDN), with the notable exceptions stated above and rest parameters not being supported yet.
Variants
Math in AssemblyScript is available in multiple variants.
| Variant | Description |
|---|---|
| NativeMath | WebAssembly implementation for f64 |
| NativeMathf | WebAssembly implementation for f32 |
| JSMath | JavaScript implementation for f64 (imported from the host) |
By default, the global Math object is an alias of NativeMath and Mathf is an alias of NativeMathf .
Using NativeMath
This is the default, so no additional configuration options are required. Note, however, that Math.random needs a way to seed the random number generator in this scenario, which WebAssembly alone cannot do, hence a function env.seed() is imported from the host (see also) that must return an f64 value (the seed). The loader and import "WASI" automatically take care of providing the seed function in this scenario, but one can always implement their own, for example to make the PRNG deterministic by returning a fixed seed value.
Using JSMath
Where small module size is more important than performance, one can opt to override the default by adding --use Math=JSMath on the command line, essentially aliasing Math with JSMath instead, which maps to an import of the browser's math implementation. Naturally, this option requires importing the browser's Math object as a whole, but does not require seeding / is not seedable. The loader automatically takes care of importing the browser's math in this scenario.
Static members
The type T below substitutes either f32 or f64 depending on the implementation used.
Constants
- ts
const E: TThe base of natural logarithms, e, approximately 2.718.
- ts
const LN2: TThe natural logarithm of 2, approximately 0.693.
- ts
const LN10: TThe natural logarithm of 10, approximately 2.302.
- ts
const LOG2E: TThe base 2 logarithm of e, approximately 1.442.
- ts
const LOG10E: TThe base 10 logarithm of e, approximately 0.434.
- ts
const PI: TThe ratio of the circumference of a circle to its diameter, approximately 3.14159.
- ts
const SQRT1_2: TThe square root of 1/2, approximately 0.707.
- ts
const SQRT2: TThe square root of 2, approximately 1.414.
Functions
- ts
function abs(x: T): TReturns the absolute value of
x. - ts
function acos(x: T): TReturns the arccosine (in radians) of
x. - ts
function acosh(x: T): TReturns the hyperbolic arc-cosine of
x. - ts
function asin(x: T): TReturns the arcsine (in radians) of
x. - ts
function asinh(x: T): TReturns the hyperbolic arcsine of
x. - ts
function atan(x: T): TReturns the arctangent (in radians) of
x. - ts
function atan2(y: T, x: T): TReturns the arctangent of the quotient of its arguments.
- ts
function atanh(x: T): TReturns the hyperbolic arctangent of
x. - ts
function cbrt(x: T): TReturns the cube root of
x. - ts
function ceil(x: T): TReturns the smallest integer greater than or equal to
x. - ts
function clz32(x: T): TReturns the number of leading zero bits in the 32-bit binary representation of
x. - ts
function cos(x: T): TReturns the cosine (in radians) of
x. - ts
function cosh(x: T): TReturns the hyperbolic cosine of
x. - ts
function exp(x: T): TReturns e to the power of
x. - ts
function expm1(x: T): TReturns e to the power of
x, minus 1. - ts
function floor(x: T): TReturns the largest integer less than or equal to
x. - ts
function fround(x: T): TReturns the nearest 32-bit single precision float representation of
x. - ts
function hypot(value1: T, value2: T): TReturns the square root of the sum of squares of its arguments.
- ts
function imul(a: T, b: T): TReturns the result of the C-like 32-bit multiplication of
aandb. - ts
function log(x: T): TReturns the natural logarithm (base e) of
x. - ts
function log10(x: T): TReturns the base 10 logarithm of
x. - ts
function log1p(x: T): TReturns the natural logarithm (base e) of 1 +
x. - ts
function log2(x: T): TReturns the base 2 logarithm of
x. - ts
function max(value1: T, value2: T): TReturns the largest-valued number of its arguments.
- ts
function min(value1: T, value2: T): TReturns the lowest-valued number of its arguments.
- ts
function pow(base: T, exponent: T): TReturns
baseto the power ofexponent. - ts
function random(): TReturns a pseudo-random number in the range from 0.0 inclusive up to but not including 1.0.
::: tip
Seeding happens automatically in common scenarios. See the notes on using NativeMath above if it doesn't.
::: - ts
function round(x: T): TReturns the value of
xrounded to the nearest integer. - ts
function sign(x: T): TReturns the sign of
x, indicating whether the number is positive, negative or zero. - ts
function signbit(x: T): boolReturns whether the sign bit of
xis set. - ts
function sin(x: T): TReturns the sine of
x. - ts
function sinh(x: T): TReturns the hyperbolic sine of
x. - ts
function sqrt(x: T): TReturns the square root of
x. - ts
function tan(x: T): TReturns the tangent of
x. - ts
function tanh(x: T): TReturns the hyperbolic tangent of
x. - ts
function trunc(x: T): TReturns the integer part of
xby removing any fractional digits.
Considerations
The Math implementations are meant as a drop-in replacement for JavaScript's Math so sometimes mimic special JavaScript semantics, like Math.round always rounding towards +Infinity. Also, functions like Math.fround or Math.imul do not return an f32 respectively an i32 as some might expect for the same reason. Hence, depending on the use case, using WebAssembly's math instructions directly can be a worthwhile alternative where portability is not a concern.
File: src/stdlib/number.md
sidebarDepth: 2
Number
Wrappers for basic numerical values.
The Number object has been split into one class per basic WebAssembly type as well. Unlike in JavaScript, these classes cannot have actual instances, hence it works a bit different than JavaScript's (MDN).
Integers
The name Number below stands for one of the wrappers I8, I16, I32, I64, U8, U16, U32 or U64 representing their respective basic integer type T.
Static members
- ts
const MIN_VALUE: TThe smallest representable value of the respective basic type. Differs from floating point values where it is the smallest representable positive value.
- ts
const MAX_VALUE: TThe largest representable value of the respective basic type.
- ts
function parseInt(value: string, radix?: i32): TParses a string to a value of the respective basic type.
Instance members
- Returns the respective basic value converted to a string.ts
function toString(radix?: i32): string
Floats
The name Number below stands for one of the wrappers F32 or F64 representing their respective basic floating point type T.
Static members
- ts
const EPSILON: TThe difference between
1.0and the smallest number larger than1.0. - ts
const MAX_VALUE: TThe largest representable positive value by the respective basic type.
- ts
const MIN_VALUE: TThe smallest representable positive value by the respective basic type.
- ts
const MAX_SAFE_INTEGER: TThe largest safe integer representable by the respective basic type.
- ts
const MIN_SAFE_INTEGER: TThe smallest safe integer representable by the respective basic type.
- ts
const POSITIVE_INFINITY: TPositive infinity of the respective basic type.
- ts
const NEGATIVE_INFINITY: TNegative infinity of the respective basic type.
- ts
const NaN: TNaN (Not A Number) of the respective basic type.
- ts
function isNan(value: T): boolTests if the value is
NaN. - ts
function isFinite(value: T): boolTests if the value is finite, that is not
NaN,POSITIVE_INFINITYorNEGATIVE_INFINITY. - ts
function isInteger(value: T): boolTests if the value is an integer.
- ts
function isSafeInteger(value: T): boolTests if the value is a safe integer.
- ts
function parseInt(value: string, radix?: i32): TParses a string to an integer value of the respective basic type.
- ts
function parseFloat(value: string): TParses a string to a float value of the respective basic type.
Instance members
- Returns the respective basic value converted to a string. Thets
function toString(radix?: i32): stringradixparameter is currently ignored here.
File: src/stdlib/process.md
process
An implementation of a Node.js-like process global on top of WASI. Requires a WASI-enabled host and opting into WASI in the entry file with
import "wasi";Static members
- ts
const arch: stringString representing the CPU architecture for which the binary was compiled. Either
wasm32orwasm64. - ts
const platform: stringString representing the operating system platform for which the binary was compiled. Always
wasm. - ts
const argv: string[]Array of command line arguments passed to the binary upon instantiation.
- ts
const env: Map<string,string>Map of variables in the binary's user environment.
- ts
var exitCode: i32Process exit code to use when the process exits gracefully. Defaults to
0. - ts
function exit(code?: i32): voidTerminates the process with either the given exit code, or
process.exitCodeif omitted. - ts
const stdin: ReadableStream const stdout: WritableStream const stderr: WritableStreamStreams connected to
stdin(fd0),stdout(fd1) andstderr(fd2) respectively. - ts
function time(): i64Obtains the system's current time of day, in milliseconds since Unix epoch.
- ts
function hrtime(): u64Obtains the system's monotonic high resolution time, in nanoseconds since an arbitrary time in the past.
File: src/stdlib/set.md
Set
A set of unique generic values.
The Set API is very similar to JavaScript's (MDN), but iterators are not implemented yet.
Constructor
- Constructs a new set of unique value of typets
new Set<T>()T.
Instance members
Fields
- The current number of unique values in the set.ts
readonly size: i32
Methods
- ts
function add(value: T): voidAdds the specified value to the set. Does nothing if the value already exists.
- ts
function delete(value: T): boolDeletes the specified value. Returns
trueif the value was found, otherwisefalse. - ts
function clear(): voidClears the set, deleting all values.
- ts
function has(value: T): boolTests if the specified value exists in the set.
- ts
function values(): Array<T>Gets the values contained in this set as an array, in insertion order. This is preliminary while iterators are not supported.
- ts
function toString(): stringReturns a string representation of this map.
File: src/stdlib/staticarray.md
StaticArray
A randomly accessible sequence of values of a generic type with a fixed length.
The StaticArray API is similar to the Array API, with the important difference that it has a fixed length that cannot change. Unlike a normal Array, a StaticArray does not have a separate backing buffer, so no level of indirection, and as such can have minimal overhead and performance characteristics very similar to arrays in C.
Constructor
- Constructs a new static array.ts
new StaticArray<T>(length: i32)
Static members
- ts
function fromArray<T>(source: T[]): StaticArray<T>Creates a static array from a normal array.
- ts
function concat<T>(source: StaticArray<T>, other: StaticArray<T>): StaticArray<T>Like the instance member
concat, but taking and returning aStaticArray. - ts
function slice<T>(source: StaticArray<T>, start?: i32, end?: i32): StaticArray<T>Like the instance member
slice, but returning aStaticArray.
Instance members
Fields
- The fixed length of this static array.ts
readonly length: i32
Methods
- ts
function concat(other: T[]): T[]Concatenates the values of this static and the other normal array to a new normal array, in this order.
- ts
function includes(value: T, fromIndex?: i32): boolTests if the array includes the specified value, optionally providing a starting index.
- ts
function indexOf(value: T, fromIndex?: i32): i32Gets the first index where the specified value can be found in the array. Returns
-1if not found. - ts
function join(separator?: string): stringConcatenates all values of the array to a string, separated by the specified separator (default:
,). - ts
function lastIndexOf(value: T, fromIndex?: i32): i32Gets the last index where the specified value can be found in the array. Returns
-1if not found. - ts
function slice(start?: i32, end?: i32): T[]Returns a shallow copy of this static array's values from
begininclusive toendexclusive, as a new normal array. If omitted,enddefaults to the end of the array. - ts
function toString(): stringReturns the result of
join().
File: src/stdlib/string.md
String
A fixed-length sequence of UTF-16 code units.
The String API works very much like JavaScript's (MDN), with the notable difference that the string type is an actual alias of String.
Static members
- ts
function fromCharCode(unit: i32, surr?: i32): stringCreates a one character long string from the specified UTF-16 code units.
- ts
function fromCharCodes(units: u16[]): stringCreates a string from a sequence of UTF-16 code units.
- ts
function fromCodePoint(code: i32): stringCreates a one character long string from the specified Unicode code point.
- ts
function fromCodePoints(codes: i32[]): stringCreates a string from a sequence of Unicode code points.
Instance members
Fields
- The length of the string in UTF-16 code units.ts
readonly length: i32
Methods
- ts
function charAt(pos: i32): stringGets the UTF-16 code unit at the specified position as a single character string. Returns
""(empty string) if out of bounds. - ts
function charCodeAt(pos: i32): i32Gets the UTF-16 code unit at the specified position as a number. Returns
-1if out of bounds. - ts
function codePointAt(pos: i32): i32Gets the Unicode code point at the specified (UTF-16 code unit) position as a number, possibly combining multiple successive UTF-16 code units. Returns
-1if out of bounds. - ts
function concat(other: string): stringConcatenates this string with another string, in this order, and returns the resulting string.
- ts
function endsWith(search: string, end?: i32): boolTests if the strings ends with the specified string. If specified,
endindicates the position at which to stop searching, acting as if it is the length of the string. - ts
function includes(search: string, start?: i32): boolTests if the string includes the search string. If specified,
startindicates the position at which to begin searching. - ts
function indexOf(search: string, start?: i32): i32Gets the first index of the specified search string within the string, or
-1if not found. If specified,startindicates the position at which to begin searching. - ts
function lastIndexOf(search: string, start?: i32): i32Gets the last index of the specified search string within the string, or
-1if not found. If specified,posindicates the position at which to begin searching from right to left. - ts
function padStart(length: i32, pad: string): stringPads the string with the contents of another string, possibly multiple times, until the resulting string reaches the specified length, returning the resulting string.
- ts
function padEnd(length: i32, pad: string): stringPads the string with the contents of another string, possibly multiple times, until the resulting string reaches the specified length, returning the resulting string.
- ts
function repeat(count?: i32): stringRepeats the string
counttimes and returns the concatenated result. - ts
function replace(search: string, replacement: string): stringReplaces the first occurrence of
searchwithreplacement. - ts
function replaceAll(search: string, replacement: string): stringReplaces all occurrences of
searchwithreplacement. - ts
function slice(start: i32, end?: i32): stringReturns the region of the string from
startinclusive toendexclusive, as a new string. If omitted,enddefaults to the end of the string. - ts
function split(separator?: string, limit?: i32): string[]Splits the string at each occurrence of
separatorand returns the result as a new array of strings that has a maximum oflimitvalues. Iflimitis omitted, no limit is assumed. Ifseparatoris omitted or not present in the string, the string becomes the sole element of the array. Ifseparatoris an empty string, the split is performed between code units (not code points), potentially destroying surrogate pairs. - ts
function startsWith(search: string, start?: i32): boolTests if the string starts with the specified string. If specified,
posindicates the position at which to begin searching, acting as the start of the string. - ts
function substring(start: i32, end?: i32): stringGets the part of the string in between
startinclusive andendexclusive as a new string. - ts
function toString(): thisReturns the string.
- ts
function trim(): stringRemoves white space characters from both the start and the end of the string, returning the resulting string.
- ts
function trimStart(): string function trimLeft(): stringRemoves white space characters from the start of the string, returning the resulting string.
- ts
function trimEnd(): string function trimRight(): stringRemoves white space characters from the end of the string, returning the resulting string.
Encoding API
UTF8
When integrating with an environment that uses UTF-8, the following helpers can be used to quickly re-encode String data.
- ts
function String.UTF8.byteLength(str: string, nullTerminated?: bool): i32Calculates the byte length of the specified string when encoded as UTF-8, optionally null terminated.
- ts
function String.UTF8.encode(str: string, nullTerminated?: bool): ArrayBufferEncodes the specified string to UTF-8 bytes, optionally null terminated.
- ts
function String.UTF8.encodeUnsafe(str: usize, len: i32, buf: usize, nullTerminated?: bool): usizeEncodes the specified raw string to UTF-8 bytes, opionally null terminated. Returns the number of bytes written.
- ts
function String.UTF8.decode(buf: ArrayBuffer, nullTerminated?: bool): stringDecodes the specified buffer from UTF-8 bytes to a string, optionally null terminated.
- ts
function String.UTF8.decodeUnsafe( buf: usize, len: usize, nullTerminated?: bool ): stringDecodes raw UTF-8 bytes to a string, optionally null terminated.
::: tip
Note that any ArrayBuffer return value is a pointer to the buffer's data internally and thus can be passed to let's say a C-function directly. However, if the pointer is meant to live longer than the immediate external function call, the lifetime of the buffer must be tracked so it doesn't become collected prematurely with the data becoming invalid.
:::
UTF16
The following mostly exist to have a safe way to copy between Strings and ArrayBuffers, but doesn't involve a re-encoding step.
- ts
function String.UTF16.byteLength(str: string): i32Calculates the byte length of the specified string when encoded as UTF-16.
- ts
function String.UTF16.encode(str: string): ArrayBufferEncodes the specified string to UTF-16 bytes.
- ts
function String.UTF16.encodeUnsafe(str: usize, len: i32, buf: usize): usizeEncodes the specified raw string to UTF-16 bytes. Returns the number of bytes written.
- ts
function String.UTF16.decode(buf: ArrayBuffer): stringDecodes the specified buffer from UTF-16 bytes to a string.
- ts
function String.UTF16.decodeUnsafe(buf: usize, len: usize): stringDecodes raw UTF-16 bytes to a string.
Considerations
Also see the notes on string interoperability.
File: src/stdlib/symbol.md
Symbol
A primitive data type representing a unique global symbol.
The symbol type is very similar to JavaScript's (MDN), but is not deeply integrated with the compiler yet.
Construction
- Creates a new unique symbol. Note that this is not a constructor (symbols are primitive data types) so cannot be invoked withts
function Symbol(description?: string): symbolnew.
Static members
- ts
function for(key: string): symbolObtains the existing unique symbol for the specified global key or creates a new unique symbol if the key does not exist.
- ts
function keyFor(sym: symbol): string | nullObtains the global key of the specified unique symbol, if any.
Instance members
- Returns a string representation of the symbol of the formts
function toString(): string"Symbol(key?)".