Skip to content

Commit

Permalink
Merge pull request #70 from torch2424/type-embed
Browse files Browse the repository at this point in the history
Type embeds
  • Loading branch information
torch2424 committed Apr 27, 2021
2 parents 78302f3 + b26ed1a commit 6615ff5
Show file tree
Hide file tree
Showing 51 changed files with 16,952 additions and 3,734 deletions.
133 changes: 23 additions & 110 deletions README.md

Large diffs are not rendered by default.

76 changes: 76 additions & 0 deletions examples/browser-sdk/index.html
@@ -0,0 +1,76 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>AssemblyScript SDK Example</title>
</head>
<body>
<!-- Load as-bind runtime -->
<script src="/dist/as-bind.iife.js"></script>
<!-- Load requirejs to handle the AMD version of the sdk -->
<script src="/node_modules/requirejs/require.js"></script>
<script>
const SOURCE_CODE = `
export function test(): string {
return "ohai";
}`;

require(["/node_modules/assemblyscript/dist/sdk.js"], ({
asc,
assemblyscript
}) => {
// Register the `assemblyscript` property as a module of its own
// as is expected by most transform modules.
define("assemblyscript", [], assemblyscript);
// `visitor-as/as` is usually a facade for `assemblyscript` to avoid
// multiple instances of `assemblyscript` being loaded.
// So in this case we can take matters into our own hands and just replace
// it with out `assemblyscript` instance.
define("visitor-as/as", [], assemblyscript);
// Load our ASBind transform...
require(["/dist/transform.amd.js"], asbind => {
asc.ready.then(() => {
const stdout = asc.createMemoryStream();
const stderr = asc.createMemoryStream();
asc.main(
// prettier-ignore
[
"module.ts",
"-O3",
"--exportRuntime",
"--runtime", "stub",
"--binaryFile", "module.wasm"
],
{
stdout,
stderr,
transforms: [asbind],
readFile(name, baseDir) {
return name === "module.ts" ? SOURCE_CODE : null;
},
async writeFile(name, data, baseDir) {
if (name.endsWith(".wasm")) {
const instance = await AsBindIIFE.instantiate(data);
console.log("Output:", instance.exports.test());
}
},
listFiles(dirname, baseDir) {
return [];
}
},
err => {
console.log(`>>> STDOUT >>>\n${stdout.toString()}`);
console.log(`>>> STDERR >>>\n${stderr.toString()}`);
if (err) {
console.log(">>> THROWN >>>");
console.log(err);
}
}
);
});
});
});
</script>
<p>See the browser console!</p>
</body>
</html>
2 changes: 1 addition & 1 deletion examples/markdown-parser/index.js
@@ -1,5 +1,5 @@
import { h, render, Component } from "preact";
import asbind from "../../dist/as-bind.esm";
import * as asbind from "../../dist/as-bind.esm";

// Import our TypeScript equivalent
import { convertMarkdownToHTML } from "../../dist/ts/index";
Expand Down
232 changes: 101 additions & 131 deletions lib/asbind-instance/asbind-instance.js
Expand Up @@ -4,29 +4,41 @@ import { asbindInstantiate, asbindInstantiateSync } from "./instantiate";
import { bindImportFunction, bindExportFunction } from "./bind-function";
import { isReservedExportKey } from "./reserved-export-keys";

