1use crate::{Engine, ModuleVersionStrategy};
25use anyhow::{anyhow, bail, Context, Result};
26use object::write::{Object, StandardSegment};
27use object::{File, FileFlags, Object as _, ObjectSection, SectionKind};
28use serde::{Deserialize, Serialize};
29use std::collections::BTreeMap;
30use std::str::FromStr;
31use wasmtime_environ::obj;
32use wasmtime_environ::{FlagValue, ObjectKind, Tunables};
33use wasmtime_runtime::MmapVec;
34
35const VERSION: u8 = 0;
36
37#[cfg(compiler)]
44pub fn append_compiler_info(engine: &Engine, obj: &mut Object<'_>) {
45 let section = obj.add_section(
46 obj.segment_name(StandardSegment::Data).to_vec(),
47 obj::ELF_WASM_ENGINE.as_bytes().to_vec(),
48 SectionKind::ReadOnlyData,
49 );
50 let mut data = Vec::new();
51 data.push(VERSION);
52 let version = match &engine.config().module_version {
53 ModuleVersionStrategy::WasmtimeVersion => env!("CARGO_PKG_VERSION"),
54 ModuleVersionStrategy::Custom(c) => c,
55 ModuleVersionStrategy::None => "",
56 };
57 assert!(
59 version.len() < 256,
60 "package version must be less than 256 bytes"
61 );
62 data.push(version.len() as u8);
63 data.extend_from_slice(version.as_bytes());
64 bincode::serialize_into(&mut data, &Metadata::new(engine)).unwrap();
65 obj.set_section_data(section, data, 1);
66}
67
68pub fn check_compatible(engine: &Engine, mmap: &MmapVec, expected: ObjectKind) -> Result<()> {
77 let obj = File::parse(&mmap[..]).context("failed to parse precompiled artifact as an ELF")?;
91 let expected_e_flags = match expected {
92 ObjectKind::Module => obj::EF_WASMTIME_MODULE,
93 ObjectKind::Component => obj::EF_WASMTIME_COMPONENT,
94 };
95 match obj.flags() {
96 FileFlags::Elf {
97 os_abi: obj::ELFOSABI_WASMTIME,
98 abi_version: 0,
99 e_flags,
100 } if e_flags == expected_e_flags => {}
101 _ => bail!("incompatible object file format"),
102 }
103
104 let data = obj
105 .section_by_name(obj::ELF_WASM_ENGINE)
106 .ok_or_else(|| anyhow!("failed to find section `{}`", obj::ELF_WASM_ENGINE))?
107 .data()?;
108 let (first, data) = data
109 .split_first()
110 .ok_or_else(|| anyhow!("invalid engine section"))?;
111 if *first != VERSION {
112 bail!("mismatched version in engine section");
113 }
114 let (len, data) = data
115 .split_first()
116 .ok_or_else(|| anyhow!("invalid engine section"))?;
117 let len = usize::from(*len);
118 let (version, data) = if data.len() < len + 1 {
119 bail!("engine section too small")
120 } else {
121 data.split_at(len)
122 };
123
124 match &engine.config().module_version {
125 ModuleVersionStrategy::WasmtimeVersion => {
126 let version = std::str::from_utf8(version)?;
127 if version != env!("CARGO_PKG_VERSION") {
128 bail!(
129 "Module was compiled with incompatible Wasmtime version '{}'",
130 version
131 );
132 }
133 }
134 ModuleVersionStrategy::Custom(v) => {
135 let version = std::str::from_utf8(&version)?;
136 if version != v {
137 bail!(
138 "Module was compiled with incompatible version '{}'",
139 version
140 );
141 }
142 }
143 ModuleVersionStrategy::None => { }
144 }
145 bincode::deserialize::<Metadata>(data)?.check_compatible(engine)
146}
147
148#[derive(Serialize, Deserialize)]
149struct Metadata {
150 target: String,
151 shared_flags: BTreeMap<String, FlagValue>,
152 isa_flags: BTreeMap<String, FlagValue>,
153 tunables: Tunables,
154 features: WasmFeatures,
155}
156
157#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
159struct WasmFeatures {
160 reference_types: bool,
161 multi_value: bool,
162 bulk_memory: bool,
163 component_model: bool,
164 simd: bool,
165 threads: bool,
166 multi_memory: bool,
167 exceptions: bool,
168 memory64: bool,
169 relaxed_simd: bool,
170 extended_const: bool,
171}
172
173impl Metadata {
174 #[cfg(compiler)]
175 fn new(engine: &Engine) -> Metadata {
176 let wasmparser::WasmFeatures {
177 reference_types,
178 multi_value,
179 bulk_memory,
180 component_model,
181 simd,
182 threads,
183 tail_call,
184 multi_memory,
185 exceptions,
186 memory64,
187 relaxed_simd,
188 extended_const,
189 memory_control,
190 function_references,
191
192 mutable_global: _,
194 saturating_float_to_int: _,
195 sign_extension: _,
196 floats: _,
197 } = engine.config().features;
198
199 assert!(!memory_control);
200 assert!(!tail_call);
201 assert!(!function_references);
202
203 Metadata {
204 target: engine.compiler().triple().to_string(),
205 shared_flags: engine.compiler().flags(),
206 isa_flags: engine.compiler().isa_flags(),
207 tunables: engine.config().tunables.clone(),
208 features: WasmFeatures {
209 reference_types,
210 multi_value,
211 bulk_memory,
212 component_model,
213 simd,
214 threads,
215 multi_memory,
216 exceptions,
217 memory64,
218 relaxed_simd,
219 extended_const,
220 },
221 }
222 }
223
224 fn check_compatible(mut self, engine: &Engine) -> Result<()> {
225 self.check_triple(engine)?;
226 self.check_shared_flags(engine)?;
227 self.check_isa_flags(engine)?;
228 self.check_tunables(&engine.config().tunables)?;
229 self.check_features(&engine.config().features)?;
230 Ok(())
231 }
232
233 fn check_triple(&self, engine: &Engine) -> Result<()> {
234 let engine_target = engine.target();
235 let module_target =
236 target_lexicon::Triple::from_str(&self.target).map_err(|e| anyhow!(e))?;
237
238 if module_target.architecture != engine_target.architecture {
239 bail!(
240 "Module was compiled for architecture '{}'",
241 module_target.architecture
242 );
243 }
244
245 if module_target.operating_system != engine_target.operating_system {
246 bail!(
247 "Module was compiled for operating system '{}'",
248 module_target.operating_system
249 );
250 }
251
252 Ok(())
253 }
254
255 fn check_shared_flags(&mut self, engine: &Engine) -> Result<()> {
256 for (name, val) in self.shared_flags.iter() {
257 engine
258 .check_compatible_with_shared_flag(name, val)
259 .map_err(|s| anyhow::Error::msg(s))
260 .context("compilation settings of module incompatible with native host")?;
261 }
262 Ok(())
263 }
264
265 fn check_isa_flags(&mut self, engine: &Engine) -> Result<()> {
266 for (name, val) in self.isa_flags.iter() {
267 engine
268 .check_compatible_with_isa_flag(name, val)
269 .map_err(|s| anyhow::Error::msg(s))
270 .context("compilation settings of module incompatible with native host")?;
271 }
272 Ok(())
273 }
274
275 fn check_int<T: Eq + std::fmt::Display>(found: T, expected: T, feature: &str) -> Result<()> {
276 if found == expected {
277 return Ok(());
278 }
279
280 bail!(
281 "Module was compiled with a {} of '{}' but '{}' is expected for the host",
282 feature,
283 found,
284 expected
285 );
286 }
287
288 fn check_bool(found: bool, expected: bool, feature: &str) -> Result<()> {
289 if found == expected {
290 return Ok(());
291 }
292
293 bail!(
294 "Module was compiled {} {} but it {} enabled for the host",
295 if found { "with" } else { "without" },
296 feature,
297 if expected { "is" } else { "is not" }
298 );
299 }
300
301 fn check_tunables(&mut self, other: &Tunables) -> Result<()> {
302 let Tunables {
303 static_memory_bound,
304 static_memory_offset_guard_size,
305 dynamic_memory_offset_guard_size,
306 generate_native_debuginfo,
307 parse_wasm_debuginfo,
308 consume_fuel,
309 epoch_interruption,
310 static_memory_bound_is_maximum,
311 guard_before_linear_memory,
312 relaxed_simd_deterministic,
313
314 dynamic_memory_growth_reserve: _,
316
317 generate_address_map: _,
322
323 debug_adapter_modules: _,
325 } = self.tunables;
326
327 Self::check_int(
328 static_memory_bound,
329 other.static_memory_bound,
330 "static memory bound",
331 )?;
332 Self::check_int(
333 static_memory_offset_guard_size,
334 other.static_memory_offset_guard_size,
335 "static memory guard size",
336 )?;
337 Self::check_int(
338 dynamic_memory_offset_guard_size,
339 other.dynamic_memory_offset_guard_size,
340 "dynamic memory guard size",
341 )?;
342 Self::check_bool(
343 generate_native_debuginfo,
344 other.generate_native_debuginfo,
345 "debug information support",
346 )?;
347 Self::check_bool(
348 parse_wasm_debuginfo,
349 other.parse_wasm_debuginfo,
350 "WebAssembly backtrace support",
351 )?;
352 Self::check_bool(consume_fuel, other.consume_fuel, "fuel support")?;
353 Self::check_bool(
354 epoch_interruption,
355 other.epoch_interruption,
356 "epoch interruption",
357 )?;
358 Self::check_bool(
359 static_memory_bound_is_maximum,
360 other.static_memory_bound_is_maximum,
361 "pooling allocation support",
362 )?;
363 Self::check_bool(
364 guard_before_linear_memory,
365 other.guard_before_linear_memory,
366 "guard before linear memory",
367 )?;
368 Self::check_bool(
369 relaxed_simd_deterministic,
370 other.relaxed_simd_deterministic,
371 "relaxed simd deterministic semantics",
372 )?;
373
374 Ok(())
375 }
376
377 fn check_features(&mut self, other: &wasmparser::WasmFeatures) -> Result<()> {
378 let WasmFeatures {
379 reference_types,
380 multi_value,
381 bulk_memory,
382 component_model,
383 simd,
384 threads,
385 multi_memory,
386 exceptions,
387 memory64,
388 relaxed_simd,
389 extended_const,
390 } = self.features;
391
392 Self::check_bool(
393 reference_types,
394 other.reference_types,
395 "WebAssembly reference types support",
396 )?;
397 Self::check_bool(
398 multi_value,
399 other.multi_value,
400 "WebAssembly multi-value support",
401 )?;
402 Self::check_bool(
403 bulk_memory,
404 other.bulk_memory,
405 "WebAssembly bulk memory support",
406 )?;
407 Self::check_bool(
408 component_model,
409 other.component_model,
410 "WebAssembly component model support",
411 )?;
412 Self::check_bool(simd, other.simd, "WebAssembly SIMD support")?;
413 Self::check_bool(threads, other.threads, "WebAssembly threads support")?;
414 Self::check_bool(
415 multi_memory,
416 other.multi_memory,
417 "WebAssembly multi-memory support",
418 )?;
419 Self::check_bool(
420 exceptions,
421 other.exceptions,
422 "WebAssembly exceptions support",
423 )?;
424 Self::check_bool(
425 memory64,
426 other.memory64,
427 "WebAssembly 64-bit memory support",
428 )?;
429 Self::check_bool(
430 extended_const,
431 other.extended_const,
432 "WebAssembly extended-const support",
433 )?;
434 Self::check_bool(
435 relaxed_simd,
436 other.relaxed_simd,
437 "WebAssembly relaxed-simd support",
438 )?;
439
440 Ok(())
441 }
442}
443
444#[cfg(test)]
445mod test {
446 use super::*;
447 use crate::Config;
448
449 #[test]
450 fn test_architecture_mismatch() -> Result<()> {
451 let engine = Engine::default();
452 let mut metadata = Metadata::new(&engine);
453 metadata.target = "unknown-generic-linux".to_string();
454
455 match metadata.check_compatible(&engine) {
456 Ok(_) => unreachable!(),
457 Err(e) => assert_eq!(
458 e.to_string(),
459 "Module was compiled for architecture 'unknown'",
460 ),
461 }
462
463 Ok(())
464 }
465
466 #[test]
467 fn test_os_mismatch() -> Result<()> {
468 let engine = Engine::default();
469 let mut metadata = Metadata::new(&engine);
470
471 metadata.target = format!(
472 "{}-generic-unknown",
473 target_lexicon::Triple::host().architecture
474 );
475
476 match metadata.check_compatible(&engine) {
477 Ok(_) => unreachable!(),
478 Err(e) => assert_eq!(
479 e.to_string(),
480 "Module was compiled for operating system 'unknown'",
481 ),
482 }
483
484 Ok(())
485 }
486
487 #[test]
488 fn test_cranelift_flags_mismatch() -> Result<()> {
489 let engine = Engine::default();
490 let mut metadata = Metadata::new(&engine);
491
492 metadata.shared_flags.insert(
493 "preserve_frame_pointers".to_string(),
494 FlagValue::Bool(false),
495 );
496
497 match metadata.check_compatible(&engine) {
498 Ok(_) => unreachable!(),
499 Err(e) => assert!(format!("{:?}", e).starts_with(
500 "\
501compilation settings of module incompatible with native host
502
503Caused by:
504 setting \"preserve_frame_pointers\" is configured to Bool(false) which is not supported"
505 )),
506 }
507
508 Ok(())
509 }
510
511 #[test]
512 fn test_isa_flags_mismatch() -> Result<()> {
513 let engine = Engine::default();
514 let mut metadata = Metadata::new(&engine);
515
516 metadata
517 .isa_flags
518 .insert("not_a_flag".to_string(), FlagValue::Bool(true));
519
520 match metadata.check_compatible(&engine) {
521 Ok(_) => unreachable!(),
522 Err(e) => assert!(format!("{:?}", e).starts_with(
523 "\
524compilation settings of module incompatible with native host
525
526Caused by:
527 cannot test if target-specific flag \"not_a_flag\" is available at runtime",
528 )),
529 }
530
531 Ok(())
532 }
533
534 #[test]
535 fn test_tunables_int_mismatch() -> Result<()> {
536 let engine = Engine::default();
537 let mut metadata = Metadata::new(&engine);
538
539 metadata.tunables.static_memory_offset_guard_size = 0;
540
541 match metadata.check_compatible(&engine) {
542 Ok(_) => unreachable!(),
543 Err(e) => assert_eq!(e.to_string(), "Module was compiled with a static memory guard size of '0' but '2147483648' is expected for the host"),
544 }
545
546 Ok(())
547 }
548
549 #[test]
550 fn test_tunables_bool_mismatch() -> Result<()> {
551 let mut config = Config::new();
552 config.epoch_interruption(true);
553
554 let engine = Engine::new(&config)?;
555 let mut metadata = Metadata::new(&engine);
556 metadata.tunables.epoch_interruption = false;
557
558 match metadata.check_compatible(&engine) {
559 Ok(_) => unreachable!(),
560 Err(e) => assert_eq!(
561 e.to_string(),
562 "Module was compiled without epoch interruption but it is enabled for the host"
563 ),
564 }
565
566 let mut config = Config::new();
567 config.epoch_interruption(false);
568
569 let engine = Engine::new(&config)?;
570 let mut metadata = Metadata::new(&engine);
571 metadata.tunables.epoch_interruption = true;
572
573 match metadata.check_compatible(&engine) {
574 Ok(_) => unreachable!(),
575 Err(e) => assert_eq!(
576 e.to_string(),
577 "Module was compiled with epoch interruption but it is not enabled for the host"
578 ),
579 }
580
581 Ok(())
582 }
583
584 #[test]
585 fn test_feature_mismatch() -> Result<()> {
586 let mut config = Config::new();
587 config.wasm_simd(true);
588
589 let engine = Engine::new(&config)?;
590 let mut metadata = Metadata::new(&engine);
591 metadata.features.simd = false;
592
593 match metadata.check_compatible(&engine) {
594 Ok(_) => unreachable!(),
595 Err(e) => assert_eq!(e.to_string(), "Module was compiled without WebAssembly SIMD support but it is enabled for the host"),
596 }
597
598 let mut config = Config::new();
599 config.wasm_simd(false);
600
601 let engine = Engine::new(&config)?;
602 let mut metadata = Metadata::new(&engine);
603 metadata.features.simd = true;
604
605 match metadata.check_compatible(&engine) {
606 Ok(_) => unreachable!(),
607 Err(e) => assert_eq!(e.to_string(), "Module was compiled with WebAssembly SIMD support but it is not enabled for the host"),
608 }
609
610 Ok(())
611 }
612}