├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── asyncify.mjs ├── example ├── README.md ├── browser │ ├── imports.mjs │ ├── index.html │ └── index.mjs ├── build.ps1 ├── example.rs ├── example.wasm └── node │ ├── imports.mjs │ └── index.mjs ├── index.d.ts ├── package.json ├── rollup.config.js └── test ├── callback.wat ├── index.mjs ├── mem-export.wat └── mem-import.wat /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Expected Behavior 2 | 3 | 4 | ## Actual Behavior 5 | 6 | 7 | ## Steps to Reproduce the Problem 8 | 9 | 1. 10 | 1. 11 | 1. 12 | 13 | ## Specifications 14 | 15 | - Version: 16 | - Platform: -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | > It's a good idea to open an issue first for discussion. 4 | 5 | - [ ] Tests pass 6 | - [ ] Appropriate changes to README are included in PR -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | /node_modules 4 | /dist 5 | /package-lock.json 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google.com/conduct/). 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asyncify 2 | 3 | This is a JavaScript wrapper intended to be used with Asyncify feature of Binaryen. 4 | 5 | Together, they allow to use asynchronous APIs (such as most Web APIs) from within WebAssembly written and compiled from any source language. 6 | 7 | ## Usage 8 | 9 | ### WebAssembly side 10 | 11 | Import and use required APIs as regular synchronous FFI functions in your code. 12 | 13 | After the code is compiled to WebAssembly, post-process it using `wasm-opt` from the [Binaryen toolchain](https://github.com/WebAssembly/binaryen): 14 | 15 | ```shell 16 | wasm-opt --asyncify [-O] [--pass-arg=asyncify-imports@module1.func1,...] in.wasm -o out.wasm 17 | ``` 18 | 19 | ### JavaScript side 20 | 21 | First, import asyncify via: 22 | 23 | ```javascript 24 | import * as Asyncify from 'https://unpkg.com/asyncify-wasm?module'; 25 | ``` 26 | 27 | Compilation / instantiation APIs are designed to be drop-in replacements for those of regular `WebAssembly` interface, but with `async` support. 28 | 29 | Then, you can use `new Asyncify.Instance`, `Asyncify.instantiate` and `Asyncify.instantiateStreaming` like you would with corresponding `WebAssembly` functions, but with added support for `async` imports and all exports wrapped into async functions, too. 30 | 31 | For example: 32 | 33 | ```js 34 | let { instance } = await Asyncify.instantiateStreaming(fetch('./out.wasm'), { 35 | get_resource_text: async url => { 36 | let response = await fetch(readWasmString(instance, url)); 37 | if (!response.ok) { 38 | throw new Error(`HTTP ${response.status}: ${response.statusText}`); 39 | } 40 | return passStringToWasm(instance, await response.text()); 41 | } 42 | }); 43 | 44 | await instance.exports._start(); 45 | ``` 46 | -------------------------------------------------------------------------------- /asyncify.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Put `__asyncify_data` somewhere at the start. 18 | // This address is pretty hand-wavy and we might want to make it configurable in future. 19 | // See https://github.com/WebAssembly/binaryen/blob/6371cf63687c3f638b599e086ca668c04a26cbbb/src/passes/Asyncify.cpp#L106-L113 20 | // for structure details. 21 | const DATA_ADDR = 16; 22 | // Place actual data right after the descriptor (which is 2 * sizeof(i32) = 8 bytes). 23 | const DATA_START = DATA_ADDR + 8; 24 | // End data at 1024 bytes. This is where the unused area by Clang ends and real stack / data begins. 25 | // Because this might differ between languages and parameters passed to wasm-ld, ideally we would 26 | // use `__stack_pointer` here, but, sadly, it's not exposed via exports yet. 27 | const DATA_END = 1024; 28 | 29 | const WRAPPED_EXPORTS = new WeakMap(); 30 | 31 | const State = { 32 | None: 0, 33 | Unwinding: 1, 34 | Rewinding: 2 35 | }; 36 | 37 | function isPromise(obj) { 38 | return ( 39 | !!obj && 40 | (typeof obj === 'object' || typeof obj === 'function') && 41 | typeof obj.then === 'function' 42 | ); 43 | } 44 | 45 | function proxyGet(obj, transform) { 46 | return new Proxy(obj, { 47 | get: (obj, name) => transform(obj[name]) 48 | }); 49 | } 50 | 51 | class Asyncify { 52 | constructor() { 53 | this.value = undefined; 54 | this.exports = null; 55 | } 56 | 57 | getState() { 58 | return this.exports.asyncify_get_state(); 59 | } 60 | 61 | assertNoneState() { 62 | let state = this.getState(); 63 | if (state !== State.None) { 64 | throw new Error(`Invalid async state ${state}, expected 0.`); 65 | } 66 | } 67 | 68 | wrapImportFn(fn) { 69 | return (...args) => { 70 | if (this.getState() === State.Rewinding) { 71 | this.exports.asyncify_stop_rewind(); 72 | return this.value; 73 | } 74 | this.assertNoneState(); 75 | let value = fn(...args); 76 | if (!isPromise(value)) { 77 | return value; 78 | } 79 | this.exports.asyncify_start_unwind(DATA_ADDR); 80 | this.value = value; 81 | }; 82 | } 83 | 84 | wrapModuleImports(module) { 85 | return proxyGet(module, value => { 86 | if (typeof value === 'function') { 87 | return this.wrapImportFn(value); 88 | } 89 | return value; 90 | }); 91 | } 92 | 93 | wrapImports(imports) { 94 | if (imports === undefined) return; 95 | 96 | return proxyGet(imports, (moduleImports = Object.create(null)) => 97 | this.wrapModuleImports(moduleImports) 98 | ); 99 | } 100 | 101 | wrapExportFn(fn) { 102 | let newExport = WRAPPED_EXPORTS.get(fn); 103 | 104 | if (newExport !== undefined) { 105 | return newExport; 106 | } 107 | 108 | newExport = async (...args) => { 109 | this.assertNoneState(); 110 | 111 | let result = fn(...args); 112 | 113 | while (this.getState() === State.Unwinding) { 114 | this.exports.asyncify_stop_unwind(); 115 | this.value = await this.value; 116 | this.assertNoneState(); 117 | this.exports.asyncify_start_rewind(DATA_ADDR); 118 | result = fn(...args); 119 | } 120 | 121 | this.assertNoneState(); 122 | 123 | return result; 124 | }; 125 | 126 | WRAPPED_EXPORTS.set(fn, newExport); 127 | 128 | return newExport; 129 | } 130 | 131 | wrapExports(exports) { 132 | let newExports = Object.create(null); 133 | 134 | for (let exportName in exports) { 135 | let value = exports[exportName]; 136 | if (typeof value === 'function' && !exportName.startsWith('asyncify_')) { 137 | value = this.wrapExportFn(value); 138 | } 139 | Object.defineProperty(newExports, exportName, { 140 | enumerable: true, 141 | value 142 | }); 143 | } 144 | 145 | WRAPPED_EXPORTS.set(exports, newExports); 146 | 147 | return newExports; 148 | } 149 | 150 | init(instance, imports) { 151 | const { exports } = instance; 152 | 153 | const memory = exports.memory || (imports.env && imports.env.memory); 154 | 155 | new Int32Array(memory.buffer, DATA_ADDR).set([DATA_START, DATA_END]); 156 | 157 | this.exports = this.wrapExports(exports); 158 | 159 | Object.setPrototypeOf(instance, Instance.prototype); 160 | } 161 | } 162 | 163 | export class Instance extends WebAssembly.Instance { 164 | constructor(module, imports) { 165 | let state = new Asyncify(); 166 | super(module, state.wrapImports(imports)); 167 | state.init(this, imports); 168 | } 169 | 170 | get exports() { 171 | return WRAPPED_EXPORTS.get(super.exports); 172 | } 173 | } 174 | 175 | Object.defineProperty(Instance.prototype, 'exports', { enumerable: true }); 176 | 177 | export async function instantiate(source, imports) { 178 | let state = new Asyncify(); 179 | let result = await WebAssembly.instantiate( 180 | source, 181 | state.wrapImports(imports) 182 | ); 183 | state.init( 184 | result instanceof WebAssembly.Instance ? result : result.instance, 185 | imports 186 | ); 187 | return result; 188 | } 189 | 190 | export async function instantiateStreaming(source, imports) { 191 | let state = new Asyncify(); 192 | let result = await WebAssembly.instantiateStreaming( 193 | source, 194 | state.wrapImports(imports) 195 | ); 196 | state.init(result.instance, imports); 197 | return result; 198 | } 199 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/asyncify/92463298e266db68bba35a20dd6ac0c1ecdc4c98/example/README.md -------------------------------------------------------------------------------- /example/browser/imports.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const dialogElem = document.getElementById('numberDialog'); 18 | const counterElem = document.getElementById('counter'); 19 | const formElem = document.getElementById('numberForm'); 20 | const numberElem = document.getElementById('number'); 21 | 22 | // Feature-detect and polyfill
. 23 | if (formElem.method !== 'dialog') { 24 | formElem.addEventListener('submit', (event) => { 25 | event.preventDefault(); 26 | dialogElem.close(); 27 | }); 28 | } 29 | 30 | let counter = 0; 31 | 32 | export function read_number() { 33 | return new Promise((resolve, reject) => { 34 | dialogElem.onclose = function() { 35 | let result = numberElem.valueAsNumber; 36 | formElem.reset(); 37 | resolve(result); 38 | }; 39 | 40 | dialogElem.oncancel = () => reject(new Error('Cancelled by user.')); 41 | 42 | counterElem.textContent = ++counter; 43 | dialogElem.showModal(); 44 | }); 45 | } 46 | 47 | export function print_number(num) { 48 | alert(num); 49 | } 50 | -------------------------------------------------------------------------------- /example/browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /example/browser/index.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { instantiateStreaming } from '../../asyncify.mjs'; 18 | import * as testImports from './imports.mjs'; 19 | 20 | instantiateStreaming(fetch('../example.wasm'), { 21 | env: testImports 22 | }) 23 | .then(result => result.instance.exports.run()) 24 | .catch(alert); 25 | -------------------------------------------------------------------------------- /example/build.ps1: -------------------------------------------------------------------------------- 1 | rustc --target wasm32-unknown-unknown --crate-type cdylib -O example.rs 2 | wasm-opt -O2 --asyncify example.wasm -o example.wasm 3 | -------------------------------------------------------------------------------- /example/example.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | extern "C" { 18 | fn read_number() -> i32; 19 | fn print_number(num: i32); 20 | } 21 | 22 | #[no_mangle] 23 | pub extern fn run() { 24 | let a = unsafe { read_number() }; 25 | let b = unsafe { read_number() }; 26 | let res = a + b; 27 | unsafe { print_number(res) }; 28 | } 29 | -------------------------------------------------------------------------------- /example/example.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/asyncify/92463298e266db68bba35a20dd6ac0c1ecdc4c98/example/example.wasm -------------------------------------------------------------------------------- /example/node/imports.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { createInterface } from 'readline'; 18 | 19 | const rl = createInterface({ 20 | input: process.stdin, 21 | output: process.stdout 22 | }); 23 | rl.pause(); 24 | 25 | function question(prompt) { 26 | return new Promise(resolve => rl.question(prompt, resolve)); 27 | } 28 | 29 | let counter = 0; 30 | 31 | export async function read_number() { 32 | let res = await question(`Enter number #${++counter}: `); 33 | rl.pause(); 34 | 35 | let resNumber = res | 0; 36 | if (resNumber.toString() !== res) { 37 | throw new Error('Invalid integer.'); 38 | } 39 | return resNumber; 40 | } 41 | 42 | export function print_number(num) { 43 | console.log(num); 44 | } 45 | -------------------------------------------------------------------------------- /example/node/index.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | import { readFileSync } from 'fs'; 19 | import { fileURLToPath } from 'url'; 20 | import { Instance } from '../../asyncify.mjs'; 21 | import * as testImports from './imports.mjs'; 22 | 23 | process.on('unhandledRejection', err => { 24 | throw err; 25 | }); 26 | 27 | const wasmContents = readFileSync( 28 | fileURLToPath(`${import.meta.url}/../../example.wasm`) 29 | ); 30 | const wasmModule = new WebAssembly.Module(wasmContents); 31 | const wasmInstance = new Instance(wasmModule, { 32 | env: testImports 33 | }); 34 | 35 | wasmInstance.exports.run(); 36 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export class Instance extends WebAssembly.Instance {} 2 | 3 | export const instantiate: typeof WebAssembly.instantiate; 4 | export const instantiateStreaming: typeof WebAssembly.instantiateStreaming; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asyncify-wasm", 3 | "version": "1.2.1", 4 | "description": "Standalone Asyncify helper for Binaryen", 5 | "main": "dist/asyncify.cjs", 6 | "module": "dist/asyncify.mjs", 7 | "directories": { 8 | "example": "example" 9 | }, 10 | "scripts": { 11 | "prepublishOnly": "rollup -c", 12 | "example": "node --experimental-modules --no-warnings example/node/index.mjs", 13 | "test": "node --experimental-modules --no-warnings test/index.mjs" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/GoogleChromeLabs/asyncify.git" 18 | }, 19 | "files": [ 20 | "dist/asyncify.*", 21 | "index.d.ts" 22 | ], 23 | "keywords": [ 24 | "asyncify", 25 | "binaryen", 26 | "webassembly", 27 | "wasm", 28 | "emscripten" 29 | ], 30 | "author": "Ingvar Stepanyan ", 31 | "license": "Apache-2.0", 32 | "bugs": { 33 | "url": "https://github.com/GoogleChromeLabs/asyncify/issues" 34 | }, 35 | "homepage": "https://github.com/GoogleChromeLabs/asyncify#readme", 36 | "devDependencies": { 37 | "binaryen": "^101.0.0", 38 | "rollup": "^1.29.0", 39 | "rollup-plugin-terser": "^5.2.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { terser } from 'rollup-plugin-terser'; 18 | 19 | export default { 20 | input: 'asyncify.mjs', 21 | plugins: [ 22 | terser({ 23 | compress: { 24 | passes: 2 25 | } 26 | }) 27 | ], 28 | output: [ 29 | { 30 | format: 'cjs', 31 | file: 'dist/asyncify.cjs' 32 | }, 33 | { 34 | format: 'esm', 35 | file: 'dist/asyncify.mjs' 36 | } 37 | ] 38 | }; 39 | -------------------------------------------------------------------------------- /test/callback.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $env.call_main (import "env" "call_main") (param i32)) 3 | (func $callback (export "callback") (param $counter i32) 4 | (if 5 | (i32.gt_s 6 | (local.get $counter) 7 | (i32.const 0)) 8 | (call $env.call_main 9 | (local.tee $counter 10 | (i32.sub 11 | (local.get $counter) 12 | (i32.const 1)))))) 13 | (memory $memory (export "memory") 16)) 14 | -------------------------------------------------------------------------------- /test/index.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { promises as fsp } from 'fs'; 18 | import { fileURLToPath } from 'url'; 19 | import { promisify } from 'util'; 20 | import assert from 'assert'; 21 | import binaryen from 'binaryen'; 22 | import * as Asyncify from '../asyncify.mjs'; 23 | 24 | process.on('unhandledRejection', err => { 25 | throw err; 26 | }); 27 | 28 | function getSimpleImportObject() { 29 | return { 30 | env: { 31 | memory: new WebAssembly.Memory({ initial: 16 }), 32 | get_time: Date.now, 33 | sleep: promisify(setTimeout) 34 | } 35 | }; 36 | } 37 | 38 | async function runDelayedTest(name, path, importObject, callback) { 39 | console.group(name); 40 | 41 | const src = await fsp.readFile( 42 | fileURLToPath(`${import.meta.url}/../${path}`), 43 | 'utf-8' 44 | ); 45 | 46 | const binaryenModule = binaryen.parseText(src); 47 | binaryenModule.runPasses(['asyncify']); 48 | binaryenModule.optimize(); 49 | 50 | const wasmContents = binaryenModule.emitBinary(); 51 | 52 | const wasmModule = new WebAssembly.Module(wasmContents); 53 | await callback(() => new Asyncify.Instance(wasmModule, importObject).exports); 54 | 55 | console.log('Test passed.'); 56 | 57 | console.groupEnd(); 58 | } 59 | 60 | async function runTest(name, path, importObject, callback) { 61 | return runDelayedTest(name, path, importObject, getExports => 62 | callback(getExports()) 63 | ); 64 | } 65 | 66 | async function simpleTestCb({ run, run2 }) { 67 | // Check that the export works as an asynchronous function. 68 | assert.strictEqual(await run(), 1); 69 | 70 | // Ensure that referential equality between exports is preserved for async wrappers. 71 | assert.strictEqual(run, run2); 72 | } 73 | 74 | (async () => { 75 | await runTest( 76 | 'Exported memory', 77 | 'mem-export.wat', 78 | getSimpleImportObject(), 79 | simpleTestCb 80 | ); 81 | await runTest( 82 | 'Imported memory', 83 | 'mem-import.wat', 84 | getSimpleImportObject(), 85 | simpleTestCb 86 | ); 87 | 88 | function proxyGet(getter) { 89 | return new Proxy(Object.create(null), { 90 | get: (_, name) => getter(name) 91 | }); 92 | } 93 | 94 | { 95 | let realImportObject = getSimpleImportObject(); 96 | 97 | await runTest( 98 | 'Proxy-based imports', 99 | 'mem-export.wat', 100 | proxyGet(moduleName => 101 | proxyGet(importName => realImportObject[moduleName][importName]) 102 | ), 103 | simpleTestCb 104 | ); 105 | } 106 | 107 | { 108 | let savedCallback; 109 | 110 | await runTest( 111 | 'Re-entrance', 112 | 'callback.wat', 113 | { 114 | env: { 115 | async call_main(counter) { 116 | await Promise.resolve(); 117 | await savedCallback(counter); 118 | } 119 | } 120 | }, 121 | async ({ callback }) => { 122 | savedCallback = callback; 123 | await callback(10); 124 | } 125 | ); 126 | } 127 | 128 | await runTest( 129 | 'Error handling', 130 | 'mem-export.wat', 131 | { 132 | env: { 133 | ...getSimpleImportObject().env, 134 | async sleep() { 135 | throw new Error('Expected error'); 136 | } 137 | } 138 | }, 139 | async ({ run }) => { 140 | for (let i = 0; i < 2; i++) { 141 | await assert.rejects(run, { 142 | name: 'Error', 143 | message: 'Expected error', 144 | stack: /:wasm-function\[2\]:/m 145 | }); 146 | } 147 | } 148 | ); 149 | 150 | await runDelayedTest( 151 | 'Error handling for missing import module', 152 | 'mem-export.wat', 153 | {}, 154 | getExports => 155 | assert.throws(getExports, { 156 | name: 'LinkError', 157 | message: 158 | 'WebAssembly.Instance(): Import #0 module="env" function="get_time" error: function import requires a callable' 159 | }) 160 | ); 161 | })(); 162 | -------------------------------------------------------------------------------- /test/mem-export.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $env.get_time (import "env" "get_time") (result i32)) 3 | (func $env.sleep (import "env" "sleep") (param i32)) 4 | (func $run (export "run") (result i32) 5 | (local $start i32) 6 | (local.set $start 7 | (call $env.get_time)) 8 | (call $env.sleep 9 | (i32.const 100)) 10 | (i32.gt_u 11 | (call $env.get_time) 12 | (local.get $start))) 13 | (export "run2" (func $run)) 14 | (memory $memory (export "memory") 16)) 15 | -------------------------------------------------------------------------------- /test/mem-import.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "env" "memory" (memory $memory 16)) 3 | (func $env.get_time (import "env" "get_time") (result i32)) 4 | (func $env.sleep (import "env" "sleep") (param i32)) 5 | (func $run (export "run") (result i32) 6 | (local $start i32) 7 | (local.set $start 8 | (call $env.get_time)) 9 | (call $env.sleep 10 | (i32.const 100)) 11 | (i32.gt_u 12 | (call $env.get_time) 13 | (local.get $start))) 14 | (export "run2" (func $run))) 15 | --------------------------------------------------------------------------------