Skip to content

Commit

Permalink
feat: add --multi-memory flag (#362)
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford authored Jan 27, 2024
1 parent f79b997 commit 330edc4
Show file tree
Hide file tree
Showing 14 changed files with 511 additions and 232 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,13 @@ jobs:
- name: Build
run: npm run build

- name: Test
- name: Test LTS Node.js
run: npm run test:lts
if: matrix.node == '18.x' || matrix.node == '20.x'

- name: Test Latest Node.js
run: npm run test
if: matrix.node == 'latest'

- name: WASI Preview 2 Conformance
run: cargo test
Expand Down
2 changes: 2 additions & 0 deletions crates/js-component-bindgen-component/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ impl Guest for JsComponentBindgenComponent {
valid_lifting_optimization: options.valid_lifting_optimization.unwrap_or(false),
tracing: options.tracing.unwrap_or(false),
no_namespaced_exports: options.no_namespaced_exports.unwrap_or(false),
multi_memory: options.multi_memory.unwrap_or(false),
};

let js_component_bindgen::Transpiled {
Expand Down Expand Up @@ -133,6 +134,7 @@ impl Guest for JsComponentBindgenComponent {
base64_cutoff: 0,
tracing: false,
no_namespaced_exports: false,
multi_memory: false,
};

let files = generate_types(name, resolve, world, opts).map_err(|e| e.to_string())?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ world js-component-bindgen {
/// Whether to generate namespaced exports like `foo as "local:package/foo"`.
/// These exports can break typescript builds.
no-namespaced-exports: option<bool>,

/// Whether to output core Wasm utilizing multi-memory or to polyfill
/// this handling.
multi-memory: option<bool>,
}

variant wit {
Expand Down
5 changes: 4 additions & 1 deletion crates/js-component-bindgen/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ pub enum AugmentedOp {
}

impl<'a> Translation<'a> {
pub fn new(translation: ModuleTranslation<'a>) -> Result<Translation<'a>> {
pub fn new(translation: ModuleTranslation<'a>, multi_memory: bool) -> Result<Translation<'a>> {
if multi_memory {
return Ok(Translation::Normal(translation));
}
let mut features = WasmFeatures {
multi_memory: false,
..Default::default()
Expand Down
2 changes: 1 addition & 1 deletion crates/js-component-bindgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ pub fn transpile(component: &[u8], opts: TranspileOpts) -> Result<Transpiled, an

let modules: PrimaryMap<StaticModuleIndex, core::Translation<'_>> = modules
.into_iter()
.map(|(_i, module)| core::Translation::new(module))
.map(|(_i, module)| core::Translation::new(module, opts.multi_memory))
.collect::<Result<_>>()?;

let types = types.finish();
Expand Down
3 changes: 3 additions & 0 deletions crates/js-component-bindgen/src/transpile_bindgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ pub struct TranspileOpts {
/// Whether to generate namespaced exports like `foo as "local:package/foo"`.
/// These exports can break typescript builds.
pub no_namespaced_exports: bool,
/// Whether to output core Wasm utilizing multi-memory or to polyfill
/// this handling.
pub multi_memory: bool,
}

#[derive(Default, Clone, Debug)]
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@
"build:release": "cargo xtask build release",
"build:types:preview2-shim": "cargo xtask generate wasi-types",
"lint": "eslint -c eslintrc.cjs lib/**/*.js packages/*/lib/**/*.js",
"test": "mocha -u tdd test/test.js --timeout 120000",
"test:lts": "mocha -u tdd test/test.js --timeout 30000",
"test": "node --experimental-wasm-multi-memory node_modules/mocha/bin/mocha.js -u tdd test/test.js --timeout 30000",
"prepublishOnly": "cargo xtask build release && npm run test"
},
"files": [
Expand Down
2 changes: 2 additions & 0 deletions src/cmd/transpile.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ async function wasm2Js (source) {
* minify?: bool,
* optimize?: bool,
* namespacedExports?: bool,
* multiMemory?: bool,
* optArgs?: string[],
* }} opts
* @returns {Promise<{ files: { [filename: string]: Uint8Array }, imports: string[], exports: [string, 'function' | 'instance'][] }>}
Expand Down Expand Up @@ -139,6 +140,7 @@ export async function transpileComponent (component, opts = {}) {
tlaCompat: opts.tlaCompat ?? false,
base64Cutoff: opts.js ? 0 : opts.base64Cutoff ?? 5000,
noNamespacedExports: opts.namespacedExports === false,
multiMemory: opts.multiMemory === true,
});

let outDir = (opts.outDir ?? '').replace(/\\/g, '/');
Expand Down
1 change: 1 addition & 0 deletions src/jco.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ program.command('transpile')
.addOption(new Option('-I, --instantiation [mode]', 'output for custom module instantiation').choices(['async', 'sync']).preset('async'))
.option('-q, --quiet', 'disable logging')
.option('--no-namespaced-exports', 'disable namespaced exports for typescript compatibility')
.option('--multi-memory', 'optimized output for Wasm multi-memory')
.option('--', 'for --optimize, custom wasm-opt arguments (defaults to best size optimization)')
.action(asyncAction(transpile));

Expand Down
196 changes: 126 additions & 70 deletions test/api.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
import { deepStrictEqual, ok, strictEqual } from 'node:assert';
import { readFile } from 'node:fs/promises';
import { transpile, opt, print, parse, componentWit, componentNew, componentEmbed, metadataShow, preview1AdapterReactorPath } from '../src/api.js';
import { fileURLToPath } from 'node:url';
import { platform } from 'node:process';
import { deepStrictEqual, ok, strictEqual } from "node:assert";
import { readFile } from "node:fs/promises";
import {
transpile,
opt,
print,
parse,
componentWit,
componentNew,
componentEmbed,
metadataShow,
preview1AdapterReactorPath,
} from "../src/api.js";
import { fileURLToPath } from "node:url";
import { platform } from "node:process";

const isWindows = platform === "win32";

export async function apiTest (fixtures) {
suite('API', () => {
test('Transpile', async () => {
const name = 'flavorful';
const component = await readFile(`test/fixtures/components/${name}.component.wasm`);
export async function apiTest(fixtures) {
suite("API", () => {
test("Transpile", async () => {
const name = "flavorful";
const component = await readFile(
`test/fixtures/components/${name}.component.wasm`
);
const { files, imports, exports } = await transpile(component, { name });
strictEqual(imports.length, 2);
strictEqual(exports.length, 3);
deepStrictEqual(exports[0], ['test', 'instance']);
ok(files[name + '.js']);
deepStrictEqual(exports[0], ["test", "instance"]);
ok(files[name + ".js"]);
});

test('Transpile & Optimize & Minify', async () => {
const name = 'flavorful';
const component = await readFile(`test/fixtures/components/${name}.component.wasm`);
test("Transpile & Optimize & Minify", async () => {
const name = "flavorful";
const component = await readFile(
`test/fixtures/components/${name}.component.wasm`
);
const { files, imports, exports } = await transpile(component, {
name,
minify: true,
Expand All @@ -31,16 +45,18 @@ export async function apiTest (fixtures) {
});
strictEqual(imports.length, 2);
strictEqual(exports.length, 3);
deepStrictEqual(exports[0], ['test', 'instance']);
ok(files[name + '.js'].length < 11_000);
deepStrictEqual(exports[0], ["test", "instance"]);
ok(files[name + ".js"].length < 11_000);
});

test('Transpile to JS', async () => {
const name = 'flavorful';
const component = await readFile(`test/fixtures/components/${name}.component.wasm`);
test("Transpile to JS", async () => {
const name = "flavorful";
const component = await readFile(
`test/fixtures/components/${name}.component.wasm`
);
const { files, imports, exports } = await transpile(component, {
map: {
'test*': './*.js'
"test*": "./*.js",
},
name,
validLiftingOptimization: true,
Expand All @@ -50,121 +66,161 @@ export async function apiTest (fixtures) {
});
strictEqual(imports.length, 2);
strictEqual(exports.length, 3);
deepStrictEqual(exports[0], ['test', 'instance']);
deepStrictEqual(exports[1], ['test:flavorful/test', 'instance']);
deepStrictEqual(exports[2], ['testImports', 'function']);
const source = Buffer.from(files[name + '.js']).toString();
ok(source.includes('./wasi.js'));
ok(source.includes('testwasi'));
ok(source.includes('FUNCTION_TABLE'));
for (let i = 0; i < 2; i++)
ok(source.includes(exports[i][0]));
deepStrictEqual(exports[0], ["test", "instance"]);
deepStrictEqual(exports[1], ["test:flavorful/test", "instance"]);
deepStrictEqual(exports[2], ["testImports", "function"]);
const source = Buffer.from(files[name + ".js"]).toString();
ok(source.includes("./wasi.js"));
ok(source.includes("testwasi"));
ok(source.includes("FUNCTION_TABLE"));
for (let i = 0; i < 2; i++) ok(source.includes(exports[i][0]));
});

test('Transpile map into package imports', async () => {
const name = 'flavorful';
const component = await readFile(`test/fixtures/components/${name}.component.wasm`);
test("Transpile map into package imports", async () => {
const name = "flavorful";
const component = await readFile(
`test/fixtures/components/${name}.component.wasm`
);
const { files, imports } = await transpile(component, {
name,
map: {
'test:flavorful/*': '#*import'
"test:flavorful/*": "#*import",
},
});
strictEqual(imports.length, 2);
strictEqual(imports[0], '#testimport');
const source = Buffer.from(files[name + '.js']).toString();
ok(source.includes('\'#testimport\''));
strictEqual(imports[0], "#testimport");
const source = Buffer.from(files[name + ".js"]).toString();
ok(source.includes("'#testimport'"));
});

test('Optimize', async () => {
const component = await readFile(`test/fixtures/components/flavorful.component.wasm`);
test("Optimize", async () => {
const component = await readFile(
`test/fixtures/components/flavorful.component.wasm`
);
const { component: optimizedComponent } = await opt(component);
ok(optimizedComponent.byteLength < component.byteLength);
});

test('Print & Parse', async () => {
const component = await readFile(`test/fixtures/components/flavorful.component.wasm`);
test("Print & Parse", async () => {
const component = await readFile(
`test/fixtures/components/flavorful.component.wasm`
);
const output = await print(component);
strictEqual(output.slice(0, 10), '(component');
strictEqual(output.slice(0, 10), "(component");

const componentParsed = await parse(output);
ok(componentParsed);
});

test('Wit & New', async () => {
const wit = await readFile(`test/fixtures/wit/deps/flavorful/flavorful.wit`, 'utf8');
test("Wit & New", async () => {
const wit = await readFile(
`test/fixtures/wit/deps/flavorful/flavorful.wit`,
"utf8"
);

const generatedComponent = await componentEmbed({
witSource: wit,
dummy: true,
metadata: [['language', [['javascript', '']]], ['processed-by', [['dummy-gen', 'test']]]]
metadata: [
["language", [["javascript", ""]]],
["processed-by", [["dummy-gen", "test"]]],
],
});
{
const output = await print(generatedComponent);
strictEqual(output.slice(0, 7), '(module');
strictEqual(output.slice(0, 7), "(module");
}

const newComponent = await componentNew(generatedComponent);
{
const output = await print(newComponent);
strictEqual(output.slice(0, 10), '(component');
strictEqual(output.slice(0, 10), "(component");
}

const meta = await metadataShow(newComponent);
deepStrictEqual(meta[0].metaType, {
tag: 'component',
val: 4
tag: "component",
val: 4,
});
deepStrictEqual(meta[1].producers, [['processed-by', [['wit-component', '0.20.0'], ['dummy-gen', 'test']]], ['language', [['javascript', '']]]]);
deepStrictEqual(meta[1].producers, [
[
"processed-by",
[
["wit-component", "0.20.0"],
["dummy-gen", "test"],
],
],
["language", [["javascript", ""]]],
]);
});

test('Multi-file WIT', async () => {
test("Multi-file WIT", async () => {
const generatedComponent = await componentEmbed({
dummy: true,
witPath: (isWindows ? '//?/' : '') + fileURLToPath(new URL('./fixtures/componentize/source.wit', import.meta.url)),
metadata: [['language', [['javascript', '']]], ['processed-by', [['dummy-gen', 'test']]]]
witPath:
(isWindows ? "//?/" : "") +
fileURLToPath(
new URL("./fixtures/componentize/source.wit", import.meta.url)
),
metadata: [
["language", [["javascript", ""]]],
["processed-by", [["dummy-gen", "test"]]],
],
});
{
const output = await print(generatedComponent);
strictEqual(output.slice(0, 7), '(module');
strictEqual(output.slice(0, 7), "(module");
}

const newComponent = await componentNew(generatedComponent);
{
const output = await print(newComponent);
strictEqual(output.slice(0, 10), '(component');
strictEqual(output.slice(0, 10), "(component");
}

const meta = await metadataShow(newComponent);
deepStrictEqual(meta[0].metaType, {
tag: 'component',
val: 1
tag: "component",
val: 1,
});
deepStrictEqual(meta[1].producers, [['processed-by', [['wit-component', '0.20.0'], ['dummy-gen', 'test']]], ['language', [['javascript', '']]]]);
deepStrictEqual(meta[1].producers, [
[
"processed-by",
[
["wit-component", "0.20.0"],
["dummy-gen", "test"],
],
],
["language", [["javascript", ""]]],
]);
});

test('Component new adapt', async () => {
test("Component new adapt", async () => {
const component = await readFile(`test/fixtures/modules/exitcode.wasm`);

const generatedComponent = await componentNew(component, [['wasi_snapshot_preview1', await readFile(preview1AdapterReactorPath())]]);
const generatedComponent = await componentNew(component, [
[
"wasi_snapshot_preview1",
await readFile(preview1AdapterReactorPath()),
],
]);

await print(generatedComponent);
});

test('Extract metadata', async () => {
test("Extract metadata", async () => {
const component = await readFile(`test/fixtures/modules/exitcode.wasm`);

const meta = await metadataShow(component);

deepStrictEqual(meta, [{
metaType: { tag: 'module' },
producers: [],
name: undefined,
range: [
0,
262
]
}]);
deepStrictEqual(meta, [
{
metaType: { tag: "module" },
producers: [],
name: undefined,
range: [0, 262],
},
]);
});
});
}
Loading

0 comments on commit 330edc4

Please sign in to comment.