use anyhow::Context;
const ADAPTER_BYTES: &[u8] = include_bytes!("../wasm_abi/data/viceroy-component-adapter.wasm");
const ADAPTER_NOSHIFT_BYTES: &[u8] =
include_bytes!("../wasm_abi/data/viceroy-component-adapter.noshift.wasm");
const LIBRARY_ADAPTER_BYTES: &[u8] =
include_bytes!("../wasm_abi/data/viceroy-component-adapter.library.wasm");
const LIBRARY_ADAPTER_NOSHIFT_BYTES: &[u8] =
include_bytes!("../wasm_abi/data/viceroy-component-adapter.library.noshift.wasm");
pub fn is_component(bytes: &[u8]) -> bool {
wasmparser::Parser::is_component(bytes)
}
pub fn adapt_wat(wat: &str) -> anyhow::Result<Vec<u8>> {
let bytes = wat::parse_str(wat)?;
adapt_bytes(&bytes)
}
pub fn adapt_bytes(bytes: &[u8]) -> anyhow::Result<Vec<u8>> {
let library = !has_export(bytes, "_start");
let needs_no_shift_adapter = has_wit_bindgen_imports(bytes);
let bytes = if needs_no_shift_adapter {
bytes.to_vec()
} else {
crate::shift_mem::shift_main_module(bytes)?
};
let module = mangle_imports(&bytes)?;
let adapter_bytes = match (library, needs_no_shift_adapter) {
(true, true) => LIBRARY_ADAPTER_NOSHIFT_BYTES,
(true, false) => LIBRARY_ADAPTER_BYTES,
(false, true) => ADAPTER_NOSHIFT_BYTES,
(false, false) => ADAPTER_BYTES,
};
let component = wit_component::ComponentEncoder::default()
.module(module.as_slice())?
.adapter("wasi_snapshot_preview1", adapter_bytes)?
.validate(true)
.encode()?;
let mut producers = wasm_metadata::Producers::empty();
let mut flags = Vec::with_capacity(2);
if library {
flags.push("library");
}
if needs_no_shift_adapter {
flags.push("noshift");
}
producers.add(
"processed-by",
"viceroy adapt",
&format!("{} ({})", env!("CARGO_PKG_VERSION"), flags.join(", ")),
);
let component = producers
.add_to_wasm(&component)
.context("failed to add viceroy producer metadata to wasm")?;
Ok(component)
}
fn mangle_imports(bytes: &[u8]) -> anyhow::Result<wasm_encoder::Module> {
let mut module = wasm_encoder::Module::new();
for payload in wasmparser::Parser::new(0).parse_all(bytes) {
let payload = payload?;
match payload {
wasmparser::Payload::Version {
encoding: wasmparser::Encoding::Component,
..
} => {
anyhow::bail!("Mangling only supports core-wasm modules, not components");
}
wasmparser::Payload::ImportSection(section) => {
let mut imports = wasm_encoder::ImportSection::new();
for import in section {
let import = import?;
let entity = wasm_encoder::EntityType::try_from(import.ty).map_err(|_| {
anyhow::anyhow!(
"Failed to translate type for import {}:{}",
import.module,
import.name
)
})?;
if is_fastly_module(import.module) {
let module = "wasi_snapshot_preview1";
let name = format!("{}#{}", import.module, import.name);
imports.import(module, &name, entity);
} else {
imports.import(import.module, import.name, entity);
}
}
module.section(&imports);
}
payload => {
if let Some((id, range)) = payload.as_section() {
module.section(&wasm_encoder::RawSection {
id,
data: &bytes[range],
});
}
}
}
}
Ok(module)
}
fn has_export(bytes: &[u8], wanted: &str) -> bool {
for payload in wasmparser::Parser::new(0).parse_all(bytes) {
let Ok(payload) = payload else {
return false;
};
if let wasmparser::Payload::ExportSection(section) = payload {
for export in section {
let Ok(export) = export else {
return false;
};
if export.name == wanted {
return true;
}
}
}
}
false
}
fn is_fastly_module(module: &str) -> bool {
module.starts_with("fastly_") || module == "env" || module == "fastly" || module == "xqd"
}
fn has_wit_bindgen_imports(bytes: &[u8]) -> bool {
for payload in wasmparser::Parser::new(0).parse_all(bytes) {
let Ok(payload) = payload else {
return false;
};
if let wasmparser::Payload::ImportSection(section) = payload {
for import in section {
let Ok(import) = import else {
return false;
};
if !is_fastly_module(import.module) && import.module != "wasi_snapshot_preview1" {
return true;
}
}
}
}
false
}