Skip to content

Commit 0d66635

Browse files
committed
Allowed forcing the return type on a bound export
1 parent 4e6ad59 commit 0d66635

File tree

6 files changed

+97
-40
lines changed

6 files changed

+97
-40
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ asyncTask();
111111

112112
_Did the quick start not work for you, or you are noticing some weird behavior? Please see the [FAQ and Common Issues](#faq-and-common-issues)_
113113

114+
_Want to use `as-bind` in production? Please see the [Production section in the FAQ and Common Issues](#production)_
115+
114116
## Additional Examples
115117

116118
## Passing a high-level type to a an exported function, and returning a high-level type
@@ -193,6 +195,12 @@ The `AsBind` class is meant to vaugely act as the [WebAssembly](https://develope
193195

194196
Value that is the current version of your imported AsBind.
195197

198+
##### RETURN_TYPES
199+
200+
`AsBind.RETURN_TYPES`
201+
202+
Constants represented as JSON, for forcing the return type on [bound export functions](#exports).
203+
196204
##### instantiate
197205

198206
```typescript
@@ -239,6 +247,8 @@ Each **exported function** has the properties:
239247
240248
- `shouldCacheTypes`
241249
- If you would like to disable type caching (speculative execution) for a particular function, you can do: `asBindInstance.exports.myFunction.shouldCacheTypes = false;`. Or set to true, to re-enable type caching.
250+
- (Reccomended for production usage) Set this value on a bound export function, to force it's return type. This should be set to a constant found on: [`AsBind.RETURN_TYPES`](#return_types). Defaults to `null`.
251+
- `returnType`
242252
- `unsafeReturnValue`
243253
- By default, all values (in particular [TypedArrays](https://www.assemblyscript.org/stdlib/typedarray.html#typedarray)) will be copied out of Wasm Memory, instead of giving direct read/write access. If you would like to use a view of the returned memory, you can do: `asBindInstance.exports.myFunction.unsafeReturnValue = true;`. For More context, please see the [AssemblyScript loader documentation](https://www.assemblyscript.org/loader.html#module-instance-utility) on array views.
244254
- After settings this flag on a function, it will then return it's values wrapped in an object, like so: `{ptr: /* The pointer or index in wasm memory the view is reffering to */, value: /* The returned value (TypedArray) that is backed directly by Wasm Memory */}`
@@ -301,6 +311,12 @@ Eventually for the most performant option, we would want to do some JavaScript c
301311
302312
In the future, these types of high-level data passing tools will not be needed for WebAssembly toolchains, once the [WebAssembly Inteface Types proposal](https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md) lands, and this functionality is handled by the runtime / toolchain.
303313
314+
## Production
315+
316+
`as-bind` works by abstracting away using the [AssemblyScript Loader](https://www.assemblyscript.org/loader.html). For passing values into your AssemblyScript, it uses the Loader on your half to allocate memory, and then passes the pointer to the allocated memory. However, to pass a value back from AssemblyScript to JavaScript, AsBind will iterate through all the supported types until it finds a match (or doesn't in which case it just returns the number). However, returning a value _can sometimes_ conflict with something in AssemblyScript memory, as discovered in [#50](https://github.com/torch2424/as-bind/issues/50).
317+
318+
Thus, for production usage we highly reccomend that you set the [`returnType` property on your bound export functions](#exports) to ensure that this conflict does not happen. 😄
319+
304320
## Projects using as-bind
305321
306322
- The as-bind example is a Markdown Parser, in which as-bind takes in a string, passes it to a rough markdown parser / compiler written in AssemblyScript, and returns a string. [(Live Demo)](https://torch2424.github.io/as-bind/), [(Source Code)](https://github.com/torch2424/as-bind/tree/master/examples/markdown-parser)

lib/asbind-instance/bind-function.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { validateExportsAndFunction } from "./validate";
2-
import SUPPORTED_REF_TYPES from "./supported-ref-types";
2+
import { SUPPORTED_REF_TYPES } from "./supported-ref-types";
33

44
// Function that takes in an asbindInstance, and importObject, and the path to the import function on the object
55
// (E.g ["env", "myObject", "myFunction"] for {env: myObject: {myFunction: () => {}}})
@@ -195,13 +195,21 @@ export function bindExportFunction(asbindInstance, exportFunctionKey) {
195195
// Find our supported type
196196
let supportedType = undefined;
197197

198-
// Check if we cached the return type
199-
if (functionThis.shouldCacheTypes && functionThis.cachedReturnTypes[0]) {
198+
if (functionThis.returnType) {
199+
// Check if the return type was manually set
200+
if (SUPPORTED_REF_TYPES[functionThis.returnType]) {
201+
supportedType = SUPPORTED_REF_TYPES[functionThis.returnType];
202+
}
203+
} else if (
204+
functionThis.shouldCacheTypes &&
205+
functionThis.cachedReturnTypes[0]
206+
) {
207+
// Check if we cached the return type
208+
200209
if (functionThis.cachedReturnTypes[0].type === "ref") {
201-
supportedType = supportedType =
210+
supportedType =
202211
SUPPORTED_REF_TYPES[functionThis.cachedReturnTypes[0].key];
203212
}
204-
// Let it fall through the if and handle the primitive (number) logic
205213
} else {
206214
// We need to find / cache the type
207215
Object.keys(SUPPORTED_REF_TYPES).some(key => {
@@ -242,7 +250,9 @@ export function bindExportFunction(asbindInstance, exportFunctionKey) {
242250
}
243251
} else if (typeof exportFunctionResponse === "number") {
244252
response = exportFunctionResponse;
245-
if (functionThis.shouldCacheTypes) {
253+
// Need to check if we are caching types
254+
// And, if the type was forced to a number, and we fell through, don't cache it
255+
if (functionThis.shouldCacheTypes && !functionThis.returnType) {
246256
functionThis.cachedReturnTypes[0] = {
247257
type: "number"
248258
};
@@ -257,6 +267,7 @@ export function bindExportFunction(asbindInstance, exportFunctionKey) {
257267
// Initialize the state of our function
258268
boundExport.shouldCacheTypes = true;
259269
boundExport.unsafeReturnValue = false;
270+
boundExport.returnType = null;
260271
boundExport.cachedArgTypes = [];
261272
boundExport.cachedReturnTypes = [];
262273

lib/asbind-instance/supported-ref-types.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const getUnsafeResponse = (value, ptr) => {
55
};
66
};
77

8-
const SUPPORTED_REF_TYPES = {
8+
export const SUPPORTED_REF_TYPES = {
99
STRING: {
1010
isTypeFromArgument: arg => {
1111
return typeof arg === "string";
@@ -204,4 +204,16 @@ const SUPPORTED_REF_TYPES = {
204204
}
205205
};
206206

207-
export default SUPPORTED_REF_TYPES;
207+
// Our return type constant
208+
export const RETURN_TYPES = {
209+
NUMBER: "NUMBER",
210+
STRING: "STRING",
211+
INT8ARRAY: "INT8ARRAY",
212+
UINT8ARRAY: "UINT8ARRAY",
213+
INT16ARRAY: "INT16ARRAY",
214+
UINT16ARRAY: "UINT16ARRAY",
215+
INT32ARRAY: "INT32ARRAY",
216+
UINT32ARRAY: "UINT32ARRAY",
217+
FLOAT32ARRAY: "FLOAT32ARRAY",
218+
FLOAT64ARRAY: "FLOAT64ARRAY"
219+
};

lib/lib.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import packageJson from "../package.json";
22
import AsbindInstance from "./asbind-instance/asbind-instance";
3+
import { RETURN_TYPES } from "./asbind-instance/supported-ref-types";
34

45
export const AsBind = {
5-
// General asbind versionn
6+
// General asbind version
67
version: packageJson.version,
78

9+
// Constants
10+
RETURN_TYPES: RETURN_TYPES,
11+
812
// Our APIs
913
instantiate: async (source, importObject) => {
1014
let asbindInstance = new AsbindInstance();

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/test.js

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -626,36 +626,9 @@ describe("asbind", () => {
626626

627627
describe("Unsafe Return Value", () => {
628628
let asbindInstance;
629-
let testImportCalledWith = [];
630629

631630
beforeEach(async () => {
632-
const importObjectFunction = value => {
633-
testImportCalledWith = [value];
634-
};
635-
636-
wrappedBaseImportObject = {
637-
...baseImportObject,
638-
test: {
639-
testImportString: importObjectFunction,
640-
testImportTwoStrings: (value1, value2) => {
641-
testImportCalledWith = [value1, value2];
642-
},
643-
testImportReturnNumber: () => -1,
644-
testImportInt8Array: importObjectFunction,
645-
testImportUint8Array: importObjectFunction,
646-
testImportInt16Array: importObjectFunction,
647-
testImportUint16Array: importObjectFunction,
648-
testImportInt32Array: importObjectFunction,
649-
testImportUint32Array: importObjectFunction,
650-
testImportFloat32Array: importObjectFunction,
651-
testImportFloat64Array: importObjectFunction
652-
}
653-
};
654-
655-
asbindInstance = await AsBind.instantiate(
656-
wasmBytes,
657-
wrappedBaseImportObject
658-
);
631+
asbindInstance = await AsBind.instantiate(wasmBytes, baseImportObject);
659632
});
660633

661634
it("should not break strings", () => {
@@ -706,4 +679,45 @@ describe("asbind", () => {
706679
});
707680
});
708681
});
682+
683+
describe("Forcing Return Types", () => {
684+
let asbindInstance;
685+
686+
beforeEach(async () => {
687+
asbindInstance = await AsBind.instantiate(wasmBytes, baseImportObject);
688+
});
689+
690+
it("should allow setting the returnType on a bound export function", () => {
691+
// Make sure the return type is null
692+
assert.equal(asbindInstance.exports.helloWorld.returnType, null);
693+
694+
// Call the export
695+
const defaultResponse = asbindInstance.exports.helloWorld("returnType");
696+
assert.equal(typeof defaultResponse, "string");
697+
698+
// Set the return type to a number
699+
asbindInstance.exports.helloWorld.returnType = AsBind.RETURN_TYPES.NUMBER;
700+
701+
// Call the export
702+
const numberResponse = asbindInstance.exports.helloWorld("returnType");
703+
assert.equal(typeof numberResponse, "number");
704+
705+
// Set the return type to a string
706+
asbindInstance.exports.helloWorld.returnType = AsBind.RETURN_TYPES.STRING;
707+
708+
// Call the export
709+
const stringResponse = asbindInstance.exports.helloWorld("returnType");
710+
assert.equal(typeof stringResponse, "string");
711+
712+
// Remove the returnType
713+
asbindInstance.exports.helloWorld.returnType = null;
714+
715+
// Call the export
716+
const nullReturnTypeResponse = asbindInstance.exports.helloWorld(
717+
"returnType"
718+
);
719+
assert.equal(typeof nullReturnTypeResponse, "string");
720+
});
721+
});
722+
// Done!
709723
});

0 commit comments

Comments
 (0)