Skip to content

Instantly share code, notes, and snippets.

@libetl
Created Oct 29, 2021
Embed
What would you like to do?
webassembly-config
/**
* Reads a string from the memory table.
* When a web assembly returns a string, javascript only sees the pointer
* to the memory table, and has to read the value from the table.
*
* @param instance web assembly program instance (memory + code)
* @param pointer where does the string start in the memory table
* @returns a slice of the memory table starting from pointer and ending before \0
*/
export const getString = (instance: WebAssembly.Instance, pointer: number): string => {
const memory = instance.exports.memory as WebAssembly.Memory;
const memoryBuffer = memory.buffer as ArrayBuffer;
const U16 = new Uint16Array(memoryBuffer);
const beginIndex16 = Math.ceil(pointer / 2);
const lastIndex16 = U16.indexOf(0, beginIndex16);
const value16 = U16.slice(beginIndex16, lastIndex16);
return Buffer.from(value16).toString();
};
export function newString(instance: WebAssembly.Instance, javascriptString: string): string {
if (javascriptString === null) return 0 as any as string;
const { length } = javascriptString;
const pointer = (instance.exports as any).__new(length * 2, 1);
const U16 = new Uint16Array((instance.exports.memory as WebAssembly.Memory).buffer);
const base = Math.floor(pointer / 2);
javascriptString.split('').forEach((_, index) => {
U16[base + index] = javascriptString.charCodeAt(index);
});
return pointer;
}
import { instantiate } from './webassembly-config';
export type FooFunction = (param: string) => string;
export async function init(wasmData: Buffer): Promise<{
foo: FooFunction;
}> {
const { getInstance, wrapAssemblyScript } = await instantiate(wasmData);
const { exports: libraryExports } = getInstance();
const foo = wrapAssemblyScript<FooFunction>(libraryExports.foo);
return { foo };
}
import { getString, newString } from './get-string';
type InstanceContext = {
getInstance: () => WebAssembly.Instance;
wrapAssemblyScript: <T>(pureFunction: WebAssembly.ExportValue) => T;
};
export const instantiate = async (wasmData: Buffer): Promise<InstanceContext> => {
const webAssemblyModule = await WebAssembly.compile(wasmData);
let instance: WebAssembly.Instance;
const getInstance = (): WebAssembly.Instance => instance;
instance = await WebAssembly.instantiate(webAssemblyModule, getImports(getInstance));
return { getInstance, wrapAssemblyScript: wrapAssemblyScript.bind(null, instance) };
};
export const getImports = (getInstance: () => WebAssembly.Instance): WebAssembly.Imports => {
const newMemory = new WebAssembly.Memory({ initial: 0 });
return {
console: {
'console.log': (stringPointer: number) => {
if (stringPointer === 0) console.log('null');
else console.log(getString(getInstance(), stringPointer));
},
},
env: {
memory: newMemory,
table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' }),
abort: (message: string, file: string, line: number, column: number) => {
console.error(`${file}:${line}:${column} ${message}`);
},
},
exports: {
memory: newMemory,
},
};
};
export const wrapAssemblyScript = <T>(
theInstance: WebAssembly.Instance,
pureFunction: WebAssembly.ExportValue
): T =>
((...args: any) => {
const result = (pureFunction as Function)(
...args.map((arg: any) =>
typeof arg === 'string' ? newString(theInstance, arg as string) : arg
)
);
if (typeof result === 'number' && result < 2) return result;
const resultAsString = getString(theInstance, result);
try {
return JSON.parse(resultAsString);
} catch (e) {
return resultAsString;
}
}) as unknown as T;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment