wasmtime/engine.rs
1use crate::signatures::SignatureRegistry;
2use crate::Config;
3use anyhow::{Context, Result};
4use object::write::{Object, StandardSegment};
5use object::SectionKind;
6use once_cell::sync::OnceCell;
7#[cfg(feature = "parallel-compilation")]
8use rayon::prelude::*;
9use std::path::Path;
10use std::sync::atomic::{AtomicU64, Ordering};
11use std::sync::Arc;
12#[cfg(feature = "cache")]
13use wasmtime_cache::CacheConfig;
14use wasmtime_environ::obj;
15use wasmtime_environ::{FlagValue, ObjectKind};
16use wasmtime_jit::{CodeMemory, ProfilingAgent};
17use wasmtime_runtime::{debug_builtins, CompiledModuleIdAllocator, InstanceAllocator, MmapVec};
18
19mod serialization;
20
21/// An `Engine` which is a global context for compilation and management of wasm
22/// modules.
23///
24/// An engine can be safely shared across threads and is a cheap cloneable
25/// handle to the actual engine. The engine itself will be deallocated once all
26/// references to it have gone away.
27///
28/// Engines store global configuration preferences such as compilation settings,
29/// enabled features, etc. You'll likely only need at most one of these for a
30/// program.
31///
32/// ## Engines and `Clone`
33///
34/// Using `clone` on an `Engine` is a cheap operation. It will not create an
35/// entirely new engine, but rather just a new reference to the existing engine.
36/// In other words it's a shallow copy, not a deep copy.
37///
38/// ## Engines and `Default`
39///
40/// You can create an engine with default configuration settings using
41/// `Engine::default()`. Be sure to consult the documentation of [`Config`] for
42/// default settings.
43#[derive(Clone)]
44pub struct Engine {
45 inner: Arc<EngineInner>,
46}
47
48struct EngineInner {
49 config: Config,
50 #[cfg(compiler)]
51 compiler: Box<dyn wasmtime_environ::Compiler>,
52 allocator: Box<dyn InstanceAllocator + Send + Sync>,
53 profiler: Box<dyn ProfilingAgent>,
54 signatures: SignatureRegistry,
55 epoch: AtomicU64,
56 unique_id_allocator: CompiledModuleIdAllocator,
57
58 // One-time check of whether the compiler's settings, if present, are
59 // compatible with the native host.
60 compatible_with_native_host: OnceCell<Result<(), String>>,
61}
62
63impl Engine {
64 /// Creates a new [`Engine`] with the specified compilation and
65 /// configuration settings.
66 ///
67 /// # Errors
68 ///
69 /// This method can fail if the `config` is invalid or some
70 /// configurations are incompatible.
71 ///
72 /// For example, feature `reference_types` will need to set
73 /// the compiler setting `enable_safepoints` and `unwind_info`
74 /// to `true`, but explicitly disable these two compiler settings
75 /// will cause errors.
76 pub fn new(config: &Config) -> Result<Engine> {
77 // Ensure that wasmtime_runtime's signal handlers are configured. This
78 // is the per-program initialization required for handling traps, such
79 // as configuring signals, vectored exception handlers, etc.
80 wasmtime_runtime::init_traps(crate::module::is_wasm_trap_pc);
81 debug_builtins::ensure_exported();
82
83 let registry = SignatureRegistry::new();
84 let mut config = config.clone();
85 config.validate()?;
86
87 #[cfg(compiler)]
88 let compiler = config.build_compiler()?;
89 drop(&mut config); // silence warnings without `cfg(compiler)`
90
91 let allocator = config.build_allocator()?;
92 let profiler = config.build_profiler()?;
93
94 Ok(Engine {
95 inner: Arc::new(EngineInner {
96 #[cfg(compiler)]
97 compiler,
98 config,
99 allocator,
100 profiler,
101 signatures: registry,
102 epoch: AtomicU64::new(0),
103 unique_id_allocator: CompiledModuleIdAllocator::new(),
104 compatible_with_native_host: OnceCell::new(),
105 }),
106 })
107 }
108
109 /// Eagerly initialize thread-local functionality shared by all [`Engine`]s.
110 ///
111 /// Wasmtime's implementation on some platforms may involve per-thread
112 /// setup that needs to happen whenever WebAssembly is invoked. This setup
113 /// can take on the order of a few hundred microseconds, whereas the
114 /// overhead of calling WebAssembly is otherwise on the order of a few
115 /// nanoseconds. This setup cost is paid once per-OS-thread. If your
116 /// application is sensitive to the latencies of WebAssembly function
117 /// calls, even those that happen first on a thread, then this function
118 /// can be used to improve the consistency of each call into WebAssembly
119 /// by explicitly frontloading the cost of the one-time setup per-thread.
120 ///
121 /// Note that this function is not required to be called in any embedding.
122 /// Wasmtime will automatically initialize thread-local-state as necessary
123 /// on calls into WebAssembly. This is provided for use cases where the
124 /// latency of WebAssembly calls are extra-important, which is not
125 /// necessarily true of all embeddings.
126 pub fn tls_eager_initialize() {
127 wasmtime_runtime::tls_eager_initialize();
128 }
129
130 /// Returns the configuration settings that this engine is using.
131 #[inline]
132 pub fn config(&self) -> &Config {
133 &self.inner.config
134 }
135
136 #[cfg(compiler)]
137 pub(crate) fn compiler(&self) -> &dyn wasmtime_environ::Compiler {
138 &*self.inner.compiler
139 }
140
141 pub(crate) fn allocator(&self) -> &dyn InstanceAllocator {
142 self.inner.allocator.as_ref()
143 }
144
145 pub(crate) fn profiler(&self) -> &dyn ProfilingAgent {
146 self.inner.profiler.as_ref()
147 }
148
149 #[cfg(feature = "cache")]
150 pub(crate) fn cache_config(&self) -> &CacheConfig {
151 &self.config().cache_config
152 }
153
154 /// Returns whether the engine `a` and `b` refer to the same configuration.
155 pub fn same(a: &Engine, b: &Engine) -> bool {
156 Arc::ptr_eq(&a.inner, &b.inner)
157 }
158
159 pub(crate) fn signatures(&self) -> &SignatureRegistry {
160 &self.inner.signatures
161 }
162
163 pub(crate) fn epoch_counter(&self) -> &AtomicU64 {
164 &self.inner.epoch
165 }
166
167 pub(crate) fn current_epoch(&self) -> u64 {
168 self.epoch_counter().load(Ordering::Relaxed)
169 }
170
171 /// Increments the epoch.
172 ///
173 /// When using epoch-based interruption, currently-executing Wasm
174 /// code within this engine will trap or yield "soon" when the
175 /// epoch deadline is reached or exceeded. (The configuration, and
176 /// the deadline, are set on the `Store`.) The intent of the
177 /// design is for this method to be called by the embedder at some
178 /// regular cadence, for example by a thread that wakes up at some
179 /// interval, or by a signal handler.
180 ///
181 /// See [`Config::epoch_interruption`](crate::Config::epoch_interruption)
182 /// for an introduction to epoch-based interruption and pointers
183 /// to the other relevant methods.
184 ///
185 /// ## Signal Safety
186 ///
187 /// This method is signal-safe: it does not make any syscalls, and
188 /// performs only an atomic increment to the epoch value in
189 /// memory.
190 pub fn increment_epoch(&self) {
191 self.inner.epoch.fetch_add(1, Ordering::Relaxed);
192 }
193
194 pub(crate) fn unique_id_allocator(&self) -> &CompiledModuleIdAllocator {
195 &self.inner.unique_id_allocator
196 }
197
198 /// Ahead-of-time (AOT) compiles a WebAssembly module.
199 ///
200 /// The `bytes` provided must be in one of two formats:
201 ///
202 /// * A [binary-encoded][binary] WebAssembly module. This is always supported.
203 /// * A [text-encoded][text] instance of the WebAssembly text format.
204 /// This is only supported when the `wat` feature of this crate is enabled.
205 /// If this is supplied then the text format will be parsed before validation.
206 /// Note that the `wat` feature is enabled by default.
207 ///
208 /// This method may be used to compile a module for use with a different target
209 /// host. The output of this method may be used with
210 /// [`Module::deserialize`](crate::Module::deserialize) on hosts compatible
211 /// with the [`Config`] associated with this [`Engine`].
212 ///
213 /// The output of this method is safe to send to another host machine for later
214 /// execution. As the output is already a compiled module, translation and code
215 /// generation will be skipped and this will improve the performance of constructing
216 /// a [`Module`](crate::Module) from the output of this method.
217 ///
218 /// [binary]: https://webassembly.github.io/spec/core/binary/index.html
219 /// [text]: https://webassembly.github.io/spec/core/text/index.html
220 #[cfg(compiler)]
221 #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs
222 pub fn precompile_module(&self, bytes: &[u8]) -> Result<Vec<u8>> {
223 #[cfg(feature = "wat")]
224 let bytes = wat::parse_bytes(&bytes)?;
225 let (mmap, _) = crate::Module::build_artifacts(self, &bytes)?;
226 Ok(mmap.to_vec())
227 }
228
229 /// Same as [`Engine::precompile_module`] except for a
230 /// [`Component`](crate::component::Component)
231 #[cfg(compiler)]
232 #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs
233 #[cfg(feature = "component-model")]
234 #[cfg_attr(nightlydoc, doc(cfg(feature = "component-model")))]
235 pub fn precompile_component(&self, bytes: &[u8]) -> Result<Vec<u8>> {
236 #[cfg(feature = "wat")]
237 let bytes = wat::parse_bytes(&bytes)?;
238 let (mmap, _) = crate::component::Component::build_artifacts(self, &bytes)?;
239 Ok(mmap.to_vec())
240 }
241
242 pub(crate) fn run_maybe_parallel<
243 A: Send,
244 B: Send,
245 E: Send,
246 F: Fn(A) -> Result<B, E> + Send + Sync,
247 >(
248 &self,
249 input: Vec<A>,
250 f: F,
251 ) -> Result<Vec<B>, E> {
252 if self.config().parallel_compilation {
253 #[cfg(feature = "parallel-compilation")]
254 return input
255 .into_par_iter()
256 .map(|a| f(a))
257 .collect::<Result<Vec<B>, E>>();
258 }
259
260 // In case the parallel-compilation feature is disabled or the parallel_compilation config
261 // was turned off dynamically fallback to the non-parallel version.
262 input
263 .into_iter()
264 .map(|a| f(a))
265 .collect::<Result<Vec<B>, E>>()
266 }
267
268 /// Executes `f1` and `f2` in parallel if parallel compilation is enabled at
269 /// both runtime and compile time, otherwise runs them synchronously.
270 #[allow(dead_code)] // only used for the component-model feature right now
271 pub(crate) fn join_maybe_parallel<T, U>(
272 &self,
273 f1: impl FnOnce() -> T + Send,
274 f2: impl FnOnce() -> U + Send,
275 ) -> (T, U)
276 where
277 T: Send,
278 U: Send,
279 {
280 if self.config().parallel_compilation {
281 #[cfg(feature = "parallel-compilation")]
282 return rayon::join(f1, f2);
283 }
284 (f1(), f2())
285 }
286
287 /// Returns the target triple which this engine is compiling code for
288 /// and/or running code for.
289 pub(crate) fn target(&self) -> target_lexicon::Triple {
290 // If a compiler is configured, use that target.
291 #[cfg(compiler)]
292 return self.compiler().triple().clone();
293
294 // ... otherwise it's the native target
295 #[cfg(not(compiler))]
296 return target_lexicon::Triple::host();
297 }
298
299 /// Verify that this engine's configuration is compatible with loading
300 /// modules onto the native host platform.
301 ///
302 /// This method is used as part of `Module::new` to ensure that this
303 /// engine can indeed load modules for the configured compiler (if any).
304 /// Note that if cranelift is disabled this trivially returns `Ok` because
305 /// loaded serialized modules are checked separately.
306 pub(crate) fn check_compatible_with_native_host(&self) -> Result<()> {
307 self.inner
308 .compatible_with_native_host
309 .get_or_init(|| self._check_compatible_with_native_host())
310 .clone()
311 .map_err(anyhow::Error::msg)
312 }
313
314 fn _check_compatible_with_native_host(&self) -> Result<(), String> {
315 #[cfg(compiler)]
316 {
317 let compiler = self.compiler();
318
319 // Check to see that the config's target matches the host
320 let target = compiler.triple();
321 if *target != target_lexicon::Triple::host() {
322 return Err(format!(
323 "target '{}' specified in the configuration does not match the host",
324 target
325 ));
326 }
327
328 // Also double-check all compiler settings
329 for (key, value) in compiler.flags().iter() {
330 self.check_compatible_with_shared_flag(key, value)?;
331 }
332 for (key, value) in compiler.isa_flags().iter() {
333 self.check_compatible_with_isa_flag(key, value)?;
334 }
335 }
336 Ok(())
337 }
338
339 /// Checks to see whether the "shared flag", something enabled for
340 /// individual compilers, is compatible with the native host platform.
341 ///
342 /// This is used both when validating an engine's compilation settings are
343 /// compatible with the host as well as when deserializing modules from
344 /// disk to ensure they're compatible with the current host.
345 ///
346 /// Note that most of the settings here are not configured by users that
347 /// often. While theoretically possible via `Config` methods the more
348 /// interesting flags are the ISA ones below. Typically the values here
349 /// represent global configuration for wasm features. Settings here
350 /// currently rely on the compiler informing us of all settings, including
351 /// those disabled. Settings then fall in a few buckets:
352 ///
353 /// * Some settings must be enabled, such as `preserve_frame_pointers`.
354 /// * Some settings must have a particular value, such as
355 /// `libcall_call_conv`.
356 /// * Some settings do not matter as to their value, such as `opt_level`.
357 pub(crate) fn check_compatible_with_shared_flag(
358 &self,
359 flag: &str,
360 value: &FlagValue,
361 ) -> Result<(), String> {
362 let target = self.target();
363 let ok = match flag {
364 // These settings must all have be enabled, since their value
365 // can affect the way the generated code performs or behaves at
366 // runtime.
367 "libcall_call_conv" => *value == FlagValue::Enum("isa_default".into()),
368 "preserve_frame_pointers" => *value == FlagValue::Bool(true),
369 "enable_probestack" => *value == FlagValue::Bool(crate::config::probestack_supported(target.architecture)),
370 "probestack_strategy" => *value == FlagValue::Enum("inline".into()),
371
372 // Features wasmtime doesn't use should all be disabled, since
373 // otherwise if they are enabled it could change the behavior of
374 // generated code.
375 "enable_llvm_abi_extensions" => *value == FlagValue::Bool(false),
376 "enable_pinned_reg" => *value == FlagValue::Bool(false),
377 "use_colocated_libcalls" => *value == FlagValue::Bool(false),
378 "use_pinned_reg_as_heap_base" => *value == FlagValue::Bool(false),
379
380 // If reference types are enabled this must be enabled, otherwise
381 // this setting can have any value.
382 "enable_safepoints" => {
383 if self.config().features.reference_types {
384 *value == FlagValue::Bool(true)
385 } else {
386 return Ok(())
387 }
388 }
389
390 // Windows requires unwind info as part of its ABI.
391 "unwind_info" => {
392 if target.operating_system == target_lexicon::OperatingSystem::Windows {
393 *value == FlagValue::Bool(true)
394 } else {
395 return Ok(())
396 }
397 }
398
399 // These settings don't affect the interface or functionality of
400 // the module itself, so their configuration values shouldn't
401 // matter.
402 "enable_heap_access_spectre_mitigation"
403 | "enable_table_access_spectre_mitigation"
404 | "enable_nan_canonicalization"
405 | "enable_jump_tables"
406 | "enable_float"
407 | "enable_simd"
408 | "enable_verifier"
409 | "regalloc_checker"
410 | "regalloc_verbose_logs"
411 | "is_pic"
412 | "machine_code_cfg_info"
413 | "tls_model" // wasmtime doesn't use tls right now
414 | "opt_level" // opt level doesn't change semantics
415 | "use_egraphs" // optimizing with egraphs doesn't change semantics
416 | "enable_alias_analysis" // alias analysis-based opts don't change semantics
417 | "probestack_func_adjusts_sp" // probestack above asserted disabled
418 | "probestack_size_log2" // probestack above asserted disabled
419 | "regalloc" // shouldn't change semantics
420 | "enable_incremental_compilation_cache_checks" // shouldn't change semantics
421 | "enable_atomics" => return Ok(()),
422
423 // Everything else is unknown and needs to be added somewhere to
424 // this list if encountered.
425 _ => {
426 return Err(format!("unknown shared setting {:?} configured to {:?}", flag, value))
427 }
428 };
429
430 if !ok {
431 return Err(format!(
432 "setting {:?} is configured to {:?} which is not supported",
433 flag, value,
434 ));
435 }
436 Ok(())
437 }
438
439 /// Same as `check_compatible_with_native_host` except used for ISA-specific
440 /// flags. This is used to test whether a configured ISA flag is indeed
441 /// available on the host platform itself.
442 pub(crate) fn check_compatible_with_isa_flag(
443 &self,
444 flag: &str,
445 value: &FlagValue,
446 ) -> Result<(), String> {
447 match value {
448 // ISA flags are used for things like CPU features, so if they're
449 // disabled then it's compatible with the native host.
450 FlagValue::Bool(false) => return Ok(()),
451
452 // Fall through below where we test at runtime that features are
453 // available.
454 FlagValue::Bool(true) => {}
455
456 // Only `bool` values are supported right now, other settings would
457 // need more support here.
458 _ => {
459 return Err(format!(
460 "isa-specific feature {:?} configured to unknown value {:?}",
461 flag, value
462 ))
463 }
464 }
465
466 #[allow(unused_assignments)]
467 let mut enabled = None;
468
469 #[cfg(target_arch = "aarch64")]
470 {
471 enabled = match flag {
472 "has_lse" => Some(std::arch::is_aarch64_feature_detected!("lse")),
473 // No effect on its own, but in order to simplify the code on a
474 // platform without pointer authentication support we fail if
475 // "has_pauth" is enabled, but "sign_return_address" is not.
476 "has_pauth" => Some(std::arch::is_aarch64_feature_detected!("paca")),
477 // No effect on its own.
478 "sign_return_address_all" => Some(true),
479 // The pointer authentication instructions act as a `NOP` when
480 // unsupported (but keep in mind "has_pauth" as well), so it is
481 // safe to enable them.
482 "sign_return_address" => Some(true),
483 // No effect on its own.
484 "sign_return_address_with_bkey" => Some(true),
485 // The `BTI` instruction acts as a `NOP` when unsupported, so it
486 // is safe to enable it.
487 "use_bti" => Some(true),
488 // fall through to the very bottom to indicate that support is
489 // not enabled to test whether this feature is enabled on the
490 // host.
491 _ => None,
492 };
493 }
494
495 // There is no is_s390x_feature_detected macro yet, so for now
496 // we use getauxval from the libc crate directly.
497 #[cfg(all(target_arch = "s390x", target_os = "linux"))]
498 {
499 let v = unsafe { libc::getauxval(libc::AT_HWCAP) };
500 const HWCAP_S390X_VXRS_EXT2: libc::c_ulong = 32768;
501
502 enabled = match flag {
503 // There is no separate HWCAP bit for mie2, so assume
504 // that any machine with vxrs_ext2 also has mie2.
505 "has_vxrs_ext2" | "has_mie2" => Some((v & HWCAP_S390X_VXRS_EXT2) != 0),
506 // fall through to the very bottom to indicate that support is
507 // not enabled to test whether this feature is enabled on the
508 // host.
509 _ => None,
510 }
511 }
512
513 #[cfg(target_arch = "riscv64")]
514 {
515 enabled = match flag {
516 // make sure `test_isa_flags_mismatch` test pass.
517 "not_a_flag" => None,
518 // due to `is_riscv64_feature_detected` is not stable.
519 // we cannot use it.
520 _ => Some(true),
521 }
522 }
523
524 #[cfg(target_arch = "x86_64")]
525 {
526 enabled = match flag {
527 "has_sse3" => Some(std::is_x86_feature_detected!("sse3")),
528 "has_ssse3" => Some(std::is_x86_feature_detected!("ssse3")),
529 "has_sse41" => Some(std::is_x86_feature_detected!("sse4.1")),
530 "has_sse42" => Some(std::is_x86_feature_detected!("sse4.2")),
531 "has_popcnt" => Some(std::is_x86_feature_detected!("popcnt")),
532 "has_avx" => Some(std::is_x86_feature_detected!("avx")),
533 "has_avx2" => Some(std::is_x86_feature_detected!("avx2")),
534 "has_fma" => Some(std::is_x86_feature_detected!("fma")),
535 "has_bmi1" => Some(std::is_x86_feature_detected!("bmi1")),
536 "has_bmi2" => Some(std::is_x86_feature_detected!("bmi2")),
537 "has_avx512bitalg" => Some(std::is_x86_feature_detected!("avx512bitalg")),
538 "has_avx512dq" => Some(std::is_x86_feature_detected!("avx512dq")),
539 "has_avx512f" => Some(std::is_x86_feature_detected!("avx512f")),
540 "has_avx512vl" => Some(std::is_x86_feature_detected!("avx512vl")),
541 "has_avx512vbmi" => Some(std::is_x86_feature_detected!("avx512vbmi")),
542 "has_lzcnt" => Some(std::is_x86_feature_detected!("lzcnt")),
543
544 // fall through to the very bottom to indicate that support is
545 // not enabled to test whether this feature is enabled on the
546 // host.
547 _ => None,
548 };
549 }
550
551 match enabled {
552 Some(true) => return Ok(()),
553 Some(false) => {
554 return Err(format!(
555 "compilation setting {:?} is enabled, but not available on the host",
556 flag
557 ))
558 }
559 // fall through
560 None => {}
561 }
562
563 Err(format!(
564 "cannot test if target-specific flag {:?} is available at runtime",
565 flag
566 ))
567 }
568
569 #[cfg(compiler)]
570 pub(crate) fn append_compiler_info(&self, obj: &mut Object<'_>) {
571 serialization::append_compiler_info(self, obj);
572 }
573
574 #[cfg(compiler)]
575 pub(crate) fn append_bti(&self, obj: &mut Object<'_>) {
576 let section = obj.add_section(
577 obj.segment_name(StandardSegment::Data).to_vec(),
578 obj::ELF_WASM_BTI.as_bytes().to_vec(),
579 SectionKind::ReadOnlyData,
580 );
581 let contents = if self.compiler().is_branch_protection_enabled() {
582 1
583 } else {
584 0
585 };
586 obj.append_section_data(section, &[contents], 1);
587 }
588
589 /// Loads a `CodeMemory` from the specified in-memory slice, copying it to a
590 /// uniquely owned mmap.
591 ///
592 /// The `expected` marker here is whether the bytes are expected to be a
593 /// precompiled module or a component.
594 pub(crate) fn load_code_bytes(
595 &self,
596 bytes: &[u8],
597 expected: ObjectKind,
598 ) -> Result<Arc<CodeMemory>> {
599 self.load_code(MmapVec::from_slice(bytes)?, expected)
600 }
601
602 /// Like `load_code_bytes`, but creates a mmap from a file on disk.
603 pub(crate) fn load_code_file(
604 &self,
605 path: &Path,
606 expected: ObjectKind,
607 ) -> Result<Arc<CodeMemory>> {
608 self.load_code(
609 MmapVec::from_file(path).with_context(|| {
610 format!("failed to create file mapping for: {}", path.display())
611 })?,
612 expected,
613 )
614 }
615
616 pub(crate) fn load_code(&self, mmap: MmapVec, expected: ObjectKind) -> Result<Arc<CodeMemory>> {
617 serialization::check_compatible(self, &mmap, expected)?;
618 let mut code = CodeMemory::new(mmap)?;
619 code.publish()?;
620 Ok(Arc::new(code))
621 }
622}
623
624impl Default for Engine {
625 fn default() -> Engine {
626 Engine::new(&Config::default()).unwrap()
627 }
628}
629
630#[cfg(test)]
631mod tests {
632 use crate::{Config, Engine, Module, OptLevel};
633
634 use anyhow::Result;
635 use tempfile::TempDir;
636
637 #[test]
638 fn cache_accounts_for_opt_level() -> Result<()> {
639 let td = TempDir::new()?;
640 let config_path = td.path().join("config.toml");
641 std::fs::write(
642 &config_path,
643 &format!(
644 "
645 [cache]
646 enabled = true
647 directory = '{}'
648 ",
649 td.path().join("cache").display()
650 ),
651 )?;
652 let mut cfg = Config::new();
653 cfg.cranelift_opt_level(OptLevel::None)
654 .cache_config_load(&config_path)?;
655 let engine = Engine::new(&cfg)?;
656 Module::new(&engine, "(module (func))")?;
657 assert_eq!(engine.config().cache_config.cache_hits(), 0);
658 assert_eq!(engine.config().cache_config.cache_misses(), 1);
659 Module::new(&engine, "(module (func))")?;
660 assert_eq!(engine.config().cache_config.cache_hits(), 1);
661 assert_eq!(engine.config().cache_config.cache_misses(), 1);
662
663 let mut cfg = Config::new();
664 cfg.cranelift_opt_level(OptLevel::Speed)
665 .cache_config_load(&config_path)?;
666 let engine = Engine::new(&cfg)?;
667 Module::new(&engine, "(module (func))")?;
668 assert_eq!(engine.config().cache_config.cache_hits(), 0);
669 assert_eq!(engine.config().cache_config.cache_misses(), 1);
670 Module::new(&engine, "(module (func))")?;
671 assert_eq!(engine.config().cache_config.cache_hits(), 1);
672 assert_eq!(engine.config().cache_config.cache_misses(), 1);
673
674 let mut cfg = Config::new();
675 cfg.cranelift_opt_level(OptLevel::SpeedAndSize)
676 .cache_config_load(&config_path)?;
677 let engine = Engine::new(&cfg)?;
678 Module::new(&engine, "(module (func))")?;
679 assert_eq!(engine.config().cache_config.cache_hits(), 0);
680 assert_eq!(engine.config().cache_config.cache_misses(), 1);
681 Module::new(&engine, "(module (func))")?;
682 assert_eq!(engine.config().cache_config.cache_hits(), 1);
683 assert_eq!(engine.config().cache_config.cache_misses(), 1);
684
685 let mut cfg = Config::new();
686 cfg.debug_info(true).cache_config_load(&config_path)?;
687 let engine = Engine::new(&cfg)?;
688 Module::new(&engine, "(module (func))")?;
689 assert_eq!(engine.config().cache_config.cache_hits(), 0);
690 assert_eq!(engine.config().cache_config.cache_misses(), 1);
691 Module::new(&engine, "(module (func))")?;
692 assert_eq!(engine.config().cache_config.cache_hits(), 1);
693 assert_eq!(engine.config().cache_config.cache_misses(), 1);
694
695 Ok(())
696 }
697}