1pub mod codegen;
2pub mod stubwasi;
3
4use std::path::Path;
5
6use anyhow::{Context, Result, anyhow};
7use bytes::Bytes;
8use stubwasi::stub_wasi_imports;
9use wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_REACTOR_ADAPTER;
10use wasmtime::component::{Component, Linker, ResourceTable};
11use wasmtime::{Config, Engine, Store};
12use wasmtime_wasi::p2::pipe::{MemoryInputPipe, MemoryOutputPipe};
13use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView};
14use wasmtime_wizer::{WasmtimeWizerComponent, Wizer};
15use wit_parser::Resolve;
16
17include!(concat!(env!("OUT_DIR"), "/output.rs"));
18
19wasmtime::component::bindgen!({
20 path: "wit/init.wit",
21 world: "init",
22 exports: { default: async },
23});
24
25struct Ctx {
26 wasi: WasiCtx,
27 table: ResourceTable,
28}
29
30impl WasiView for Ctx {
31 fn ctx(&mut self) -> WasiCtxView<'_> {
32 WasiCtxView {
33 ctx: &mut self.wasi,
34 table: &mut self.table,
35 }
36 }
37}
38
39pub struct ComponentizeOpts<'a> {
41 pub wit_path: &'a Path,
43 pub js_source: &'a str,
45 pub world_name: Option<&'a str>,
47 pub stub_wasi: bool,
49 pub disable_gc: bool,
51 pub runtime: Runtime<'a>,
53}
54
55#[derive(Clone, Copy, Debug)]
57pub enum Runtime<'a> {
58 Default,
63 OptSize,
68 DefaultSync,
74 OptSizeSync,
80 Custom(&'a [u8]),
82}
83
84impl Default for Runtime<'_> {
85 fn default() -> Self {
86 default_builtin_runtime()
87 }
88}
89
90pub fn default_builtin_runtime() -> Runtime<'static> {
92 if cfg!(feature = "opt-size") {
93 Runtime::OptSize
94 } else {
95 Runtime::Default
96 }
97}
98
99pub async fn componentize(opts: &ComponentizeOpts<'_>) -> Result<Vec<u8>> {
101 let mut resolve = Resolve::default();
102 let (pkg_id, _) = resolve.push_path(opts.wit_path)?;
103 let world_id = resolve.select_world(&[pkg_id], opts.world_name)?;
104
105 let shim = codegen::generate_shim(&resolve, world_id);
106 let mut wit_dylib = wit_dylib::create(&resolve, world_id, None);
107
108 wit_component::embed_component_metadata(
109 &mut wit_dylib,
110 &resolve,
111 world_id,
112 wit_component::StringEncoding::UTF8,
113 )?;
114
115 let pre_wizer_component = wit_component::Linker::default()
116 .validate(true)
117 .library(
118 "componentize_qjs_runtime.wasm",
119 runtime_wasm(opts.runtime),
120 false,
121 )?
122 .library("wit-dylib.wasm", &wit_dylib, false)?
123 .adapter(
124 "wasi_snapshot_preview1",
125 WASI_SNAPSHOT_PREVIEW1_REACTOR_ADAPTER,
126 )?
127 .encode()
128 .context("failed to link and encode component")?;
129
130 let mut component =
131 wizer_init(&pre_wizer_component, &shim, opts.js_source, opts.disable_gc).await?;
132
133 if opts.stub_wasi {
134 component = stub_wasi_imports(&component).context("failed to stub WASI imports")?;
135 }
136
137 Ok(component)
138}
139
140pub fn default_runtime_wasm() -> &'static [u8] {
142 DEFAULT_RUNTIME_WASM
143}
144
145pub fn opt_size_runtime_wasm() -> &'static [u8] {
147 OPT_SIZE_RUNTIME_WASM
148}
149
150pub fn default_sync_runtime_wasm() -> &'static [u8] {
152 DEFAULT_SYNC_RUNTIME_WASM
153}
154
155pub fn opt_size_sync_runtime_wasm() -> &'static [u8] {
157 OPT_SIZE_SYNC_RUNTIME_WASM
158}
159
160fn runtime_wasm(runtime: Runtime<'_>) -> &[u8] {
161 match runtime {
162 Runtime::Default => DEFAULT_RUNTIME_WASM,
163 Runtime::OptSize => OPT_SIZE_RUNTIME_WASM,
164 Runtime::DefaultSync => DEFAULT_SYNC_RUNTIME_WASM,
165 Runtime::OptSizeSync => OPT_SIZE_SYNC_RUNTIME_WASM,
166 Runtime::Custom(wasm) => wasm,
167 }
168}
169
170async fn wizer_init(component: &[u8], shim: &str, js: &str, disable_gc: bool) -> Result<Vec<u8>> {
171 let stdout = MemoryOutputPipe::new(10000);
172 let stderr = MemoryOutputPipe::new(10000);
173
174 let mut wasi = WasiCtxBuilder::new();
175 let wasi = wasi
176 .stdin(MemoryInputPipe::new(Bytes::new()))
177 .stdout(stdout.clone())
178 .stderr(stderr.clone())
179 .build();
180
181 let table = ResourceTable::new();
182 let mut config = Config::new();
183 config.wasm_component_model(true);
184 config.wasm_component_model_async(true);
185
186 let engine = Engine::new(&config)?;
187 let mut store = Store::new(&engine, Ctx { wasi, table });
188
189 let wizer = Wizer::new();
190 let (cx, instrumented) = wizer.instrument_component(component)?;
191 let comp = Component::new(&engine, &instrumented)?;
192
193 let mut linker = Linker::new(&engine);
194 wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
195 wasmtime_wasi::p3::add_to_linker(&mut linker)?;
196 let instance = linker.instantiate_async(&mut store, &comp).await?;
197
198 let init = Init::new(&mut store, &instance)?;
199 init.call_init(&mut store, shim, js, disable_gc)
200 .await?
201 .map_err(|e| anyhow!("{e}"))
202 .with_context(move || {
203 format!(
204 "{}{}",
205 String::from_utf8_lossy(&stdout.contents()),
206 String::from_utf8_lossy(&stderr.contents())
207 )
208 })?;
209
210 let component = wizer
211 .snapshot_component(
212 cx,
213 &mut WasmtimeWizerComponent {
214 store: &mut store,
215 instance,
216 },
217 )
218 .await?;
219
220 Ok(component)
221}