1use std::fs;
77
78pub(crate) mod bytecode;
79pub(crate) mod exports;
80pub(crate) mod transform;
81
82pub(crate) mod js;
83pub(crate) mod plugin;
84pub(crate) mod wit;
85
86use crate::exports::Exports;
87pub use crate::js::JS;
88pub use crate::plugin::Plugin;
89pub use crate::wit::WitOptions;
90
91use transform::SourceCodeSection;
92use walrus::{
93 DataId, DataKind, ExportItem, FunctionBuilder, FunctionId, LocalId, MemoryId, Module, ValType,
94};
95use wasm_opt::{OptimizationOptions, ShrinkLevel};
96use wasmtime::{Engine, Linker, Store};
97use wasmtime_wasi::{WasiCtxBuilder, p2::pipe::MemoryInputPipe};
98
99use anyhow::Result;
100use wasmtime_wizer::Wizer;
101
102#[derive(Debug, Clone, Default)]
104pub enum LinkingKind {
105 #[default]
106 Static,
108 Dynamic,
110}
111
112#[derive(Debug, Clone, Default)]
114pub enum SourceEmbedding {
115 #[default]
116 Uncompressed,
118 Compressed,
120 Omitted,
122}
123
124#[derive(Debug)]
127pub(crate) struct Identifiers {
128 cabi_realloc: FunctionId,
129 invoke: FunctionId,
130 memory: MemoryId,
131}
132
133impl Identifiers {
134 fn new(cabi_realloc: FunctionId, invoke: FunctionId, memory: MemoryId) -> Self {
135 Self {
136 cabi_realloc,
137 invoke,
138 memory,
139 }
140 }
141}
142
143#[derive(Debug)]
146pub(crate) struct BytecodeMetadata {
147 ptr: LocalId,
148 len: i32,
149 data_section: DataId,
150}
151
152impl BytecodeMetadata {
153 fn new(ptr: LocalId, len: i32, data_section: DataId) -> Self {
154 Self {
155 ptr,
156 len,
157 data_section,
158 }
159 }
160}
161
162#[derive(Debug, Default, Clone)]
164pub struct Generator {
165 pub(crate) plugin: Plugin,
167 pub(crate) linking: LinkingKind,
169 pub(crate) source_embedding: SourceEmbedding,
171 pub(crate) wit_opts: WitOptions,
173 pub(crate) function_exports: Exports,
175 js_runtime_config: Vec<u8>,
177 producer_version: Option<String>,
179 deterministic: bool,
181}
182
183impl Generator {
184 pub fn new(plugin: Plugin) -> Self {
186 Self {
187 plugin,
188 ..Self::default()
189 }
190 }
191
192 pub fn linking(&mut self, linking: LinkingKind) -> &mut Self {
194 self.linking = linking;
195 self
196 }
197
198 pub fn source_embedding(&mut self, source_embedding: SourceEmbedding) -> &mut Self {
200 self.source_embedding = source_embedding;
201 self
202 }
203
204 pub fn wit_opts(&mut self, wit_opts: wit::WitOptions) -> &mut Self {
206 self.wit_opts = wit_opts;
207 self
208 }
209
210 #[cfg(feature = "plugin_internal")]
211 pub fn js_runtime_config(&mut self, js_runtime_config: Vec<u8>) -> &mut Self {
213 self.js_runtime_config = js_runtime_config;
214 self
215 }
216
217 pub fn producer_version(&mut self, producer_version: String) -> &mut Self {
219 self.producer_version = Some(producer_version);
220 self
221 }
222
223 pub fn deterministic(&mut self, deterministic: bool) -> &mut Self {
226 self.deterministic = deterministic;
227 self
228 }
229}
230
231impl Generator {
232 async fn generate_initial_module(&self) -> Result<Module> {
234 let config = transform::module_config();
235 let module = match &self.linking {
236 LinkingKind::Static => {
237 let engine = Engine::default();
238 let mut builder = WasiCtxBuilder::new();
239 builder
240 .stdin(MemoryInputPipe::new(self.js_runtime_config.clone()))
241 .inherit_stdout()
242 .inherit_stderr();
243 if self.deterministic {
244 deterministic_wasi_ctx::add_determinism_to_wasi_ctx_builder(&mut builder);
245 }
246 let wasi = builder.build_p1();
247 let mut store = Store::new(&engine, wasi);
248 let wasm = Wizer::new()
249 .init_func("initialize-runtime")
250 .run(&mut store, self.plugin.as_bytes(), async |store, module| {
251 let engine = store.engine();
252 let mut linker = Linker::new(engine);
253 wasmtime_wasi::p1::add_to_linker_async(&mut linker, |cx| cx)?;
254 linker.define_unknown_imports_as_traps(module)?;
255 let instance = linker.instantiate_async(store, module).await?;
256 Ok(instance)
257 })
258 .await?;
259 config.parse(&wasm)?
260 }
261 LinkingKind::Dynamic => Module::with_config(config),
262 };
263 Ok(module)
264 }
265
266 pub(crate) fn resolve_identifiers(&self, module: &mut Module) -> Result<Identifiers> {
268 match self.linking {
269 LinkingKind::Static => {
270 let cabi_realloc = module.exports.get_func("cabi_realloc")?;
271 let invoke = module.exports.get_func("invoke")?;
272 let ExportItem::Memory(memory) = module
273 .exports
274 .iter()
275 .find(|e| e.name == "memory")
276 .ok_or_else(|| anyhow::anyhow!("Missing memory export"))?
277 .item
278 else {
279 anyhow::bail!("Export with name memory must be of type memory")
280 };
281 Ok(Identifiers::new(cabi_realloc, invoke, memory))
282 }
283 LinkingKind::Dynamic => {
284 let import_namespace = self.plugin.import_namespace()?;
287
288 let cabi_realloc_type = module.types.add(
289 &[ValType::I32, ValType::I32, ValType::I32, ValType::I32],
290 &[ValType::I32],
291 );
292 let (cabi_realloc_fn_id, _) =
293 module.add_import_func(&import_namespace, "cabi_realloc", cabi_realloc_type);
294
295 let invoke_params = [
296 ValType::I32,
297 ValType::I32,
298 ValType::I32,
299 ValType::I32,
300 ValType::I32,
301 ]
302 .as_slice();
303 let invoke_type = module.types.add(invoke_params, &[]);
304 let (invoke_fn_id, _) =
305 module.add_import_func(&import_namespace, "invoke", invoke_type);
306
307 let (memory_id, _) = module.add_import_memory(
308 &import_namespace,
309 "memory",
310 false,
311 false,
312 0,
313 None,
314 None,
315 );
316
317 Ok(Identifiers::new(
318 cabi_realloc_fn_id,
319 invoke_fn_id,
320 memory_id,
321 ))
322 }
323 }
324 }
325
326 fn generate_main(
328 &self,
329 module: &mut Module,
330 js: &js::JS,
331 imports: &Identifiers,
332 ) -> Result<BytecodeMetadata> {
333 let bytecode = bytecode::compile_source(&self.plugin, js.as_bytes())?;
334 let bytecode_len: i32 = bytecode.len().try_into()?;
335 let bytecode_data = module.data.add(DataKind::Passive, bytecode);
336
337 let mut main = FunctionBuilder::new(&mut module.types, &[], &[]);
338 let bytecode_ptr_local = module.locals.add(ValType::I32);
339 let mut instructions = main.func_body();
340 instructions
341 .i32_const(0) .i32_const(0) .i32_const(1) .i32_const(bytecode_len) .call(imports.cabi_realloc)
347 .local_tee(bytecode_ptr_local) .i32_const(0) .i32_const(bytecode_len) .memory_init(imports.memory, bytecode_data);
353 instructions
355 .local_get(bytecode_ptr_local) .i32_const(bytecode_len)
357 .i32_const(0) .i32_const(0) .i32_const(0) .call(imports.invoke);
361 let main = main.finish(vec![], &mut module.funcs);
362
363 module.exports.add("_start", main);
364 Ok(BytecodeMetadata::new(
365 bytecode_ptr_local,
366 bytecode_len,
367 bytecode_data,
368 ))
369 }
370
371 fn generate_exports(
373 &self,
374 module: &mut Module,
375 identifiers: &Identifiers,
376 bc_metadata: &BytecodeMetadata,
377 ) -> Result<()> {
378 if !self.function_exports.is_empty() {
379 let fn_name_ptr_local = module.locals.add(ValType::I32);
380 for export in &self.function_exports {
381 let js_export_bytes = export.js.as_bytes();
383 let js_export_len: i32 = js_export_bytes.len().try_into().unwrap();
384 let fn_name_data = module.data.add(DataKind::Passive, js_export_bytes.to_vec());
385
386 let mut export_fn = FunctionBuilder::new(&mut module.types, &[], &[]);
387 export_fn
388 .func_body()
389 .i32_const(0) .i32_const(0) .i32_const(1) .i32_const(bc_metadata.len) .call(identifiers.cabi_realloc)
395 .local_tee(bc_metadata.ptr)
396 .i32_const(0) .i32_const(bc_metadata.len) .memory_init(identifiers.memory, bc_metadata.data_section) .data_drop(bc_metadata.data_section)
400 .i32_const(0) .i32_const(0) .i32_const(1) .i32_const(js_export_len) .call(identifiers.cabi_realloc)
406 .local_tee(fn_name_ptr_local)
407 .i32_const(0) .i32_const(js_export_len) .memory_init(identifiers.memory, fn_name_data) .data_drop(fn_name_data)
411 .local_get(bc_metadata.ptr)
413 .i32_const(bc_metadata.len)
414 .i32_const(1) .local_get(fn_name_ptr_local)
416 .i32_const(js_export_len)
417 .call(identifiers.invoke);
418 let export_fn = export_fn.finish(vec![], &mut module.funcs);
419 module.exports.add(&export.wit, export_fn);
420 }
421 }
422 Ok(())
423 }
424
425 fn postprocess(&self, module: &mut Module) -> Result<Vec<u8>> {
427 match self.linking {
428 LinkingKind::Static => {
429 module.exports.remove("invoke")?;
431 module.exports.remove("compile-src")?;
432
433 let tempdir = tempfile::tempdir()?;
435 let tempfile_path = tempdir.path().join("temp.wasm");
436
437 module.emit_wasm_file(&tempfile_path)?;
438
439 OptimizationOptions::new_opt_level_3() .shrink_level(ShrinkLevel::Level0) .debug_info(false)
442 .run(&tempfile_path, &tempfile_path)?;
443
444 Ok(fs::read(&tempfile_path)?)
445 }
446 LinkingKind::Dynamic => Ok(module.emit_wasm()),
447 }
448 }
449
450 pub async fn generate(&mut self, js: &js::JS) -> Result<Vec<u8>> {
452 if self.wit_opts.defined() {
453 self.function_exports = exports::process_exports(
454 js,
455 self.wit_opts.unwrap_path(),
456 self.wit_opts.unwrap_world(),
457 )?;
458 }
459
460 let mut module = self.generate_initial_module().await?;
461 let identifiers = self.resolve_identifiers(&mut module)?;
462 let bc_metadata = self.generate_main(&mut module, js, &identifiers)?;
463 self.generate_exports(&mut module, &identifiers, &bc_metadata)?;
464
465 transform::add_producers_section(
466 &mut module.producers,
467 self.producer_version
468 .as_deref()
469 .unwrap_or(env!("CARGO_PKG_VERSION")),
470 );
471 match self.source_embedding {
472 SourceEmbedding::Omitted => {}
473 SourceEmbedding::Uncompressed => {
474 module.customs.add(SourceCodeSection::uncompressed(js)?);
475 }
476 SourceEmbedding::Compressed => {
477 module.customs.add(SourceCodeSection::compressed(js)?);
478 }
479 }
480
481 let wasm = self.postprocess(&mut module)?;
482 Ok(wasm)
483 }
484}