Skip to content

Commit 2612c44

Browse files
author
Guy Bedford
authored
gen-host-js: base64 option (#411)
1 parent 2047fa4 commit 2612c44

7 files changed

Lines changed: 70 additions & 34 deletions

File tree

Cargo.lock

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

crates/bindgen-core/src/component.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ pub fn generate(
6262
// Insert all core wasm modules into the generated `Files` which will
6363
// end up getting used in the `generate_instantiate` method.
6464
for (i, module) in modules.iter() {
65-
files.push(&gen.core_file_name(name, i), module.wasm);
65+
files.push(&gen.core_file_name(name, i.as_u32()), module.wasm);
6666
}
6767

6868
// With all that prep work delegate to `WorldGenerator::generate` here
@@ -98,11 +98,11 @@ pub trait ComponentGenerator: WorldGenerator {
9898
interfaces: &ComponentInterfaces,
9999
);
100100

101-
fn core_file_name(&mut self, name: &str, idx: StaticModuleIndex) -> String {
102-
let i_str = if idx.as_u32() == 0 {
101+
fn core_file_name(&mut self, name: &str, idx: u32) -> String {
102+
let i_str = if idx == 0 {
103103
String::from("")
104104
} else {
105-
(idx.as_u32() + 1).to_string()
105+
(idx + 1).to_string()
106106
};
107107
format!("{}.core{i_str}.wasm", name)
108108
}

crates/bindgen-core/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,10 @@ impl Files {
245245
}
246246
}
247247

248+
pub fn remove(&mut self, name: &str) -> Option<Vec<u8>> {
249+
return self.files.remove(name);
250+
}
251+
248252
pub fn iter(&self) -> impl Iterator<Item = (&'_ str, &'_ [u8])> {
249253
self.files.iter().map(|p| (p.0.as_str(), p.1.as_slice()))
250254
}

crates/gen-host-js/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ clap = { workspace = true, optional = true }
1616
wasmtime-environ = { workspace = true, features = ['component-model'] }
1717
wit-component = { workspace = true }
1818
indexmap = "1.0"
19+
base64 = "0.13.1"
1920

2021
[dev-dependencies]
2122
test-helpers = { path = '../test-helpers' }

crates/gen-host-js/src/lib.rs

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ struct Js {
3636
/// Type script definitions which will become the export object
3737
export_object: wit_bindgen_core::Source,
3838

39+
/// Core module count
40+
core_module_cnt: usize,
41+
3942
/// Various options for code generation.
4043
opts: Opts,
4144

@@ -48,35 +51,34 @@ struct Js {
4851
pub struct Opts {
4952
/// Disables generation of `*.d.ts` files and instead only generates `*.js`
5053
/// source files.
51-
#[cfg_attr(feature = "clap", arg(long = "no-typescript"))]
54+
#[cfg_attr(feature = "clap", arg(long))]
5255
pub no_typescript: bool,
5356
/// Provide a custom JS instantiation API for the component instead
5457
/// of the direct importable native ESM output.
5558
#[cfg_attr(
5659
feature = "clap",
5760
arg(
58-
long = "instantiation",
61+
long,
5962
short = 'I',
6063
conflicts_with = "compatibility",
6164
conflicts_with = "compat"
6265
)
6366
)]
6467
pub instantiation: bool,
68+
/// Inline core WebAssembly modules as base64 strings.
69+
#[cfg_attr(feature = "clap", arg(long, conflicts_with = "instantiation"))]
70+
pub base64: bool,
6571
/// Comma-separated list of "from-specifier=./to-specifier.js" mappings of
6672
/// component import specifiers to JS import specifiers.
67-
#[cfg_attr(feature = "clap", arg(long = "map"), clap(value_parser = maps_str_to_map))]
73+
#[cfg_attr(feature = "clap", arg(long), clap(value_parser = maps_str_to_map))]
6874
pub map: Option<HashMap<String, String>>,
6975
/// Enables all compat flags: --nodejs-compat.
70-
#[cfg_attr(feature = "clap", arg(long = "compat"))]
76+
#[cfg_attr(feature = "clap", arg(long))]
7177
pub compat: bool,
7278
/// Enables compatibility in Node.js without a fetch global.
7379
#[cfg_attr(
7480
feature = "clap",
75-
arg(
76-
long = "nodejs-compat",
77-
group = "compatibility",
78-
conflicts_with = "compat"
79-
)
81+
arg(long, group = "compatibility", conflicts_with = "compat")
8082
)]
8183
pub nodejs_compat: bool,
8284
}
@@ -183,6 +185,8 @@ impl ComponentGenerator for Js {
183185
modules: &PrimaryMap<StaticModuleIndex, ModuleTranslation<'_>>,
184186
interfaces: &ComponentInterfaces,
185187
) {
188+
self.core_module_cnt = modules.len();
189+
186190
// Generate the TypeScript definition of the `instantiate` function
187191
// which is the main workhorse of the generated bindings.
188192
if self.opts.instantiation {
@@ -257,6 +261,21 @@ impl ComponentGenerator for Js {
257261
}
258262
}
259263

264+
if self.opts.base64 && self.core_module_cnt > 0 {
265+
let mut first = true;
266+
output.push_str("const ");
267+
for i in 0..self.core_module_cnt {
268+
if first {
269+
first = false;
270+
} else {
271+
output.push_str(", ");
272+
}
273+
let data = files.remove(&self.core_file_name(name, i as u32)).unwrap();
274+
uwrite!(output, "BINARY{i} = '{}'", base64::encode(&data));
275+
}
276+
output.push_str(";");
277+
}
278+
260279
output.push_str(&self.src.js);
261280

262281
let mut bytes = output.as_bytes();
@@ -426,7 +445,17 @@ impl Js {
426445
const dataView = mem => dv.buffer === mem.buffer ? dv : dv = new DataView(mem.buffer);
427446
"),
428447

429-
Intrinsic::LoadWasm => if self.opts.nodejs_compat {
448+
Intrinsic::LoadWasm => if self.opts.base64 {
449+
if self.opts.nodejs_compat {
450+
self.src.js("
451+
const loadWasm = str => WebAssembly.compile(typeof Buffer !== 'undefined' ? Buffer.from(str, 'base64') : Uint8Array.from(atob(str), b => b.charCodeAt(0)));
452+
")
453+
} else {
454+
self.src.js("
455+
const loadWasm = str => WebAssembly.compile(Uint8Array.from(atob(str), b => b.charCodeAt(0)));
456+
")
457+
}
458+
} else if self.opts.nodejs_compat {
430459
self.src.js("
431460
const isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
432461
let _fs;
@@ -723,12 +752,19 @@ impl Instantiator<'_> {
723752
}
724753

725754
let local_name = format!("module{}", idx.as_u32());
726-
let name = self.gen.core_file_name(&self.name, *idx);
755+
let name = self.gen.core_file_name(&self.name, idx.as_u32());
727756
if self.gen.opts.instantiation {
728757
uwrite!(
729758
self.src.js,
730759
"const {local_name} = compileCore(\"{name}\");\n"
731760
);
761+
} else if self.gen.opts.base64 {
762+
let load_wasm = self.gen.intrinsic(Intrinsic::LoadWasm);
763+
let idx_num = idx.as_u32();
764+
uwrite!(
765+
self.src.js,
766+
"const {local_name} = {load_wasm}(BINARY{idx_num});\n"
767+
);
732768
} else {
733769
let load_wasm = self.gen.intrinsic(Intrinsic::LoadWasm);
734770
uwrite!(

crates/gen-host-wasmtime-py/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ impl<'a> Instantiator<'a> {
580580

581581
fn instantiate_static_module(&mut self, idx: StaticModuleIndex, args: &[CoreDef]) {
582582
let i = self.instances.push(idx);
583-
let core_file_name = self.gen.core_file_name(&self.name, idx);
583+
let core_file_name = self.gen.core_file_name(&self.name, idx.as_u32());
584584
self.gen.init.pyimport("os", None);
585585

586586
uwriteln!(

tests/runtime/smoke/host.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,21 @@
1-
// Flags: --instantiation
2-
3-
import * as helpers from "./helpers.js";
4-
import { instantiate } from "./smoke.js";
5-
1+
// Flags: --base64 --compat --map testwasi=./helpers.js,imports=./host.js
62
function assert(x: boolean, msg: string) {
73
if (!x)
84
throw new Error(msg);
95
}
106

11-
async function run() {
12-
let hit = false;
7+
let hit = false;
138

14-
const wasm = await instantiate(helpers.loadWasm, {
15-
testwasi: helpers,
16-
imports: {
17-
thunk() {
18-
hit = true;
19-
},
20-
},
21-
});
9+
export function thunk () {
10+
hit = true;
11+
}
12+
13+
async function run() {
14+
const wasm = await import('./smoke.js');
2215

2316
wasm.thunk();
2417
assert(hit, "import not called");
2518
}
2619

27-
await run()
20+
// Async cycle handling
21+
setTimeout(run);

0 commit comments

Comments
 (0)