Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/torch2424/as-bind into up…
Browse files Browse the repository at this point in the history
…date-as-loader
  • Loading branch information
torch2424 committed Nov 11, 2020
2 parents 4442d68 + 7f82fdb commit 1752cbb
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 37 deletions.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ asyncTask();

_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)_

_Want to use `as-bind` in production? Please see the [Production section in the FAQ and Common Issues](#production)_

## Additional Examples

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

Value that is the current version of your imported AsBind.

##### RETURN_TYPES

`AsBind.RETURN_TYPES`

Constants (represented as JSON) of the supported return types on bound export functions. This is useful for forcing the return type on [bound export functions](#exports).

For example, this could be used like:

```typescript
// Force our return type to our expected string
asBindInstance.exports.myExportedFunctionThatReturnsAString.returnType =
AsBind.RETURN_TYPES.STRING;
const myString = asBindInstance.exports.myExportedFunctionThatReturnsAString();

// Force our return type to return a number (The pointer to the string)
asBindInstance.exports.myExportedFunctionThatReturnsAString.returnType =
AsBind.RETURN_TYPES.NUMBER;
const myNumber = asBindInstance.exports.myExportedFunctionThatReturnsAString();
```

##### instantiate

```typescript
Expand Down Expand Up @@ -239,6 +261,8 @@ Each **exported function** has the properties:
- `shouldCacheTypes`
- 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.
- `returnType`
- (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`.
- `unsafeReturnValue`
- 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.
- 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 */}`
Expand Down Expand Up @@ -301,6 +325,12 @@ Eventually for the most performant option, we would want to do some JavaScript c
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.
## Production
`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).
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. 😄
## Projects using as-bind
- 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)
Expand Down
23 changes: 17 additions & 6 deletions lib/asbind-instance/bind-function.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { validateExportsAndFunction } from "./validate";
import SUPPORTED_REF_TYPES from "./supported-ref-types";
import { SUPPORTED_REF_TYPES } from "./supported-ref-types";

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

