diff --git a/lib/asbind-instance/asbind-instance.js b/lib/asbind-instance/asbind-instance.js index d09316c..4422792 100644 --- a/lib/asbind-instance/asbind-instance.js +++ b/lib/asbind-instance/asbind-instance.js @@ -45,6 +45,7 @@ export default class AsbindInstance { this.unboundExports = {}; this.exports = {}; this.importObject = {}; + this.asyncifyStorageSize = 8 * 1024; } getTypeId(typeName) { @@ -62,6 +63,11 @@ export default class AsbindInstance { } _validate() { + this.isAsyncifyModule = Boolean( + WebAssembly.Module.exports(this.module).find( + ({ name }) => name === "asyncify_start_unwind" + ) + ); if ( !WebAssembly.Module.exports(this.module).find(exp => exp.name === "__new") ) { @@ -136,5 +142,11 @@ export default class AsbindInstance { descriptor ); } + + if (!this.isAsyncifyModule) { + // If this module wasn’t built with Ayncify, we mock + // the asyncify state function to return that we are in “normal” mode. + this.exports.asyncify_get_state = () => 0; + } } } diff --git a/lib/asbind-instance/bind-function.js b/lib/asbind-instance/bind-function.js index e96b347..a5e7208 100644 --- a/lib/asbind-instance/bind-function.js +++ b/lib/asbind-instance/bind-function.js @@ -27,6 +27,18 @@ export function bindImportFunction( // Create a wrapper function that applies the correct converter function to arguments and // return value respectively. return function(...args) { + if (asbindInstance.exports.asyncify_get_state() === 2 /* Rewinding */) { + asbindInstance.loadedModule.exports.asyncify_stop_rewind(); + asbindInstance.loadedModule.exports.__unpin( + asbindInstance.asyncifyState.ptr + ); + return returnValueConverterFunction( + asbindInstance, + asbindInstance.asyncifyState.value, + importedFunctionDescriptor.returnType + ); + } + if (args.length != argumentConverterFunctions.length) { throw Error( `Expected ${argumentConverterFunctions.length} arguments, got ${args.length}` @@ -40,11 +52,35 @@ export function bindImportFunction( ) ); const result = importedFunction(...newArgs); - return returnValueConverterFunction( - asbindInstance, - result, - importedFunctionDescriptor.returnType + if (!asbindInstance.isAsyncifyModule || !(result instanceof Promise)) { + return returnValueConverterFunction( + asbindInstance, + result, + importedFunctionDescriptor.returnType + ); + } + asbindInstance.asyncifyState = { + ptr: asbindInstance.loadedModule.exports.__new( + asbindInstance.asyncifyStorageSize, + 0 + ) + }; + asbindInstance.loadedModule.exports.__pin(asbindInstance.asyncifyState.ptr); + const dv = new DataView(asbindInstance.loadedModule.exports.memory.buffer); + dv.setUint32( + asbindInstance.asyncifyState.ptr, + asbindInstance.asyncifyState.ptr + 8, + true ); + dv.setUint32( + asbindInstance.asyncifyState.ptr + 4, + asbindInstance.asyncifyState.ptr + asbindInstance.asyncifyStorageSize, + true + ); + asbindInstance.loadedModule.exports.asyncify_start_unwind( + asbindInstance.asyncifyState.ptr + ); + asbindInstance.asyncifyState.promise = result; }; } @@ -78,11 +114,25 @@ export function bindExportFunction( exportedFunctionDescriptor.parameters[i] ) ); - const result = exportedFunction(...newArgs); - return returnValueConverterFunction( - asbindInstance, - result, - exportedFunctionDescriptor.returnType - ); + return function f(...args) { + const result = exportedFunction(...args); + if (asbindInstance.exports.asyncify_get_state() === 0 /* Normal */) { + return returnValueConverterFunction( + asbindInstance, + result, + exportedFunctionDescriptor.returnType + ); + } + asbindInstance.loadedModule.exports.asyncify_stop_unwind(); + let localAsyncifyState = asbindInstance.asyncifyState; + return localAsyncifyState.promise.then(value => { + localAsyncifyState.value = value; + asbindInstance.asyncifyState = localAsyncifyState; + asbindInstance.loadedModule.exports.asyncify_start_rewind( + asbindInstance.asyncifyState.ptr + ); + return f(...args); + }); + }.bind(this)(...newArgs); }; } diff --git a/test/test-runner.js b/test/test-runner.js index 7456d41..095d806 100644 --- a/test/test-runner.js +++ b/test/test-runner.js @@ -1,5 +1,6 @@ const { promisify } = require("util"); const fs = require("fs/promises"); +const path = require("path"); const Express = require("express"); const Mocha = require("mocha"); @@ -32,7 +33,7 @@ async function compileAllAsc() { const transformFile = require.resolve("../dist/transform.cjs.js"); for (const ascFile of ascFiles) { console.log(`Compiling ${ascFile}...`); - await asc.main([ + const params = [ "--runtime", "stub", "--exportRuntime", @@ -41,7 +42,13 @@ async function compileAllAsc() { "--binaryFile", ascFile.replace(/\.ts$/, ".wasm"), ascFile - ]); + ]; + try { + const configFile = "./" + path.join(path.dirname(ascFile), "config.js"); + const config = require(configFile); + await config?.params?.(params); + } catch (e) {} + await asc.main(params); } } diff --git a/test/tests/string-promise/asc.ts b/test/tests/string-promise/asc.ts new file mode 100644 index 0000000..92a577a --- /dev/null +++ b/test/tests/string-promise/asc.ts @@ -0,0 +1,5 @@ +export function swapAndPad(a: string, b: string): string { + return "!" + swappedConcat(a, b) + "!"; +} + +declare function swappedConcat(a: string, b: string): string; diff --git a/test/tests/string-promise/config.js b/test/tests/string-promise/config.js new file mode 100644 index 0000000..7c95bef --- /dev/null +++ b/test/tests/string-promise/config.js @@ -0,0 +1,5 @@ +module.exports = { + params(param) { + param.push("--runPasses", "asyncify"); + } +}; diff --git a/test/tests/string-promise/test.js b/test/tests/string-promise/test.js new file mode 100644 index 0000000..4af38cc --- /dev/null +++ b/test/tests/string-promise/test.js @@ -0,0 +1,12 @@ +describe("as-bind", function() { + it("should handle strings", async function() { + const instance = await AsBind.instantiate(this.rawModule, { + asc: { + async swappedConcat(a, b) { + return b + a; + } + } + }); + assert((await instance.exports.swapAndPad("a", "b")) === "!ba!"); + }); +});