use wasm_encoder::{ConstExpr, DataSection, MemorySection, MemoryType, RawSection};
use wasmparser::{DataKind, Parser, Payload};
use crate::imp::compiler::error::{CompilerError, Result};
use crate::imp::extract::const_i32_offset;
const WASM_PAGE: u64 = 65536;
pub fn inject_data_section(template: &[u8], blob: &[u8], mem_offset: u32) -> Result<Vec<u8>> {
let needed_bytes = mem_offset as u64 + blob.len() as u64;
let needed_pages = needed_bytes.div_ceil(WASM_PAGE);
let mut data = DataSection::new();
let mut module = wasm_encoder::Module::new();
for payload in Parser::new(0).parse_all(template) {
let payload = payload.map_err(|e| CompilerError::InvalidTemplate(e.to_string()))?;
match payload {
Payload::DataSection(reader) => {
for seg in reader {
let seg = seg.map_err(|e| CompilerError::InvalidTemplate(e.to_string()))?;
match seg.kind {
DataKind::Passive => {
data.passive(seg.data.iter().copied());
}
DataKind::Active {
memory_index,
offset_expr,
} => {
let off = const_i32_offset(&offset_expr).ok_or_else(|| {
CompilerError::InvalidTemplate(
"unsupported active data-segment offset expression".into(),
)
})?;
data.active(
memory_index,
&ConstExpr::i32_const(off),
seg.data.iter().copied(),
);
}
}
}
}
Payload::DataCountSection { .. } => {}
Payload::MemorySection(reader) => {
let mut mem = MemorySection::new();
for m in reader {
let m = m.map_err(|e| CompilerError::InvalidTemplate(e.to_string()))?;
let min = m.initial.max(needed_pages);
if needed_pages > crate::imp::compiler::template::MAX_MEMORY_PAGES {
return Err(CompilerError::Validation(format!(
"data section needs {needed_pages} pages but §5.1 memory ceiling is {}",
crate::imp::compiler::template::MAX_MEMORY_PAGES
)));
}
mem.memory(MemoryType {
minimum: min,
maximum: Some(crate::imp::compiler::template::MAX_MEMORY_PAGES),
memory64: m.memory64,
shared: m.shared,
page_size_log2: None,
});
}
module.section(&mem);
}
Payload::CodeSectionStart { range, .. } => {
module.section(&RawSection {
id: 10,
data: &template[range],
});
}
Payload::CodeSectionEntry(_) => {}
Payload::TypeSection(r) => {
module.section(&RawSection {
id: 1,
data: &template[r.range()],
});
}
Payload::ImportSection(r) => {
module.section(&RawSection {
id: 2,
data: &template[r.range()],
});
}
Payload::FunctionSection(r) => {
module.section(&RawSection {
id: 3,
data: &template[r.range()],
});
}
Payload::TableSection(r) => {
module.section(&RawSection {
id: 4,
data: &template[r.range()],
});
}
Payload::GlobalSection(r) => {
module.section(&RawSection {
id: 6,
data: &template[r.range()],
});
}
Payload::ExportSection(r) => {
module.section(&RawSection {
id: 7,
data: &template[r.range()],
});
}
Payload::StartSection { range, .. } => {
module.section(&RawSection {
id: 8,
data: &template[range],
});
}
Payload::ElementSection(r) => {
module.section(&RawSection {
id: 9,
data: &template[r.range()],
});
}
Payload::CustomSection(r) => {
module.section(&RawSection {
id: 0,
data: &template[r.range()],
});
}
_ => {}
}
}
data.active(
0,
&ConstExpr::i32_const(mem_offset as i32),
blob.iter().copied(),
);
module.section(&data);
let bytes = module.finish();
Parser::new(0)
.parse_all(&bytes)
.try_for_each(|p| p.map(|_| ()))
.map_err(|e| CompilerError::Validation(e.to_string()))?;
Ok(bytes)
}
pub fn extract_data_section(module: &[u8], mem_offset: u32) -> Result<Vec<u8>> {
use crate::imp::core::datasection::DataView;
use crate::imp::extract::{extract_digs_segment, ExtractError};
let raw = extract_digs_segment(module, mem_offset).map_err(|e| match e {
ExtractError::BadWasm => CompilerError::InvalidTemplate("invalid wasm module".into()),
ExtractError::NoDataSection => {
CompilerError::InvalidTemplate("no DIGS data segment at expected offset".into())
}
})?;
let view = DataView::parse(&raw)
.map_err(|e| CompilerError::InvalidTemplate(format!("bad DIGS blob: {e:?}")))?;
Ok(raw[..view.total_len()].to_vec())
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
use super::*;
use wasmparser::{Parser, Payload};
fn memory_limits(module: &[u8]) -> (u64, Option<u64>) {
for payload in Parser::new(0).parse_all(module) {
if let Payload::MemorySection(reader) = payload.unwrap() {
let m = reader.into_iter().next().unwrap().unwrap();
return (m.initial, m.maximum);
}
}
panic!("no memory section");
}
#[test]
fn inject_normalizes_unbounded_memory_to_ceiling() {
let watsrc = r#"(module (memory (export "memory") 1))"#;
let template = wat::parse_str(watsrc).unwrap();
let (_, max) = memory_limits(&template);
assert_eq!(max, None, "precondition: template has no declared max");
let blob = b"DIGS-blob-bytes";
let out = inject_data_section(&template, blob, 0x10).expect("inject ok");
let (_, max) = memory_limits(&out);
assert_eq!(
max,
Some(6144),
"§5.1 emitted module must declare maximum 6144"
);
}
#[test]
fn inject_preserves_ceiling_memory_max() {
let watsrc = r#"(module (memory (export "memory") 4 6144))"#;
let template = wat::parse_str(watsrc).unwrap();
let blob = b"DIGS";
let out = inject_data_section(&template, blob, 0x10).expect("inject ok");
let (_, max) = memory_limits(&out);
assert_eq!(max, Some(6144));
}
}