use std::path::Path;
use wasmtime::{Engine, component::Component};
use wasmtime_wizer::{WasmtimeWizerComponent, Wizer};
use crate::{
host::{BoxError, Host, HttpRequest, HttpResponse},
internal::{
module::{
ModuleConfig,
cache::{cache_key, write_cache_file_atomic},
},
sandbox::{InstanceState, exports::GuestIndices},
},
sandbox::{DirectoryMapping, Error, Result},
value::Value as IsolaValue,
};
pub async fn load_or_compile_component(
engine: &Engine,
wasm_path: &Path,
directory_mappings: &[DirectoryMapping],
cfg: &ModuleConfig,
) -> Result<Component> {
let wasm_bytes = tokio::fs::read(wasm_path).await.map_err(Error::from)?;
let Some(cache_dir) = &cfg.cache else {
let bytes =
compile_serialized_component(engine, cfg, directory_mappings, &wasm_bytes).await?;
let component = unsafe { Component::deserialize(engine, &bytes) }.map_err(Error::Wasm)?;
return Ok(component);
};
tokio::fs::create_dir_all(cache_dir)
.await
.map_err(Error::from)?;
let key = cache_key(engine, cfg, &wasm_bytes);
let cache_path = cache_dir.join(format!("{key}.cwasm"));
if let Ok(component) = unsafe { Component::deserialize_file(engine, &cache_path) } {
return Ok(component);
}
let bytes = compile_serialized_component(engine, cfg, directory_mappings, &wasm_bytes).await?;
write_cache_file_atomic(&cache_path, &bytes).await?;
let component =
unsafe { Component::deserialize_file(engine, &cache_path) }.map_err(Error::Wasm)?;
Ok(component)
}
async fn compile_serialized_component(
engine: &Engine,
cfg: &ModuleConfig,
directory_mappings: &[DirectoryMapping],
wasm_bytes: &[u8],
) -> Result<Vec<u8>> {
let engine = engine.clone();
let cfg = cfg.clone();
let directory_mappings = directory_mappings.to_vec();
let wasm_bytes = wasm_bytes.to_vec();
tokio::task::spawn_blocking(move || {
tokio::runtime::Handle::current().block_on(async move {
let wizer = Wizer::new();
let (cx, instrumented_wasm) = wizer
.instrument_component(&wasm_bytes)
.map_err(Error::Wasm)?;
let component = Component::new(&engine, &instrumented_wasm).map_err(Error::Wasm)?;
let linker = InstanceState::<CompileHost>::new_linker(&engine).map_err(Error::Wasm)?;
let mut store = InstanceState::new(
&engine,
&directory_mappings,
&cfg.env,
cfg.max_memory,
CompileHost,
)
.map_err(Error::Wasm)?;
store.epoch_deadline_async_yield_and_update(1);
let pre = linker.instantiate_pre(&component).map_err(Error::Wasm)?;
let instance = pre
.instantiate_async(&mut store)
.await
.map_err(Error::Wasm)?;
let guest = GuestIndices::new(&pre)
.map_err(Error::Wasm)?
.load(&mut store, &instance)
.map_err(Error::Wasm)?;
guest
.call_initialize(&mut store, true, cfg.prelude.as_deref())
.await
.map_err(Error::Wasm)?;
let data = wizer
.snapshot_component(
cx,
&mut WasmtimeWizerComponent {
store: &mut store,
instance,
},
)
.await
.map_err(Error::Wasm)?;
let component = Component::new(&engine, &data).map_err(Error::Wasm)?;
component.serialize().map_err(Error::Wasm)
})
})
.await
.map_err(|e| Error::Other(e.into()))?
}
struct CompileHost;
#[async_trait::async_trait]
impl Host for CompileHost {
async fn hostcall(
&self,
_call_type: &str,
_payload: IsolaValue,
) -> core::result::Result<IsolaValue, BoxError> {
Err(std::io::Error::other("unsupported during compilation").into())
}
async fn http_request(
&self,
_req: HttpRequest,
) -> core::result::Result<HttpResponse, BoxError> {
Err(std::io::Error::other("unsupported during compilation").into())
}
}