// Need to traverse the importObject and bind all import functions
const traverseObjectAndRunCallbackForFunctions = (
baseObject,
keys,
callback
) => {
if (!baseObject) {
return;
const SECTION_NAME = "as-bind_bindings";

// Basically a deep-copy, but can be limited in levels.
function copyObject(obj, { depth = Number.POSITIVE_INFINITY } = {}) {
if (depth <= 0 || !obj || typeof obj !== "object") {
return obj;
}
return Object.fromEntries(
Object.entries(obj).map(([key, val]) => [
key,
copyObject(val, { depth: depth - 1 })
])
);
}

Object.keys(baseObject).forEach(baseObjectKey => {
if (typeof baseObject[baseObjectKey] === "function") {
// Call the callback
callback(baseObject, keys, baseObjectKey);
} else if (typeof baseObject[baseObjectKey] === "object") {
traverseObjectAndRunCallbackForFunctions(
baseObject[baseObjectKey],
[...keys, baseObjectKey],
callback
);
async function compileStreaming(source) {
source = await Promise.resolve(source);
if (typeof Response !== "undefined" && source instanceof Response) {
if (WebAssembly.compileStreaming) {
return WebAssembly.compileStreaming(source);
}
});
};
source = await source.arrayBuffer();
}
return WebAssembly.compile(source);
}

function extractTypeDescriptor(module) {
const sections = WebAssembly.Module.customSections(module, SECTION_NAME);
const str = new TextDecoder("utf8").decode(new Uint8Array(sections[0]));
try {
return JSON.parse(str);
} catch (e) {
throw Error(`Couldn’t decode type descriptor: ${e.message}`);
}
}

export default class AsbindInstance {
constructor() {
Expand All @@ -35,136 +47,94 @@ export default class AsbindInstance {
this.importObject = {};
}

async _instantiate(source, importObject) {
// Bind our import function
this._instantiateBindImportFunctions(importObject);

// Instantiate the module through the loader
const unboundExports = await asbindInstantiate(source, this.importObject);

// Bind our unbound exports
this._instantiateBindUnboundExports(unboundExports);
}

_instantiateSync(source, importObject) {
// Bind our import function
this._instantiateBindImportFunctions(importObject);

// Instantiate the module through the loader
const unboundExports = asbindInstantiateSync(source, this.importObject);

// Bind our unbound exports
this._instantiateBindUnboundExports(unboundExports);
getTypeId(typeName) {
if (typeName in this.typeDescriptor.typeIds) {
return this.typeDescriptor.typeIds[typeName].id;
}
throw Error(`Unknown type ${JSON.stringify(typeName)}`);
}

_instantiateBindImportFunctions(importObject) {
// Set our import object, as we will need it to store type caching
this.importObject = importObject;

// Need to traverse the importObject and bind all import functions
traverseObjectAndRunCallbackForFunctions(
this.importObject,
[],
(baseObject, keys, baseObjectKey) => {
// Wrap the original key, but then expose a new key for the unbound import
let importFunction = baseObject[baseObjectKey];
baseObject[`__asbind_unbound_${baseObjectKey}`] = importFunction;
baseObject[baseObjectKey] = bindImportFunction(
this,
this.importObject,
[...keys, baseObjectKey]
);
}
);
getTypeSize(typeName) {
if (typeName in this.typeDescriptor.typeIds) {
return this.typeDescriptor.typeIds[typeName].byteSize;
}
throw Error(`Unknown type ${JSON.stringify(typeName)}`);
}

_instantiateBindUnboundExports(unboundExports) {
// Set our unbound exports
this.unboundExports = unboundExports;

// Ensure that the AS Wasm was built with the as-bind entryfile
_validate() {
if (
!WebAssembly.Module.exports(this.module).find(exp => exp.name === "__new")
) {
throw Error(
"The AssemblyScript wasm module was not built with --exportRuntime, which is required."
);
}
if (
!this.unboundExports.__asbind_entryfile_flag ||
this.unboundExports.__asbind_entryfile_flag === 0
WebAssembly.Module.customSections(this.module, SECTION_NAME).length !== 1
) {
throw new Error(
"as-bind: The instantiated AssemblyScript wasm module was not built with the as-bind entryfile. Please see the as-bind documentation (Quick Start), and rebuild your AssemblyScript wasm module."
"The AssemblyScript wasm module was not built with the as-bind transform."
);
}

// Wrap appropriate the appropriate export functions
this.exports = {};
Object.keys(this.unboundExports).forEach(unboundExportKey => {
if (
typeof this.unboundExports[unboundExportKey] === "function" &&
!isReservedExportKey(unboundExportKey)
) {
// Wrap the export
this.exports[unboundExportKey] = bindExportFunction(
this,
unboundExportKey
);
} else {
this.exports[unboundExportKey] = this.unboundExports[unboundExportKey];
}
});
}

enableExportFunctionTypeCaching() {
Object.keys(this.exports).forEach(exportKey => {
if (this.exports[exportKey]) {
this.exports[exportKey].shouldCacheTypes = true;
}
});
}
async _instantiate(source, importObject) {
this.module = await compileStreaming(source);

disableExportFunctionTypeCaching() {
Object.keys(this.exports).forEach(exportKey => {
if (this.exports[exportKey]) {
this.exports[exportKey].shouldCacheTypes = false;
}
});
this._validate();
this.typeDescriptor = extractTypeDescriptor(this.module);
this._instantiateBindImportFunctions(importObject);
// Instantiate the module through the loader
this.loadedModule = await asbindInstantiate(this.module, this.importObject);
this._instantiateBindUnboundExports();
}

enableExportFunctionUnsafeReturnValue() {
Object.keys(this.exports).forEach(exportKey => {
if (this.exports[exportKey]) {
this.exports[exportKey].unsafeReturnValue = true;
}
});
}
_instantiateSync(source, importObject) {
this.module = new WebAssembly.Module(source);

disableExportFunctionUnsafeReturnValue() {
Object.keys(this.exports).forEach(exportKey => {
if (this.exports[exportKey]) {
this.exports[exportKey].unsafeReturnValue = false;
}
});
this._validate();
this.typeDescriptor = extractTypeDescriptor(this.module);
this._instantiateBindImportFunctions(importObject);
this.loadedModule = asbindInstantiateSync(this.module, this.importObject);
this._instantiateBindUnboundExports();
}

enableImportFunctionTypeCaching() {
// Need to traverse the importObject and bind all import functions
traverseObjectAndRunCallbackForFunctions(
this.importObject,
[],
(baseObject, keys, baseObjectKey) => {
// Wrap the original key, but then expose a new key for the unbound import
let importFunction = baseObject[baseObjectKey];
importFunction.shouldCacheTypes = true;
_instantiateBindImportFunctions(importObject) {
this.importObject = copyObject(importObject, { depth: 2 });

for (const [moduleName, moduleDescriptor] of Object.entries(
this.typeDescriptor.importedFunctions
)) {
for (const [importedFunctionName, descriptor] of Object.entries(
moduleDescriptor
)) {
this.importObject[moduleName][
`__asbind_unbound_${importedFunctionName}`
] = importObject[moduleName][importedFunctionName];
this.importObject[moduleName][
importedFunctionName
] = bindImportFunction(
this,
importObject[moduleName][importedFunctionName],
descriptor
);
}
);
}
}

disableImportFunctionTypeCaching() {
// Need to traverse the importObject and bind all import functions
traverseObjectAndRunCallbackForFunctions(
this.importObject,
[],
(baseObject, keys, baseObjectKey) => {
// Wrap the original key, but then expose a new key for the unbound import
let importFunction = baseObject[baseObjectKey];
importFunction.shouldCacheTypes = false;
}
);
_instantiateBindUnboundExports() {
// Wrap appropriate the appropriate export functions
const unboundExports = this.loadedModule.exports;
this.exports = copyObject(unboundExports, { depth: 1 });

for (const [exportedFunctionName, descriptor] of Object.entries(
this.typeDescriptor.exportedFunctions
)) {
this.exports[exportedFunctionName] = bindExportFunction(
this,
unboundExports[exportedFunctionName],
descriptor
);
}
}
}

0 comments on commit 6615ff5

Please sign in to comment.