├── .gitignore ├── LICENSE ├── README.md ├── assembly ├── fib.ts ├── sum.ts └── tsconfig.json ├── assemblyscript_loader.js ├── package-lock.json ├── package.json ├── test_fib.ts └── test_sum.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.wasm 3 | *.wasm.map 4 | *.wat 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Daniel Buckmaster 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Future Is Now 2 | 3 | > Running Wasm with Deno by compiling TypeScript using AssemblyScript 4 | 5 | [Deno](https://deno.land) is a new runtime for JavaScript and TypeScript, with native support for loading [WebAssembly](https://webassembly.org/) (Wasm) modules. 6 | This is just one of many reasons to be looking forward to Deno's v1.0 release. 7 | 8 | [AssemblyScript](https://assemblyscript.org/) is a compiler that understands a subset of TypeScript and produces Wasm modules. 9 | Being able to write a high-level language that isn't _all_ of TypeScript, but isn't C or Rust, seems like a really helpful way to get started experimenting with Wasm in an existing JS or TS application. 10 | 11 | For a new server project I'm starting, I wanted to see if I could use Deno instead of Node, and write some of the server's logic in AssemblyScript for high performance. 12 | It turns out that it's not very hard to get these two great projects to work together. 13 | 14 | ## Dependencies 15 | 16 | This project relies on having `node`, `npm` and `deno` installed. 17 | 18 | I'm using `node` 12, `npm` 6.13, and `deno` 0.34. 19 | 20 | You can install the latter by running: 21 | 22 | ```bash 23 | curl -fsSL https://deno.land/x/install/install.sh | sh -s v0.34.0 24 | ``` 25 | 26 | To install AssemblyScript, just run 27 | 28 | ```bash 29 | npm ci 30 | ``` 31 | 32 | Then take a moment to appreciate that `node_modules` is only 30MB on-disk! 33 | 34 | ## Building Wasm from source 35 | 36 | `assembly/fib.ts` contains an example of TypeScript code written for AssemblyScript (taken from their documentation). 37 | There are two ways we can compile it for use with Deno: 38 | 39 | 1. Using the AssemblyScript runtime. 40 | 2. Using no runtime. This is easier to get into Deno, but you'll miss out on everything [documented here](https://docs.assemblyscript.org/details/runtime). 41 | 42 | This project is set up to create outputs using both approaches in `assembly/fib.runtime.wasm` and `assembly/fib.standalone.wasm` respectively. 43 | To compile both modules, run 44 | 45 | ```bash 46 | npm run asbuild 47 | ``` 48 | 49 | Take a look at `package.json` to see what this script does. 50 | Then you'll see a bunch of files created in the `assembly` folder. 51 | 52 | The TypeScript files that will be compiled to Wasm are placed in their own folder so they can have a custom `tsconfig.json` which loads a bunch of compatibility stuff. 53 | I'm not sure if this can be worked around, but it's an acceptable constraint for now! 54 | 55 | ## Deno and Wasm 56 | 57 | There are three ways to import Wasm modules into Deno JS/TS code which are used in `test_fib.ts` and `test_sum.ts`: 58 | 59 | **Direct imports** 60 | 61 | Deno allows you to import a Wasm module as an ES module: 62 | 63 | ```typescript 64 | import { fib } from "./assembly/fib.standalone.wasm"; 65 | ``` 66 | 67 | This is convenient, but won't work with more advanced features like AS's runtime, as far as I can tell. 68 | 69 | **Browser WebAssembly API** 70 | 71 | Deno implements many of the same APIs as in browsers. 72 | WebAssembly is just one example. 73 | See the `runWithStandalone` function in `test_sum.ts` for an example and [MDN](https://developer.mozilla.org/en-US/docs/WebAssembly/Using_the_JavaScript_API) for more details. 74 | 75 | **AssemblyScript loader** 76 | 77 | AS has [a loader script](https://github.com/AssemblyScript/assemblyscript/tree/master/lib/loader) which helps setting things up for you. 78 | 79 | I had to inline a copy of the loader here (in `assemblyscript_loader.js`), because the existing source does not use ES modules. 80 | Hopefully that will change in a future version. 81 | 82 | ## Running Deno 83 | 84 | After building your Wasm modules, run the example using: 85 | 86 | ```bash 87 | deno --allow-read=. test_fib.ts 88 | ``` 89 | 90 | You should see: 91 | 92 | ``` 93 | fib(10) from module with runtime: 94 | 89 95 | fib(10) from standalone module: 96 | 89 97 | ``` 98 | 99 | `allow-read` is required because the script loads the `fib.runtime.wasm` file from disk. 100 | If you wrote a script that only used the ESM `import` method to include `fib.standalone.wasm` this permission would not be required, because imports do not require permissions. 101 | 102 | ## Passing memory into Wasm 103 | 104 | `test_sum.ts` shows a more complex example of passing a memory region (an array of 10 `Float64`s) into the Wasm module. 105 | The Wasm code in `assembly/sum.ts` sums the values in memory given a start and end index. 106 | 107 | Run the example using: 108 | 109 | ```bash 110 | deno --allow-read=. test_sum.ts 111 | ``` 112 | 113 | You should see: 114 | 115 | ``` 116 | sum(0, 10) from module with runtime: 117 | 10 118 | sum(0, 10) from standalone module: 119 | 10 120 | ``` 121 | 122 | An important concern is the interaction between the passed-in memory buffer and AssemblyScript's runtime code (when compiled with the runtime). 123 | Note that in the NPM script `asbuild:sum.runtime`, we pass the `--memoryBase 80` option. 124 | This reserves 80 bytes of space (enough for 10 `f64`s) _before_ the AS runtime places its static data in memory. 125 | 126 | It's not necessary to pass this option in `asbuild:sum.standalone` because AS doesn't need to use any `data` segments for the runtime's data structures. 127 | For more details, see https://docs.assemblyscript.org/details/memory 128 | -------------------------------------------------------------------------------- /assembly/fib.ts: -------------------------------------------------------------------------------- 1 | // Copied from https://docs.assemblyscript.org/ 2 | export function fib(n: i32): i32 { 3 | var a = 0, b = 1 4 | for (let i = 0; i < n; i++) { 5 | let t = a + b; a = b; b = t 6 | } 7 | return b 8 | } 9 | -------------------------------------------------------------------------------- /assembly/sum.ts: -------------------------------------------------------------------------------- 1 | export function sum(from: usize, to: usize): f64 { 2 | let total: f64 = 0; 3 | for (let i: usize = from; i < to; i += 1) { 4 | // << 3 is the same as multiplying by 8, the size in bytes of a 64-bit float 5 | // in memory. 6 | total += load(i << 3); 7 | } 8 | return total; 9 | } 10 | -------------------------------------------------------------------------------- /assembly/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../node_modules/assemblyscript/std/assembly.json", 3 | "include": [ 4 | "./**/*.ts" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /assemblyscript_loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is adapted from the AssemblyScript project. See original source at: 3 | * https://github.com/AssemblyScript/assemblyscript/blob/b942d611aafa63f0be12f75dbdd5470361fac13a/lib/loader/index.js 4 | * 5 | * The code is under the Apache License 2.0. See the license at: 6 | * https://github.com/AssemblyScript/assemblyscript/blob/b942d611aafa63f0be12f75dbdd5470361fac13a/LICENSE 7 | */ 8 | 9 | // Runtime header offsets 10 | const ID_OFFSET = -8; 11 | const SIZE_OFFSET = -4; 12 | 13 | // Runtime ids 14 | const ARRAYBUFFER_ID = 0; 15 | const STRING_ID = 1; 16 | const ARRAYBUFFERVIEW_ID = 2; 17 | 18 | // Runtime type information 19 | const ARRAYBUFFERVIEW = 1 << 0; 20 | const ARRAY = 1 << 1; 21 | const SET = 1 << 2; 22 | const MAP = 1 << 3; 23 | const VAL_ALIGN_OFFSET = 5; 24 | const VAL_ALIGN = 1 << VAL_ALIGN_OFFSET; 25 | const VAL_SIGNED = 1 << 10; 26 | const VAL_FLOAT = 1 << 11; 27 | const VAL_NULLABLE = 1 << 12; 28 | const VAL_MANAGED = 1 << 13; 29 | const KEY_ALIGN_OFFSET = 14; 30 | const KEY_ALIGN = 1 << KEY_ALIGN_OFFSET; 31 | const KEY_SIGNED = 1 << 19; 32 | const KEY_FLOAT = 1 << 20; 33 | const KEY_NULLABLE = 1 << 21; 34 | const KEY_MANAGED = 1 << 22; 35 | 36 | // Array(BufferView) layout 37 | const ARRAYBUFFERVIEW_BUFFER_OFFSET = 0; 38 | const ARRAYBUFFERVIEW_DATASTART_OFFSET = 4; 39 | const ARRAYBUFFERVIEW_DATALENGTH_OFFSET = 8; 40 | const ARRAYBUFFERVIEW_SIZE = 12; 41 | const ARRAY_LENGTH_OFFSET = 12; 42 | const ARRAY_SIZE = 16; 43 | 44 | const BIGINT = typeof BigUint64Array !== "undefined"; 45 | const THIS = Symbol(); 46 | const CHUNKSIZE = 1024; 47 | 48 | /** Gets a string from an U32 and an U16 view on a memory. */ 49 | function getStringImpl(buffer, ptr) { 50 | const U32 = new Uint32Array(buffer); 51 | const U16 = new Uint16Array(buffer); 52 | var length = U32[(ptr + SIZE_OFFSET) >>> 2] >>> 1; 53 | var offset = ptr >>> 1; 54 | if (length <= CHUNKSIZE) return String.fromCharCode.apply(String, U16.subarray(offset, offset + length)); 55 | const parts = []; 56 | do { 57 | const last = U16[offset + CHUNKSIZE - 1]; 58 | const size = last >= 0xD800 && last < 0xDC00 ? CHUNKSIZE - 1 : CHUNKSIZE; 59 | parts.push(String.fromCharCode.apply(String, U16.subarray(offset, offset += size))); 60 | length -= size; 61 | } while (length > CHUNKSIZE); 62 | return parts.join("") + String.fromCharCode.apply(String, U16.subarray(offset, offset + length)); 63 | } 64 | 65 | /** Prepares the base module prior to instantiation. */ 66 | function preInstantiate(imports) { 67 | const baseModule = {}; 68 | 69 | function getString(memory, ptr) { 70 | if (!memory) return ""; 71 | return getStringImpl(memory.buffer, ptr); 72 | } 73 | 74 | // add common imports used by stdlib for convenience 75 | const env = (imports.env = imports.env || {}); 76 | env.abort = env.abort || function abort(mesg, file, line, colm) { 77 | const memory = baseModule.memory || env.memory; // prefer exported, otherwise try imported 78 | throw Error("abort: " + getString(memory, mesg) + " at " + getString(memory, file) + ":" + line + ":" + colm); 79 | } 80 | env.trace = env.trace || function trace(mesg, n) { 81 | const memory = baseModule.memory || env.memory; 82 | console.log("trace: " + getString(memory, mesg) + (n ? " " : "") + Array.prototype.slice.call(arguments, 2, 2 + n).join(", ")); 83 | } 84 | imports.Math = imports.Math || Math; 85 | imports.Date = imports.Date || Date; 86 | 87 | return baseModule; 88 | } 89 | 90 | /** Prepares the final module once instantiation is complete. */ 91 | function postInstantiate(baseModule, instance) { 92 | const rawExports = instance.exports; 93 | const memory = rawExports.memory; 94 | const table = rawExports.table; 95 | const alloc = rawExports["__alloc"]; 96 | const retain = rawExports["__retain"]; 97 | const rttiBase = rawExports["__rtti_base"] || ~0; // oob if not present 98 | 99 | /** Gets the runtime type info for the given id. */ 100 | function getInfo(id) { 101 | const U32 = new Uint32Array(memory.buffer); 102 | const count = U32[rttiBase >>> 2]; 103 | if ((id >>>= 0) >= count) throw Error("invalid id: " + id); 104 | return U32[(rttiBase + 4 >>> 2) + id * 2]; 105 | } 106 | 107 | /** Gets the runtime base id for the given id. */ 108 | function getBase(id) { 109 | const U32 = new Uint32Array(memory.buffer); 110 | const count = U32[rttiBase >>> 2]; 111 | if ((id >>>= 0) >= count) throw Error("invalid id: " + id); 112 | return U32[(rttiBase + 4 >>> 2) + id * 2 + 1]; 113 | } 114 | 115 | /** Gets the runtime alignment of a collection's values. */ 116 | function getValueAlign(info) { 117 | return 31 - Math.clz32((info >>> VAL_ALIGN_OFFSET) & 31); // -1 if none 118 | } 119 | 120 | /** Gets the runtime alignment of a collection's keys. */ 121 | function getKeyAlign(info) { 122 | return 31 - Math.clz32((info >>> KEY_ALIGN_OFFSET) & 31); // -1 if none 123 | } 124 | 125 | /** Allocates a new string in the module's memory and returns its retained pointer. */ 126 | function __allocString(str) { 127 | const length = str.length; 128 | const ptr = alloc(length << 1, STRING_ID); 129 | const U16 = new Uint16Array(memory.buffer); 130 | for (var i = 0, p = ptr >>> 1; i < length; ++i) U16[p + i] = str.charCodeAt(i); 131 | return ptr; 132 | } 133 | 134 | baseModule.__allocString = __allocString; 135 | 136 | /** Reads a string from the module's memory by its pointer. */ 137 | function __getString(ptr) { 138 | const buffer = memory.buffer; 139 | const id = new Uint32Array(buffer)[ptr + ID_OFFSET >>> 2]; 140 | if (id !== STRING_ID) throw Error("not a string: " + ptr); 141 | return getStringImpl(buffer, ptr); 142 | } 143 | 144 | baseModule.__getString = __getString; 145 | 146 | /** Gets the view matching the specified alignment, signedness and floatness. */ 147 | function getView(alignLog2, signed, float) { 148 | const buffer = memory.buffer; 149 | if (float) { 150 | switch (alignLog2) { 151 | case 2: return new Float32Array(buffer); 152 | case 3: return new Float64Array(buffer); 153 | } 154 | } else { 155 | switch (alignLog2) { 156 | case 0: return new (signed ? Int8Array : Uint8Array)(buffer); 157 | case 1: return new (signed ? Int16Array : Uint16Array)(buffer); 158 | case 2: return new (signed ? Int32Array : Uint32Array)(buffer); 159 | case 3: return new (signed ? BigInt64Array : BigUint64Array)(buffer); 160 | } 161 | } 162 | throw Error("unsupported align: " + alignLog2); 163 | } 164 | 165 | /** Allocates a new array in the module's memory and returns its retained pointer. */ 166 | function __allocArray(id, values) { 167 | const info = getInfo(id); 168 | if (!(info & (ARRAYBUFFERVIEW | ARRAY))) throw Error("not an array: " + id + " @ " + info); 169 | const align = getValueAlign(info); 170 | const length = values.length; 171 | const buf = alloc(length << align, ARRAYBUFFER_ID); 172 | const arr = alloc(info & ARRAY ? ARRAY_SIZE : ARRAYBUFFERVIEW_SIZE, id); 173 | const U32 = new Uint32Array(memory.buffer); 174 | U32[arr + ARRAYBUFFERVIEW_BUFFER_OFFSET >>> 2] = retain(buf); 175 | U32[arr + ARRAYBUFFERVIEW_DATASTART_OFFSET >>> 2] = buf; 176 | U32[arr + ARRAYBUFFERVIEW_DATALENGTH_OFFSET >>> 2] = length << align; 177 | if (info & ARRAY) U32[arr + ARRAY_LENGTH_OFFSET >>> 2] = length; 178 | const view = getView(align, info & VAL_SIGNED, info & VAL_FLOAT); 179 | if (info & VAL_MANAGED) { 180 | for (let i = 0; i < length; ++i) view[(buf >>> align) + i] = retain(values[i]); 181 | } else { 182 | view.set(values, buf >>> align); 183 | } 184 | return arr; 185 | } 186 | 187 | baseModule.__allocArray = __allocArray; 188 | 189 | /** Gets a live view on an array's values in the module's memory. Infers the array type from RTTI. */ 190 | function __getArrayView(arr) { 191 | const U32 = new Uint32Array(memory.buffer); 192 | const id = U32[arr + ID_OFFSET >>> 2]; 193 | const info = getInfo(id); 194 | if (!(info & ARRAYBUFFERVIEW)) throw Error("not an array: " + id); 195 | const align = getValueAlign(info); 196 | var buf = U32[arr + ARRAYBUFFERVIEW_DATASTART_OFFSET >>> 2]; 197 | const length = info & ARRAY 198 | ? U32[arr + ARRAY_LENGTH_OFFSET >>> 2] 199 | : U32[buf + SIZE_OFFSET >>> 2] >>> align; 200 | return getView(align, info & VAL_SIGNED, info & VAL_FLOAT) 201 | .subarray(buf >>>= align, buf + length); 202 | } 203 | 204 | baseModule.__getArrayView = __getArrayView; 205 | 206 | /** Copies an array's values from the module's memory. Infers the array type from RTTI. */ 207 | function __getArray(arr) { 208 | const input = __getArrayView(arr); 209 | const len = input.length; 210 | const out = new Array(len); 211 | for (let i = 0; i < len; i++) out[i] = input[i]; 212 | return out; 213 | } 214 | 215 | baseModule.__getArray = __getArray; 216 | 217 | /** Copies an ArrayBuffer's value from the module's memory. */ 218 | function __getArrayBuffer(ptr) { 219 | const buffer = memory.buffer; 220 | const length = new Uint32Array(buffer)[ptr + SIZE_OFFSET >>> 2]; 221 | return buffer.slice(ptr, ptr + length); 222 | } 223 | 224 | baseModule.__getArrayBuffer = __getArrayBuffer; 225 | 226 | /** Copies a typed array's values from the module's memory. */ 227 | function getTypedArray(Type, alignLog2, ptr) { 228 | return new Type(getTypedArrayView(Type, alignLog2, ptr)); 229 | } 230 | 231 | /** Gets a live view on a typed array's values in the module's memory. */ 232 | function getTypedArrayView(Type, alignLog2, ptr) { 233 | const buffer = memory.buffer; 234 | const U32 = new Uint32Array(buffer); 235 | const bufPtr = U32[ptr + ARRAYBUFFERVIEW_DATASTART_OFFSET >>> 2]; 236 | return new Type(buffer, bufPtr, U32[bufPtr + SIZE_OFFSET >>> 2] >>> alignLog2); 237 | } 238 | 239 | baseModule.__getInt8Array = getTypedArray.bind(null, Int8Array, 0); 240 | baseModule.__getInt8ArrayView = getTypedArrayView.bind(null, Int8Array, 0); 241 | baseModule.__getUint8Array = getTypedArray.bind(null, Uint8Array, 0); 242 | baseModule.__getUint8ArrayView = getTypedArrayView.bind(null, Uint8Array, 0); 243 | baseModule.__getUint8ClampedArray = getTypedArray.bind(null, Uint8ClampedArray, 0); 244 | baseModule.__getUint8ClampedArrayView = getTypedArrayView.bind(null, Uint8ClampedArray, 0); 245 | baseModule.__getInt16Array = getTypedArray.bind(null, Int16Array, 1); 246 | baseModule.__getInt16ArrayView = getTypedArrayView.bind(null, Int16Array, 1); 247 | baseModule.__getUint16Array = getTypedArray.bind(null, Uint16Array, 1); 248 | baseModule.__getUint16ArrayView = getTypedArrayView.bind(null, Uint16Array, 1); 249 | baseModule.__getInt32Array = getTypedArray.bind(null, Int32Array, 2); 250 | baseModule.__getInt32ArrayView = getTypedArrayView.bind(null, Int32Array, 2); 251 | baseModule.__getUint32Array = getTypedArray.bind(null, Uint32Array, 2); 252 | baseModule.__getUint32ArrayView = getTypedArrayView.bind(null, Uint32Array, 2); 253 | if (BIGINT) { 254 | baseModule.__getInt64Array = getTypedArray.bind(null, BigInt64Array, 3); 255 | baseModule.__getInt64ArrayView = getTypedArrayView.bind(null, BigInt64Array, 3); 256 | baseModule.__getUint64Array = getTypedArray.bind(null, BigUint64Array, 3); 257 | baseModule.__getUint64ArrayView = getTypedArrayView.bind(null, BigUint64Array, 3); 258 | } 259 | baseModule.__getFloat32Array = getTypedArray.bind(null, Float32Array, 2); 260 | baseModule.__getFloat32ArrayView = getTypedArrayView.bind(null, Float32Array, 2); 261 | baseModule.__getFloat64Array = getTypedArray.bind(null, Float64Array, 3); 262 | baseModule.__getFloat64ArrayView = getTypedArrayView.bind(null, Float64Array, 3); 263 | 264 | /** Tests whether an object is an instance of the class represented by the specified base id. */ 265 | function __instanceof(ptr, baseId) { 266 | const U32 = new Uint32Array(memory.buffer); 267 | var id = U32[(ptr + ID_OFFSET) >>> 2]; 268 | if (id <= U32[rttiBase >>> 2]) { 269 | do if (id == baseId) return true; 270 | while (id = getBase(id)); 271 | } 272 | return false; 273 | } 274 | 275 | baseModule.__instanceof = __instanceof; 276 | 277 | // Pull basic exports to baseModule so code in preInstantiate can use them 278 | baseModule.memory = baseModule.memory || memory; 279 | baseModule.table = baseModule.table || table; 280 | 281 | // Demangle exports and provide the usual utility on the prototype 282 | return demangle(rawExports, baseModule); 283 | } 284 | 285 | function isResponse(o) { 286 | return typeof Response !== "undefined" && o instanceof Response; 287 | } 288 | 289 | /** Asynchronously instantiates an AssemblyScript module from anything that can be instantiated. */ 290 | export async function instantiate(source, imports) { 291 | if (isResponse(source = await source)) return instantiateStreaming(source, imports); 292 | return postInstantiate( 293 | preInstantiate(imports || (imports = {})), 294 | await WebAssembly.instantiate( 295 | source instanceof WebAssembly.Module 296 | ? source 297 | : await WebAssembly.compile(source), 298 | imports 299 | ) 300 | ); 301 | } 302 | 303 | /** Synchronously instantiates an AssemblyScript module from a WebAssembly.Module or binary buffer. */ 304 | export function instantiateSync(source, imports) { 305 | return postInstantiate( 306 | preInstantiate(imports || (imports = {})), 307 | new WebAssembly.Instance( 308 | source instanceof WebAssembly.Module 309 | ? source 310 | : new WebAssembly.Module(source), 311 | imports 312 | ) 313 | ) 314 | } 315 | 316 | /** Asynchronously instantiates an AssemblyScript module from a response, i.e. as obtained by `fetch`. */ 317 | export async function instantiateStreaming(source, imports) { 318 | if (!WebAssembly.instantiateStreaming) { 319 | return instantiate( 320 | isResponse(source = await source) 321 | ? source.arrayBuffer() 322 | : source, 323 | imports 324 | ); 325 | } 326 | return postInstantiate( 327 | preInstantiate(imports || (imports = {})), 328 | (await WebAssembly.instantiateStreaming(source, imports)).instance 329 | ); 330 | } 331 | 332 | /** Demangles an AssemblyScript module's exports to a friendly object structure. */ 333 | export function demangle(exports, baseModule) { 334 | var module = baseModule ? Object.create(baseModule) : {}; 335 | var setArgumentsLength = exports["__argumentsLength"] 336 | ? function(length) { exports["__argumentsLength"].value = length; } 337 | : exports["__setArgumentsLength"] || exports["__setargc"] || function() {}; 338 | for (let internalName in exports) { 339 | if (!Object.prototype.hasOwnProperty.call(exports, internalName)) continue; 340 | const elem = exports[internalName]; 341 | let parts = internalName.split("."); 342 | let curr = module; 343 | while (parts.length > 1) { 344 | let part = parts.shift(); 345 | if (!Object.prototype.hasOwnProperty.call(curr, part)) curr[part] = {}; 346 | curr = curr[part]; 347 | } 348 | let name = parts[0]; 349 | let hash = name.indexOf("#"); 350 | if (hash >= 0) { 351 | let className = name.substring(0, hash); 352 | let classElem = curr[className]; 353 | if (typeof classElem === "undefined" || !classElem.prototype) { 354 | let ctor = function(...args) { 355 | return ctor.wrap(ctor.prototype.constructor(0, ...args)); 356 | }; 357 | ctor.prototype = { 358 | valueOf: function valueOf() { 359 | return this[THIS]; 360 | } 361 | }; 362 | ctor.wrap = function(thisValue) { 363 | return Object.create(ctor.prototype, { [THIS]: { value: thisValue, writable: false } }); 364 | }; 365 | if (classElem) Object.getOwnPropertyNames(classElem).forEach(name => 366 | Object.defineProperty(ctor, name, Object.getOwnPropertyDescriptor(classElem, name)) 367 | ); 368 | curr[className] = ctor; 369 | } 370 | name = name.substring(hash + 1); 371 | curr = curr[className].prototype; 372 | if (/^(get|set):/.test(name)) { 373 | if (!Object.prototype.hasOwnProperty.call(curr, name = name.substring(4))) { 374 | let getter = exports[internalName.replace("set:", "get:")]; 375 | let setter = exports[internalName.replace("get:", "set:")]; 376 | Object.defineProperty(curr, name, { 377 | get: function() { return getter(this[THIS]); }, 378 | set: function(value) { setter(this[THIS], value); }, 379 | enumerable: true 380 | }); 381 | } 382 | } else { 383 | if (name === 'constructor') { 384 | (curr[name] = (...args) => { 385 | setArgumentsLength(args.length); 386 | return elem(...args); 387 | }).original = elem; 388 | } else { // instance method 389 | (curr[name] = function(...args) { // ! 390 | setArgumentsLength(args.length); 391 | return elem(this[THIS], ...args); 392 | }).original = elem; 393 | } 394 | } 395 | } else { 396 | if (/^(get|set):/.test(name)) { 397 | if (!Object.prototype.hasOwnProperty.call(curr, name = name.substring(4))) { 398 | Object.defineProperty(curr, name, { 399 | get: exports[internalName.replace("set:", "get:")], 400 | set: exports[internalName.replace("get:", "set:")], 401 | enumerable: true 402 | }); 403 | } 404 | } else if (typeof elem === "function" && elem !== setArgumentsLength) { 405 | (curr[name] = (...args) => { 406 | setArgumentsLength(args.length); 407 | return elem(...args); 408 | }).original = elem; 409 | } else { 410 | curr[name] = elem; 411 | } 412 | } 413 | } 414 | return module; 415 | } 416 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "giraffe", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "assemblyscript": { 8 | "version": "0.9.2", 9 | "resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.9.2.tgz", 10 | "integrity": "sha512-eqPbxS323ivXdUclGJjvihxEEMBaXcNYHLb17j3j3UaYvkqVPNSrNuJ/H1r7G/UwmQnFQ8NDrQ9BncOFCGmahg==", 11 | "dev": true, 12 | "requires": { 13 | "binaryen": "90.0.0-nightly.20200214", 14 | "long": "^4.0.0" 15 | } 16 | }, 17 | "binaryen": { 18 | "version": "90.0.0-nightly.20200214", 19 | "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-90.0.0-nightly.20200214.tgz", 20 | "integrity": "sha512-ZHajaPd2aP6kDrM9q77afnA402Ufsuw9yLUxtAfgmIglP1JMB2XSRmaXc5pgCELaX53PWJIdcH07LL8uNfTBRw==", 21 | "dev": true 22 | }, 23 | "long": { 24 | "version": "4.0.0", 25 | "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", 26 | "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", 27 | "dev": true 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deno_assemblyscript_example", 3 | "scripts": { 4 | "asbuild:fib.runtime": "asc assembly/fib.ts -b assembly/fib.runtime.wasm -t assembly/fib.runtime.wat --validate --sourceMap --optimize", 5 | "asbuild:fib.standalone": "asc assembly/fib.ts -b assembly/fib.standalone.wasm -t assembly/fib.standalone.wat --runtime none --validate --sourceMap --optimize", 6 | "asbuild:sum.runtime": "asc assembly/sum.ts -b assembly/sum.runtime.wasm -t assembly/sum.runtime.wat --importMemory --memoryBase 80 --validate --sourceMap --optimize", 7 | "asbuild:sum.standalone": "asc assembly/sum.ts -b assembly/sum.standalone.wasm -t assembly/sum.standalone.wat --importMemory --runtime none --validate --sourceMap --optimize", 8 | "asbuild": "npm run asbuild:fib.runtime && npm run asbuild:fib.standalone && npm run asbuild:sum.runtime && npm run asbuild:sum.standalone" 9 | }, 10 | "devDependencies": { 11 | "assemblyscript": "^0.9.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test_fib.ts: -------------------------------------------------------------------------------- 1 | // The AssemblyScript loader provides convenience and support for AS's runtime. 2 | // See https://docs.assemblyscript.org/basics/loader for more details. 3 | import * as loader from "./assemblyscript_loader.js"; 4 | const wasmModule = loader.instantiateSync(Deno.readFileSync("./assembly/fib.runtime.wasm")); 5 | 6 | console.log("fib(10) from module with runtime:"); 7 | console.log(wasmModule.fib(10)); 8 | 9 | // Deno can also import WASM modules directly. Because this module has no dependency 10 | // on AS's runtime, we don't need to use the loader. 11 | import { fib } from "./assembly/fib.standalone.wasm"; 12 | 13 | console.log("fib(10) from standalone module:"); 14 | console.log(fib(10)); 15 | -------------------------------------------------------------------------------- /test_sum.ts: -------------------------------------------------------------------------------- 1 | import * as loader from "./assemblyscript_loader.js"; 2 | 3 | function runWithLoader() { 4 | const memory = new WebAssembly.Memory({ initial: 1 }); 5 | // Treat the raw memory bytes as an array of Float64s so we can work with it 6 | // conveniently. Assign the first 10 elements to 1. 7 | const floats = new Float64Array(memory.buffer); 8 | for (let i = 0; i < 10; i += 1) { 9 | floats[i] = 1; 10 | } 11 | 12 | const wasmModule = loader.instantiateSync( 13 | Deno.readFileSync("./assembly/sum.runtime.wasm"), 14 | // We need to pass in the memory in the "env" namespace. You'll see different 15 | // conventions for this (e.g. the MDN docs usually use the "js" namespace for 16 | // memory imports) but this is AssemblyScript's convention. 17 | { env: { memory } } 18 | ); 19 | 20 | console.log("sum(0, 10) from module with runtime:"); 21 | console.log(wasmModule.sum(0, 10)); 22 | } 23 | 24 | async function runWithStandalone() { 25 | const memory = new WebAssembly.Memory({ initial: 1 }); 26 | const floats = new Float64Array(memory.buffer); 27 | for (let i = 0; i < 10; i += 1) { 28 | floats[i] = 1; 29 | } 30 | 31 | // Instead of importing the .wasm file directly, we'll instantiate it using the 32 | // same APIs that are available in the web browser (except Deno.readFileSync, 33 | // of course). 34 | const lib = await WebAssembly.instantiate( 35 | new Uint8Array(Deno.readFileSync("./assembly/sum.standalone.wasm")), 36 | { env: { memory } } 37 | ); 38 | 39 | console.log("sum(0, 10) from standalone module:"); 40 | console.log(lib.instance.exports.sum(0, 10)); 41 | } 42 | 43 | runWithLoader(); 44 | runWithStandalone(); 45 | --------------------------------------------------------------------------------