// Check if we cached the return type
if (functionThis.shouldCacheTypes && functionThis.cachedReturnTypes[0]) {
if (functionThis.returnType) {
// Check if the return type was manually set
if (SUPPORTED_REF_TYPES[functionThis.returnType]) {
supportedType = SUPPORTED_REF_TYPES[functionThis.returnType];
}
} else if (
functionThis.shouldCacheTypes &&
functionThis.cachedReturnTypes[0]
) {
// Check if we cached the return type

if (functionThis.cachedReturnTypes[0].type === "ref") {
supportedType = supportedType =
supportedType =
SUPPORTED_REF_TYPES[functionThis.cachedReturnTypes[0].key];
}
// Let it fall through the if and handle the primitive (number) logic
} else {
// We need to find / cache the type
Object.keys(SUPPORTED_REF_TYPES).some(key => {
Expand Down Expand Up @@ -242,7 +250,9 @@ export function bindExportFunction(asbindInstance, exportFunctionKey) {
}
} else if (typeof exportFunctionResponse === "number") {
response = exportFunctionResponse;
if (functionThis.shouldCacheTypes) {
// Need to check if we are caching types
// And, if the type was forced to a number, and we fell through, don't cache it
if (functionThis.shouldCacheTypes && !functionThis.returnType) {
functionThis.cachedReturnTypes[0] = {
type: "number"
};
Expand All @@ -257,6 +267,7 @@ export function bindExportFunction(asbindInstance, exportFunctionKey) {
// Initialize the state of our function
boundExport.shouldCacheTypes = true;
boundExport.unsafeReturnValue = false;
boundExport.returnType = null;
boundExport.cachedArgTypes = [];
boundExport.cachedReturnTypes = [];

Expand Down
16 changes: 14 additions & 2 deletions lib/asbind-instance/supported-ref-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const getUnsafeResponse = (value, ptr) => {
};
};

const SUPPORTED_REF_TYPES = {
export const SUPPORTED_REF_TYPES = {
STRING: {
isTypeFromArgument: arg => {
return typeof arg === "string";
Expand Down Expand Up @@ -204,4 +204,16 @@ const SUPPORTED_REF_TYPES = {
}
};

export default SUPPORTED_REF_TYPES;
// Our return type constant
export const RETURN_TYPES = {
NUMBER: "NUMBER",
STRING: "STRING",
INT8ARRAY: "INT8ARRAY",
UINT8ARRAY: "UINT8ARRAY",
INT16ARRAY: "INT16ARRAY",
UINT16ARRAY: "UINT16ARRAY",
INT32ARRAY: "INT32ARRAY",
UINT32ARRAY: "UINT32ARRAY",
FLOAT32ARRAY: "FLOAT32ARRAY",
FLOAT64ARRAY: "FLOAT64ARRAY"
};
6 changes: 5 additions & 1 deletion lib/lib.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import packageJson from "../package.json";
import AsbindInstance from "./asbind-instance/asbind-instance";
import { RETURN_TYPES } from "./asbind-instance/supported-ref-types";

export const AsBind = {
// General asbind versionn
// General asbind version
version: packageJson.version,

// Constants
RETURN_TYPES: RETURN_TYPES,

// Our APIs
instantiate: async (source, importObject) => {
let asbindInstance = new AsbindInstance();
Expand Down
70 changes: 42 additions & 28 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -626,36 +626,9 @@ describe("asbind", () => {

describe("Unsafe Return Value", () => {
let asbindInstance;
let testImportCalledWith = [];

beforeEach(async () => {
const importObjectFunction = value => {
testImportCalledWith = [value];
};

wrappedBaseImportObject = {
...baseImportObject,
test: {
testImportString: importObjectFunction,
testImportTwoStrings: (value1, value2) => {
testImportCalledWith = [value1, value2];
},
testImportReturnNumber: () => -1,
testImportInt8Array: importObjectFunction,
testImportUint8Array: importObjectFunction,
testImportInt16Array: importObjectFunction,
testImportUint16Array: importObjectFunction,
testImportInt32Array: importObjectFunction,
testImportUint32Array: importObjectFunction,
testImportFloat32Array: importObjectFunction,
testImportFloat64Array: importObjectFunction
}
};

asbindInstance = await AsBind.instantiate(
wasmBytes,
wrappedBaseImportObject
);
asbindInstance = await AsBind.instantiate(wasmBytes, baseImportObject);
});

it("should not break strings", () => {
Expand Down Expand Up @@ -706,4 +679,45 @@ describe("asbind", () => {
});
});
});

describe("Forcing Return Types", () => {
let asbindInstance;

beforeEach(async () => {
asbindInstance = await AsBind.instantiate(wasmBytes, baseImportObject);
});

it("should allow setting the returnType on a bound export function", () => {
// Make sure the return type is null
assert.equal(asbindInstance.exports.helloWorld.returnType, null);

// Call the export
const defaultResponse = asbindInstance.exports.helloWorld("returnType");
assert.equal(typeof defaultResponse, "string");

// Set the return type to a number
asbindInstance.exports.helloWorld.returnType = AsBind.RETURN_TYPES.NUMBER;

// Call the export
const numberResponse = asbindInstance.exports.helloWorld("returnType");
assert.equal(typeof numberResponse, "number");

// Set the return type to a string
asbindInstance.exports.helloWorld.returnType = AsBind.RETURN_TYPES.STRING;

// Call the export
const stringResponse = asbindInstance.exports.helloWorld("returnType");
assert.equal(typeof stringResponse, "string");

// Remove the returnType
asbindInstance.exports.helloWorld.returnType = null;

// Call the export
const nullReturnTypeResponse = asbindInstance.exports.helloWorld(
"returnType"
);
assert.equal(typeof nullReturnTypeResponse, "string");
});
});
// Done!
});

0 comments on commit 1752cbb

Please sign in to comment.