Skip to main content

viceroy_lib/
execute.rs

1//! Guest code execution.
2
3use {
4    crate::{
5        Error,
6        acl::Acls,
7        adapt,
8        body::Body,
9        body_tee::tee,
10        cache::Cache,
11        component as compute,
12        config::{
13            Backends, DeviceDetection, Dictionaries, ExperimentalModule, FakeValidFastlyKeys,
14            Geolocation, UnknownImportBehavior,
15        },
16        downstream::{DownstreamMetadata, DownstreamRequest, DownstreamResponse, prepare_request},
17        error::{ExecutionError, NonHttpResponse},
18        handoff::{HandoffConfig, HandoffRequestInfo, HandoffTlsConfig, perform_handoff},
19        http::framing::apply_response_framing,
20        linking::{ComponentCtx, WasmCtx, create_store, link_host_functions},
21        object_store::ObjectStores,
22        sandbox::Sandbox,
23        secret_store::SecretStores,
24        shielding_site::ShieldingSites,
25        upstream::TlsConfig,
26    },
27    futures::{
28        Future,
29        task::{Context, Poll},
30    },
31    http::StatusCode,
32    hyper::{Request, Response},
33    pin_project::pin_project,
34    std::{
35        collections::HashSet,
36        fmt, fs,
37        io::Write,
38        net::{Ipv4Addr, SocketAddr},
39        path::{Path, PathBuf},
40        pin::Pin,
41        sync::{
42            Arc, Mutex,
43            atomic::{AtomicBool, AtomicU64, Ordering},
44        },
45        thread::{self, JoinHandle},
46        time::{Duration, Instant, SystemTime},
47    },
48    tokio::sync::Mutex as AsyncMutex,
49    tokio::sync::mpsc,
50    tokio::sync::oneshot::{self, Sender},
51    tracing::{Instrument, Level, error, event, info, info_span},
52    wasmtime::{
53        Engine, GuestProfiler, InstancePre, Linker, Module, ProfilingStrategy,
54        component::{self, Component},
55    },
56    wasmtime_wasi::I32Exit,
57};
58
59pub use wasmtime::WasmFeatures;
60
61pub const DEFAULT_EPOCH_INTERRUPTION_PERIOD: Duration = Duration::from_micros(50);
62
63const NEXT_REQ_PENDING_MAX: usize = 5;
64const REGION_NONE: &str = "none";
65
66enum Instance {
67    Module(Module, InstancePre<WasmCtx>),
68    Component(
69        Component,
70        compute::bindings::AdapterServicePre<ComponentCtx>,
71    ),
72}
73
74impl Instance {
75    fn unwrap_module(&self) -> (&Module, &InstancePre<WasmCtx>) {
76        match self {
77            Instance::Module(m, i) => (m, i),
78            Instance::Component(_, _) => panic!("unwrap_module called on a component"),
79        }
80    }
81}
82
83/// Configuration for profiling Wasm execution.
84///
85/// Viceroy supports two types of profiling:
86/// - **Guest profiling**: Profiles the WebAssembly guest code itself, producing Firefox profiler compatible JSON.
87/// - **Native profiling**: Profiles wasmtime's JIT compiler for debugging across guest, viceroy and wasmtime.
88#[derive(Clone)]
89pub enum ProfilingConfig {
90    /// No profiling enabled
91    None,
92    /// Profile the WebAssembly guest code
93    Guest {
94        /// Path to write profiling results. In serve mode, this should be a directory
95        /// where per-request profiles are written. In run mode, this is a single file.
96        path: PathBuf,
97        /// Period at which the guest should be sampled (default: 50μs)
98        sample_period: Duration,
99    },
100    /// Profile wasmtime's JIT compiler using native profiling tools
101    Native(wasmtime::ProfilingStrategy),
102}
103
104impl ProfilingConfig {
105    /// Get the native profiling strategy for wasmtime
106    pub fn native_strategy(&self) -> wasmtime::ProfilingStrategy {
107        match self {
108            ProfilingConfig::Native(strategy) => *strategy,
109            _ => wasmtime::ProfilingStrategy::None,
110        }
111    }
112
113    /// Get the guest profile configuration if guest profiling is enabled
114    fn guest_config(&self) -> Option<GuestProfileConfig> {
115        match self {
116            ProfilingConfig::Guest {
117                path,
118                sample_period,
119            } => Some(GuestProfileConfig {
120                path: path.clone(),
121                sample_period: *sample_period,
122            }),
123            _ => None,
124        }
125    }
126}
127
128// Keep GuestProfileConfig internal for now to maintain backwards compatibility
129#[derive(Clone)]
130struct GuestProfileConfig {
131    path: PathBuf,
132    sample_period: Duration,
133}
134
135pub struct NextRequest(Option<(Box<DownstreamRequest>, Arc<ExecuteCtx>)>);
136
137impl NextRequest {
138    pub fn into_request(mut self) -> Option<DownstreamRequest> {
139        self.0.take().map(|(r, _)| *r)
140    }
141}
142
143impl fmt::Debug for NextRequest {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        let debug = self.0.as_ref().map(|(r, _)| r);
146        f.debug_tuple("NextRequest")
147            .field(&debug)
148            .finish_non_exhaustive()
149    }
150}
151
152impl Drop for NextRequest {
153    fn drop(&mut self) {
154        let Some((req, ctx)) = self.0.take() else {
155            return;
156        };
157
158        ctx.retry_request(*req);
159    }
160}
161
162/// Execution context used by a [`ViceroyService`](struct.ViceroyService.html).
163///
164/// This is all of the state needed to instantiate a module, in order to respond to an HTTP
165/// request. Note that it is very important that `ExecuteCtx` be cheaply clonable, as it is cloned
166/// every time that a viceroy service handles an incoming connection.
167pub struct ExecuteCtx {
168    /// A reference to the global context for Wasm compilation.
169    engine: Engine,
170    /// An almost-linked Instance: each import function is linked, just needs a Store
171    instance_pre: Arc<Instance>,
172    /// Preloaded TLS certificates and configuration
173    tls_config: TlsConfig,
174    /// The ID to assign the next incoming request
175    next_req_id: Arc<AtomicU64>,
176    /// The cache for this service.
177    cache: Arc<Cache>,
178    /// Senders waiting for new requests for reusable sandboxes.
179    pending_reuse: Arc<AsyncMutex<Vec<Sender<NextRequest>>>>,
180    epoch_increment_thread: Option<JoinHandle<()>>,
181    // `Arc` so that it can be tracked both by this context and `epoch_increment_thread`.
182    epoch_increment_stop: Arc<AtomicBool>,
183    /// Configuration for guest profiling if enabled
184    guest_profile_config: Option<Arc<GuestProfileConfig>>,
185    /// The settings supplied through [`ExecuteCtxBuilder`].
186    settings: Settings,
187}
188
189/// The parts of an [`ExecuteCtx`] that can be configured through [`ExecuteCtxBuilder`].
190///
191/// These are held separately from the rest of the context so that the builder can collect them
192/// before the Wasm engine has been created: building the engine is deferred to
193/// [`ExecuteCtxBuilder::finish`], so that engine-affecting settings can be adjusted first.
194struct Settings {
195    /// The acls for this execution.
196    acls: Acls,
197    /// The backends for this execution.
198    backends: Backends,
199    /// The device detection mappings for this execution.
200    device_detection: DeviceDetection,
201    /// The geolocation mappings for this execution.
202    geolocation: Geolocation,
203    /// The dictionaries for this execution.
204    dictionaries: Dictionaries,
205    /// Path to the config, defaults to None
206    config_path: Option<PathBuf>,
207    /// Where to direct logging endpoint messages, defaults to stdout
208    capture_logs: Arc<Mutex<dyn Write + Send>>,
209    /// Whether to treat stdout as a logging endpoint
210    log_stdout: bool,
211    /// Whether to treat stderr as a logging endpoint
212    log_stderr: bool,
213    /// The local Pushpin proxy port
214    local_pushpin_proxy_port: Option<u16>,
215    /// Whether WebSocket passthrough is enabled for this service.
216    ///
217    /// Set this to `false` to simulate a service that does not have the WebSockets feature
218    /// enabled, so that guests can exercise their handling of that state locally.
219    enable_local_websocket_passthrough: bool,
220    /// The ObjectStore associated with this instance of Viceroy
221    object_store: ObjectStores,
222    /// The secret stores for this execution.
223    secret_stores: SecretStores,
224    /// The shielding sites for this execution.
225    shielding_sites: ShieldingSites,
226    /// The valid mock Fastly API keys that should be considered valid.
227    fake_valid_fastly_keys: FakeValidFastlyKeys,
228}
229
230impl Default for Settings {
231    fn default() -> Self {
232        Self {
233            acls: Acls::new(),
234            backends: Backends::default(),
235            device_detection: DeviceDetection::default(),
236            geolocation: Geolocation::default(),
237            dictionaries: Dictionaries::default(),
238            config_path: None,
239            capture_logs: Arc::new(Mutex::new(std::io::stdout())),
240            log_stdout: false,
241            log_stderr: false,
242            local_pushpin_proxy_port: None,
243            enable_local_websocket_passthrough: true,
244            object_store: ObjectStores::new(),
245            secret_stores: SecretStores::new(),
246            shielding_sites: ShieldingSites::new(),
247            fake_valid_fastly_keys: FakeValidFastlyKeys::new(),
248        }
249    }
250}
251
252impl ExecuteCtx {
253    /// Build a new execution context with unified profiling configuration.
254    ///
255    /// Note that the module is not read, compiled, or linked until
256    /// [`ExecuteCtxBuilder::finish`] is called, so errors relating to the module itself are
257    /// reported from there rather than here.
258    pub fn build(
259        module_path: impl AsRef<Path>,
260        profiling: ProfilingConfig,
261        wasi_modules: HashSet<ExperimentalModule>,
262        unknown_import_behavior: UnknownImportBehavior,
263        adapt_components: bool,
264        wasm_features: WasmFeatures,
265    ) -> Result<ExecuteCtxBuilder, Error> {
266        Ok(ExecuteCtxBuilder {
267            module_path: module_path.as_ref().to_owned(),
268            profiling,
269            wasi_modules,
270            unknown_import_behavior,
271            adapt_components,
272            wasm_features,
273            debug_info: false,
274            settings: Settings::default(),
275        })
276    }
277
278    /// Create a new execution context with unified profiling configuration.
279    ///
280    /// This is a convenience wrapper around `build().finish()`.
281    pub fn new(
282        module_path: impl AsRef<Path>,
283        profiling: ProfilingConfig,
284        wasi_modules: HashSet<ExperimentalModule>,
285        unknown_import_behavior: UnknownImportBehavior,
286        adapt_components: bool,
287        wasm_features: WasmFeatures,
288    ) -> Result<Arc<Self>, Error> {
289        ExecuteCtx::build(
290            module_path,
291            profiling,
292            wasi_modules,
293            unknown_import_behavior,
294            adapt_components,
295            wasm_features,
296        )?
297        .finish()
298    }
299
300    /// Get the engine for this execution context.
301    pub fn engine(&self) -> &Engine {
302        &self.engine
303    }
304
305    /// Get the acls for this execution context.
306    pub fn acls(&self) -> &Acls {
307        &self.settings.acls
308    }
309
310    /// Get the backends for this execution context.
311    pub fn backends(&self) -> &Backends {
312        &self.settings.backends
313    }
314
315    /// Get the device detection mappings for this execution context.
316    pub fn device_detection(&self) -> &DeviceDetection {
317        &self.settings.device_detection
318    }
319
320    /// Get the geolocation mappings for this execution context.
321    pub fn geolocation(&self) -> &Geolocation {
322        &self.settings.geolocation
323    }
324
325    /// Get the dictionaries for this execution context.
326    pub fn dictionaries(&self) -> &Dictionaries {
327        &self.settings.dictionaries
328    }
329
330    /// Where to direct logging endpoint messages. Defaults to stdout.
331    pub fn capture_logs(&self) -> Arc<Mutex<dyn Write + Send>> {
332        self.settings.capture_logs.clone()
333    }
334
335    /// Whether to treat stdout as a logging endpoint.
336    pub fn log_stdout(&self) -> bool {
337        self.settings.log_stdout
338    }
339
340    /// Whether to treat stderr as a logging endpoint.
341    pub fn log_stderr(&self) -> bool {
342        self.settings.log_stderr
343    }
344
345    /// Gets the TLS configuration
346    pub fn tls_config(&self) -> &TlsConfig {
347        &self.tls_config
348    }
349
350    async fn maybe_receive_response(
351        mut receiver: mpsc::Receiver<DownstreamResponse>,
352    ) -> Option<(Response<Body>, Option<anyhow::Error>)> {
353        loop {
354            match receiver.recv().await? {
355                DownstreamResponse::Http(mut resp) => {
356                    apply_response_framing(&mut resp);
357
358                    // Supporting 103 Early Hints responses is currently infeasible, as Hyper does
359                    // not support sending multiple responses on a single connection. But we don't
360                    // want to generate errors for them either. Early Hints will be dropped, but
361                    // logged so that people will know they *did work*, even though they won't
362                    // reach the client.
363                    if resp.status().is_informational() {
364                        // We'll do these at different log levels in case someone wants to squelch some.
365                        tracing::warn!(
366                            "Guest returned informational response ({}) which will not be sent to the client",
367                            resp.status(),
368                        );
369                        tracing::info!("{:#?}", resp);
370                        continue;
371                    }
372
373                    return Some((resp, None));
374                }
375                DownstreamResponse::Pending(pending) => {
376                    let mut resp = pending
377                        .recv_or_else(|e| {
378                            let status = e.as_status_code();
379                            let err = anyhow::Error::from(e);
380                            anyhow_response_with_status(&err, status)
381                        })
382                        .await;
383
384                    apply_response_framing(&mut resp);
385
386                    return Some((resp, None));
387                }
388                DownstreamResponse::HandoffToPushpin(info) => {
389                    let err = NonHttpResponse::HandoffToPushpin(info).into();
390                    let resp = Response::new(Body::empty());
391                    return Some((resp, Some(err)));
392                }
393                DownstreamResponse::HandoffToBackend(info) => {
394                    let resp = Response::new(Body::empty());
395                    let err = NonHttpResponse::HandoffToBackend(info).into();
396                    return Some((resp, Some(err)));
397                }
398            }
399        }
400    }
401
402    /// Asynchronously handle a request.
403    ///
404    /// This method fully instantiates the wasm module housed within the `ExecuteCtx`,
405    /// including running the wasm start function. It then proceeds to execute the
406    /// instantiated module's WASI entry point, running to completion. If execution
407    /// results in an error, a response is still produced, but with a 500 status code.
408    ///
409    /// Build time: Before you build or test your code, we recommend to set the release flag
410    /// e.g. `cargo test --release` otherwise the execution will be very slow. This has to do
411    /// with the Cranelift compiler, which is extremely slow when compiled in debug mode.
412    ///
413    /// # Example
414    ///
415    /// ```no_run
416    /// # use std::collections::HashSet;
417    /// # use wasmtime::WasmFeatures;
418    /// use hyper::{Body, http::Request};
419    /// # use viceroy_lib::{Error, ExecuteCtx, ProfilingConfig, ViceroyService};
420    /// # use viceroy_lib::config::UnknownImportBehavior;
421    /// # async fn f() -> Result<(), Error> {
422    /// # let req = Request::new(Body::from(""));
423    /// let adapt_core_wasm = false;
424    /// let wasm_features = WasmFeatures::default();
425    /// let ctx = ExecuteCtx::new(
426    ///     "path/to/a/file.wasm",
427    ///     ProfilingConfig::None,
428    ///     HashSet::new(),
429    ///     UnknownImportBehavior::LinkError,
430    ///     adapt_core_wasm,
431    ///     wasm_features,
432    /// )?;
433    /// let local = "127.0.0.1:80".parse().unwrap();
434    /// let remote = "127.0.0.1:0".parse().unwrap();
435    /// let resp = ctx.handle_request(req, local, remote).await?;
436    /// # Ok(())
437    /// # }
438    /// ```
439    pub async fn handle_request(
440        self: Arc<Self>,
441        mut incoming_req: Request<hyper::Body>,
442        local: SocketAddr,
443        remote: SocketAddr,
444    ) -> Result<(Response<Body>, Option<anyhow::Error>), Error> {
445        let orig_req_on_upgrade = hyper::upgrade::on(&mut incoming_req);
446        let (incoming_req_parts, incoming_req_body) = incoming_req.into_parts();
447        let local_pushpin_proxy_port = self.settings.local_pushpin_proxy_port;
448
449        let (body_for_wasm, orig_body_tee) = tee(incoming_req_body).await;
450        let orig_request_info_for_pushpin = HandoffRequestInfo::from_parts(&incoming_req_parts);
451
452        let original_headers = incoming_req_parts.headers.clone();
453        let req = prepare_request(Request::from_parts(incoming_req_parts, body_for_wasm))?;
454
455        let req_id = self
456            .next_req_id
457            .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
458
459        let metadata = DownstreamMetadata {
460            req_id,
461            server_addr: local,
462            client_addr: remote,
463            compliance_region: String::from(REGION_NONE),
464            original_headers,
465        };
466
467        let backends = self.settings.backends.clone();
468        let tls_config = self.tls_config.clone();
469
470        let (resp, mut err) = self.reuse_or_spawn_guest(req, metadata).await;
471
472        let span = info_span!("request", id = req_id);
473        let _span = span.enter();
474
475        info!("response status: {:?}", resp.status());
476
477        if let Some(e) = err {
478            match e.downcast::<NonHttpResponse>() {
479                Ok(NonHttpResponse::HandoffToPushpin(handoff_info)) => {
480                    let backend_name = handoff_info.backend_name.clone();
481
482                    info!("Pushpin handoff signaled to backend '{backend_name}'");
483
484                    // Defensive only: `Sandbox::redirect_downstream_to_pushpin` is the sole
485                    // producer of this handoff, and it now rejects the call with an
486                    // `unsupported` error before signaling when no proxy port is configured.
487                    let local_pushpin_proxy_port = match local_pushpin_proxy_port {
488                        None => {
489                            error!("Pushpin handoff signaled, but Pushpin mode not enabled.");
490                            let mut resp = Response::new(Body::from(hyper::Body::from(
491                                "Pushpin handoff signaled, but Pushpin mode not enabled.",
492                            )));
493                            *resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
494                            return Ok((resp, None));
495                        }
496                        Some(port) => port,
497                    };
498
499                    let pushpin_addr =
500                        SocketAddr::new(Ipv4Addr::LOCALHOST.into(), local_pushpin_proxy_port)
501                            .to_string();
502
503                    // To distinguish backends, the HTTP request header `pushpin-route: <backend_name>` is added
504                    // to the request. Pushpin routes should be configured with `id=<backend_name>`.
505                    let additional_headers =
506                        vec![("pushpin-route".to_string(), backend_name.clone())];
507
508                    let handoff_resp = perform_handoff(
509                        handoff_info.request_info,
510                        orig_request_info_for_pushpin,
511                        orig_body_tee,
512                        orig_req_on_upgrade,
513                        HandoffConfig {
514                            target_addr: pushpin_addr.clone(),
515                            host_header: pushpin_addr, // Host header is the local proxy address
516                            display_name: format!("Pushpin [{backend_name}]"),
517                            path_prefix: None, // Path prefix is applied by Pushpin route
518                            extra_headers: additional_headers,
519                            tls_config: None, // Pushpin only runs locally, without https
520                        },
521                    )
522                    .await;
523
524                    let (p, hyper_body) = handoff_resp.into_parts();
525                    return Ok((Response::from_parts(p, Body::from(hyper_body)), None));
526                }
527                Ok(NonHttpResponse::HandoffToBackend(handoff_info)) => {
528                    let backend_name = handoff_info.backend_name.clone();
529
530                    info!("Backend handoff signaled to backend '{backend_name}'");
531
532                    let backend = backends.get(backend_name.as_str());
533                    let backend = match backend {
534                        None => {
535                            error!("Backend handoff signaled to unknown backend '{backend_name}'.");
536                            let mut resp = Response::new(Body::from(hyper::Body::from(format!(
537                                "Backend handoff signaled to unknown backend '{backend_name}'."
538                            ))));
539                            *resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
540                            return Ok((resp, None));
541                        }
542                        Some(backend) => backend,
543                    };
544
545                    let backend_uri = backend.uri.clone();
546                    let tls_handoff_config =
547                        if matches!(backend_uri.scheme_str(), Some("https" | "wss")) {
548                            Some(HandoffTlsConfig {
549                                ca_certs: backend.ca_certs.clone(),
550                                client_cert: backend.client_cert.clone(),
551                                use_sni: backend.use_sni,
552                                cert_host: backend.cert_host.clone(),
553                                dns_name_fallback: backend
554                                    .uri
555                                    .host()
556                                    .unwrap_or_default()
557                                    .to_string(),
558                                is_grpc: backend.grpc,
559                                base_tls_config: tls_config.clone(), // Pass the builder from self
560                            })
561                        } else {
562                            None
563                        };
564
565                    let backend_host = backend_uri
566                        .authority()
567                        .map(|a| match (a.port(), backend_uri.scheme_str()) {
568                            (None, Some("wss")) => format!("{}:443", a),
569                            (None, Some("https")) => format!("{}:443", a),
570                            (None, Some("ws")) => format!("{}:80", a),
571                            (None, Some("http")) => format!("{}:80", a),
572                            _ => a.to_string(), // already has a port, or scheme is unknown (unlikely)
573                        })
574                        .unwrap_or_default();
575                    // Use override_host if present, otherwise fallback to the URI's authority
576                    let host_header = backend
577                        .override_host
578                        .clone()
579                        .map(|host| host.to_str()
580                            .expect("`backend.override_host`, if provided, should be a valid header value")
581                            .to_string())
582                        .unwrap_or_else(|| backend_host.clone());
583
584                    // Prepend a path if there's actually a path in the backend URI
585                    let path_prefix = (!backend_uri.path().is_empty() && backend_uri.path() != "/")
586                        .then(|| backend_uri.path().to_string());
587
588                    let handoff_resp = perform_handoff(
589                        handoff_info.request_info,
590                        orig_request_info_for_pushpin,
591                        orig_body_tee,
592                        orig_req_on_upgrade,
593                        HandoffConfig {
594                            target_addr: backend_host,
595                            host_header,
596                            display_name: format!("Backend [{backend_name}]"),
597                            path_prefix,
598                            extra_headers: vec![], // Standard backends don't need extra headers
599                            tls_config: tls_handoff_config,
600                        },
601                    )
602                    .await;
603
604                    let (p, hyper_body) = handoff_resp.into_parts();
605                    return Ok((Response::from_parts(p, Body::from(hyper_body)), None));
606                }
607                Err(e) => {
608                    err = Some(e);
609                }
610            }
611        }
612
613        Ok((resp, err))
614    }
615
616    /// Spawn a new guest to process a request whose processing was never attempted by
617    /// a reused sandbox.
618    pub(crate) fn retry_request(self: Arc<Self>, mut downstream: DownstreamRequest) {
619        if downstream.sender.is_closed() {
620            return;
621        }
622
623        tokio::task::spawn(async move {
624            let (sender, receiver) = mpsc::channel(10);
625            let original = std::mem::replace(&mut downstream.sender, sender);
626            let (resp, err) = self.spawn_guest(downstream, receiver).await;
627            let resp = guest_result_to_response(resp, err);
628            let _ = original.send(DownstreamResponse::Http(resp)).await;
629        });
630    }
631
632    pub async fn handle_request_with_runtime_error(
633        self: Arc<Self>,
634        incoming_req: Request<hyper::Body>,
635        local: SocketAddr,
636        remote: SocketAddr,
637    ) -> Result<Response<Body>, Error> {
638        let result = self.handle_request(incoming_req, local, remote).await?;
639        let resp = guest_result_to_response(result.0, result.1);
640
641        Ok(resp)
642    }
643
644    async fn reuse_or_spawn_guest(
645        self: Arc<Self>,
646        req: Request<Body>,
647        metadata: DownstreamMetadata,
648    ) -> (Response<Body>, Option<anyhow::Error>) {
649        let (downstream, receiver) = DownstreamRequest::new(req, metadata);
650
651        let mut next_req = NextRequest(Some((Box::new(downstream), self.clone())));
652        let mut reusable = self.pending_reuse.lock().await;
653
654        while let Some(pending) = reusable.pop() {
655            match pending.send(next_req) {
656                Ok(()) => {
657                    // Drop lock and wait for the guest to process our request.
658                    drop(reusable);
659
660                    if let Some(response) = Self::maybe_receive_response(receiver).await {
661                        return response;
662                    }
663                    return (Response::default(), None);
664                }
665                Err(nr) => next_req = nr,
666            }
667        }
668
669        drop(reusable);
670
671        let downstream = next_req
672            .into_request()
673            .expect("request should still be unprocessed");
674        self.spawn_guest(downstream, receiver).await
675    }
676
677    async fn spawn_guest(
678        self: Arc<Self>,
679        downstream: DownstreamRequest,
680        receiver: mpsc::Receiver<DownstreamResponse>,
681    ) -> (Response<Body>, Option<anyhow::Error>) {
682        let active_cpu_time_us = Arc::new(AtomicU64::new(0));
683
684        // Spawn a separate task to run the guest code. That allows _this_ method to return a response early
685        // if the guest sends one, while the guest continues to run afterward within its task.
686        let req_id = downstream.metadata.req_id;
687        let guest_handle = tokio::task::spawn(CpuTimeTracking::new(
688            active_cpu_time_us.clone(),
689            self.run_guest(downstream, active_cpu_time_us)
690                .instrument(info_span!("request", id = req_id)),
691        ));
692
693        if let Some(response) = Self::maybe_receive_response(receiver).await {
694            return response;
695        }
696
697        match guest_handle
698            .await
699            .expect("guest worker finished without panicking")
700        {
701            Ok(_) => (Response::new(Body::empty()), None),
702            Err(ExecutionError::WasmTrap(e)) => {
703                event!(
704                    Level::ERROR,
705                    "There was an error handling the request {}",
706                    e.to_string()
707                );
708                (anyhow_response(&e), Some(e))
709            }
710            Err(e) => panic!("failed to run guest: {}", e),
711        }
712    }
713
714    async fn run_guest(
715        self: Arc<Self>,
716        downstream: DownstreamRequest,
717        active_cpu_time_us: Arc<AtomicU64>,
718    ) -> Result<(), ExecutionError> {
719        info!(
720            "handling request {} {}",
721            downstream.req.method(),
722            downstream.req.uri()
723        );
724        let start_timestamp = Instant::now();
725        let req_id = downstream.metadata.req_id;
726        let sandbox = Sandbox::new(downstream, active_cpu_time_us, self.clone());
727
728        let guest_profile_path = self.guest_profile_config.as_deref().map(|pcfg| {
729            let now = SystemTime::now()
730                .duration_since(SystemTime::UNIX_EPOCH)
731                .unwrap()
732                .as_secs();
733            pcfg.path.join(format!("{}-{}.json", now, req_id))
734        });
735
736        match self.instance_pre.as_ref() {
737            Instance::Component(component, instance_pre) => {
738                let profiler = self.guest_profile_config.as_deref().map(|pcfg| {
739                    let program_name = "main";
740                    GuestProfiler::new_component(
741                        program_name,
742                        pcfg.sample_period,
743                        component.clone(),
744                        std::iter::empty(),
745                    )
746                });
747
748                let req = sandbox.downstream_request();
749                let body = sandbox.downstream_request_body();
750
751                let mut store = ComponentCtx::create_store(&self, sandbox, profiler, |ctx| {
752                    ctx.arg("compute-app");
753                })
754                .map_err(ExecutionError::Context)?;
755
756                let compute = instance_pre
757                    .instantiate_async(&mut store)
758                    .await
759                    .map_err(ExecutionError::Instantiation)?;
760
761                let result = compute
762                    .fastly_compute_http_incoming()
763                    .call_handle(&mut store, req.into(), body.into())
764                    .await;
765
766                let outcome = match result {
767                    Ok(Ok(())) => Ok(()),
768
769                    Ok(Err(())) => {
770                        event!(Level::ERROR, "WebAssembly exited with an error");
771                        Err(ExecutionError::WasmTrap(anyhow::Error::msg("failed")))
772                    }
773
774                    Err(e) => {
775                        if let Some(exit) = e.downcast_ref::<I32Exit>() {
776                            if exit.0 == 0 {
777                                Ok(())
778                            } else {
779                                event!(Level::ERROR, "WebAssembly exited with error: {:?}", e);
780                                Err(ExecutionError::WasmTrap(e))
781                            }
782                        } else {
783                            event!(Level::ERROR, "WebAssembly trapped: {:?}", e);
784                            Err(ExecutionError::WasmTrap(e))
785                        }
786                    }
787                };
788
789                // If we collected a profile, write it to the file
790                write_profile_component(&mut store, guest_profile_path.as_ref());
791
792                // Ensure the downstream response channel is closed, whether or not a response was
793                // sent during execution.
794                let resp = outcome
795                    .as_ref()
796                    .err()
797                    .map(exec_err_to_response)
798                    .unwrap_or_default();
799                store
800                    .data_mut()
801                    .sandbox
802                    .close_downstream_response_sender(resp);
803
804                let request_duration = Instant::now().duration_since(start_timestamp);
805
806                info!(
807                    "guest completed using {} of WebAssembly heap",
808                    bytesize::ByteSize::b(store.data().limiter().memory_allocated as u64),
809                );
810
811                info!("guest completed in {:.0?}", request_duration);
812
813                outcome
814            }
815
816            Instance::Module(module, instance_pre) => {
817                let profiler = self.guest_profile_config.as_deref().map(|pcfg| {
818                    let program_name = "main";
819                    GuestProfiler::new(
820                        program_name,
821                        pcfg.sample_period,
822                        vec![(program_name.to_string(), module.clone())],
823                    )
824                });
825
826                // We currently have to postpone linking and instantiation to the guest task
827                // due to wasmtime limitations, in particular the fact that `Instance` is not `Send`.
828                // However, the fact that the module itself is created within `ExecuteCtx::new`
829                // means that the heavy lifting happens only once.
830                let mut store = create_store(&self, sandbox, profiler, |ctx| {
831                    ctx.arg("compute-app");
832                })
833                .map_err(ExecutionError::Context)?;
834
835                let instance = instance_pre
836                    .instantiate_async(&mut store)
837                    .await
838                    .map_err(ExecutionError::Instantiation)?;
839
840                // Pull out the `_start` function, which by convention with WASI is the main entry point for
841                // an application.
842                let main_func = instance
843                    .get_typed_func::<(), ()>(&mut store, "_start")
844                    .map_err(ExecutionError::Typechecking)?;
845
846                // Invoke the entrypoint function, which may or may not send a downstream response.
847                let outcome = match main_func.call_async(&mut store, ()).await {
848                    Ok(_) => Ok(()),
849                    Err(e) => {
850                        if let Some(exit) = e.downcast_ref::<I32Exit>() {
851                            if exit.0 == 0 {
852                                Ok(())
853                            } else {
854                                event!(Level::ERROR, "WebAssembly exited with error: {:?}", e);
855                                Err(ExecutionError::WasmTrap(e))
856                            }
857                        } else {
858                            event!(Level::ERROR, "WebAssembly trapped: {:?}", e);
859                            Err(ExecutionError::WasmTrap(e))
860                        }
861                    }
862                };
863
864                // If we collected a profile, write it to the file
865                write_profile(&mut store, guest_profile_path.as_ref());
866
867                // Ensure the downstream response channel is closed, whether or not a response was
868                // sent during execution.
869                let resp = outcome
870                    .as_ref()
871                    .err()
872                    .map(exec_err_to_response)
873                    .unwrap_or_default();
874                store.data_mut().close_downstream_response_sender(resp);
875
876                let request_duration = Instant::now().duration_since(start_timestamp);
877
878                info!(
879                    "request completed using {} of WebAssembly heap",
880                    bytesize::ByteSize::b(store.data().limiter().memory_allocated as u64)
881                );
882
883                info!("request completed in {:.0?}", request_duration);
884
885                outcome
886            }
887        }
888    }
889
890    pub async fn run_main(
891        self: Arc<Self>,
892        program_name: &str,
893        args: &[String],
894    ) -> Result<(), anyhow::Error> {
895        // placeholders for request, result sender channel, and remote IP
896        let req = Request::get("http://example.com/").body(Body::empty())?;
897        let metadata = DownstreamMetadata {
898            req_id: 0,
899            server_addr: (Ipv4Addr::LOCALHOST, 80).into(),
900            client_addr: (Ipv4Addr::LOCALHOST, 0).into(),
901            compliance_region: String::from(REGION_NONE),
902            original_headers: Default::default(),
903        };
904        let (downstream, receiver) = DownstreamRequest::new(req, metadata);
905        let active_cpu_time_us = Arc::new(AtomicU64::new(0));
906
907        let sandbox = Sandbox::new(downstream, active_cpu_time_us.clone(), self.clone());
908
909        if let Instance::Component(_, _) = self.instance_pre.as_ref() {
910            panic!("components not currently supported with `run`");
911        }
912
913        let (module, instance_pre) = self.instance_pre.unwrap_module();
914
915        let profiler = self.guest_profile_config.as_deref().map(|pcfg| {
916            GuestProfiler::new(
917                program_name,
918                pcfg.sample_period,
919                vec![(program_name.to_string(), module.clone())],
920            )
921        });
922
923        let mut store = create_store(&self, sandbox, profiler, |builder| {
924            builder.arg(program_name);
925            for arg in args {
926                builder.arg(arg);
927            }
928        })
929        .map_err(ExecutionError::Context)?;
930
931        let instance = instance_pre
932            .instantiate_async(&mut store)
933            .await
934            .map_err(ExecutionError::Instantiation)?;
935
936        // Pull out the `_start` function, which by convention with WASI is the main entry point for
937        // an application.
938        let main_func = instance
939            .get_typed_func::<(), ()>(&mut store, "_start")
940            .map_err(ExecutionError::Typechecking)?;
941
942        // Invoke the entrypoint function and collect its exit code
943        let result =
944            CpuTimeTracking::new(active_cpu_time_us, main_func.call_async(&mut store, ())).await;
945
946        // If we collected a profile, write it to the file
947        write_profile(
948            &mut store,
949            self.guest_profile_config.as_deref().map(|cfg| &cfg.path),
950        );
951
952        // Ensure the downstream response channel is closed, whether or not a response was
953        // sent during execution.
954        store
955            .data_mut()
956            .close_downstream_response_sender(Response::default());
957
958        // We don't do anything with any response on the receiver, but
959        // it's important to keep it alive until after the program has
960        // finished.
961        drop(receiver);
962
963        result
964    }
965
966    pub fn cache(&self) -> &Arc<Cache> {
967        &self.cache
968    }
969
970    pub fn config_path(&self) -> Option<&Path> {
971        self.settings.config_path.as_deref()
972    }
973
974    pub fn object_store(&self) -> &ObjectStores {
975        &self.settings.object_store
976    }
977
978    pub fn secret_stores(&self) -> &SecretStores {
979        &self.settings.secret_stores
980    }
981
982    pub fn shielding_sites(&self) -> &ShieldingSites {
983        &self.settings.shielding_sites
984    }
985
986    /// The local Pushpin proxy port, if Fanout is enabled for this execution context.
987    pub fn local_pushpin_proxy_port(&self) -> Option<u16> {
988        self.settings.local_pushpin_proxy_port
989    }
990
991    /// Whether WebSocket passthrough is enabled for this execution context.
992    pub fn enable_local_websocket_passthrough(&self) -> bool {
993        self.settings.enable_local_websocket_passthrough
994    }
995
996    /// Get the valid mock Fastly API keys for this execution context.
997    pub fn fake_valid_fastly_keys(&self) -> &FakeValidFastlyKeys {
998        &self.settings.fake_valid_fastly_keys
999    }
1000
1001    pub async fn register_pending_downstream(&self) -> Option<oneshot::Receiver<NextRequest>> {
1002        let mut pending = self.pending_reuse.lock().await;
1003
1004        if pending.len() >= NEXT_REQ_PENDING_MAX {
1005            return None;
1006        }
1007
1008        let (tx, rx) = oneshot::channel();
1009        pending.push(tx);
1010
1011        Some(rx)
1012    }
1013
1014    pub fn is_component(&self) -> bool {
1015        matches!(self.instance_pre.as_ref(), Instance::Component(_, _))
1016    }
1017}
1018
1019pub struct ExecuteCtxBuilder {
1020    /// Path to the module to run.
1021    module_path: PathBuf,
1022    /// How execution of this module should be profiled, if at all.
1023    profiling: ProfilingConfig,
1024    /// The experimental wasi modules to link against.
1025    wasi_modules: HashSet<ExperimentalModule>,
1026    /// What to do with imports the host does not provide.
1027    unknown_import_behavior: UnknownImportBehavior,
1028    /// Whether to adapt core wasm modules to components before running them.
1029    adapt_components: bool,
1030    /// The wasm proposals to enable.
1031    wasm_features: WasmFeatures,
1032    /// Whether to compile the guest with native debug info, so that it can be inspected with a
1033    /// native debugger.
1034    debug_info: bool,
1035    /// The settings to hand to the finished context.
1036    settings: Settings,
1037}
1038
1039impl ExecuteCtxBuilder {
1040    /// Read, compile, and link the module, producing an execution context ready to serve
1041    /// requests.
1042    pub fn finish(self) -> Result<Arc<ExecuteCtx>, Error> {
1043        let ExecuteCtxBuilder {
1044            module_path,
1045            profiling,
1046            wasi_modules,
1047            unknown_import_behavior,
1048            adapt_components,
1049            wasm_features,
1050            debug_info,
1051            settings,
1052        } = self;
1053
1054        let guest_profile_config = profiling.guest_config();
1055
1056        let input = fs::read(&module_path)?;
1057
1058        let is_wat = module_path
1059            .extension()
1060            .map(|str| str == "wat")
1061            .unwrap_or(false);
1062
1063        // When the input wasn't a component, but we're automatically adapting,
1064        // apply the component adapter.
1065        let is_component = adapt::is_component(&input);
1066        let (is_wat, is_component, input) = if !is_component && adapt_components {
1067            let input = if is_wat {
1068                let text = String::from_utf8(input)
1069                    .map_err(|_| anyhow::anyhow!("Failed to parse {}", module_path.display()))?;
1070                adapt::adapt_wat(&text)?
1071            } else {
1072                adapt::adapt_bytes(&input)?
1073            };
1074
1075            (false, true, input)
1076        } else {
1077            (is_wat, is_component, input)
1078        };
1079
1080        let config = &configure_wasmtime(wasm_features, profiling.native_strategy(), debug_info);
1081        let engine = Engine::new(config)?;
1082        let instance_pre = if is_component {
1083            let mut linker: component::Linker<ComponentCtx> = component::Linker::new(&engine);
1084            compute::link_host_functions(&mut linker)?;
1085            let component = if is_wat {
1086                Component::from_file(&engine, &module_path)?
1087            } else {
1088                Component::from_binary(&engine, &input)?
1089            };
1090
1091            match unknown_import_behavior {
1092                UnknownImportBehavior::LinkError => (),
1093                UnknownImportBehavior::Trap => {
1094                    linker.define_unknown_imports_as_traps(&component)?
1095                }
1096            }
1097
1098            let instance_pre = linker.instantiate_pre(&component)?;
1099            Instance::Component(
1100                component,
1101                compute::bindings::AdapterServicePre::new(instance_pre)?,
1102            )
1103        } else {
1104            let mut linker = Linker::new(&engine);
1105            link_host_functions(&mut linker, &wasi_modules)?;
1106            let module = if is_wat {
1107                Module::from_file(&engine, &module_path)?
1108            } else {
1109                Module::from_binary(&engine, &input)?
1110            };
1111
1112            match unknown_import_behavior {
1113                UnknownImportBehavior::LinkError => (),
1114                UnknownImportBehavior::Trap => linker.define_unknown_imports_as_traps(&module)?,
1115            }
1116
1117            let instance_pre = linker.instantiate_pre(&module)?;
1118            Instance::Module(module, instance_pre)
1119        };
1120
1121        // Create the epoch-increment thread. Note that the period for epoch
1122        // interruptions is driven by the guest profiling sample period if
1123        // provided as guest stack sampling is done from the epoch
1124        // interruption callback.
1125
1126        let epoch_increment_stop = Arc::new(AtomicBool::new(false));
1127        let engine_clone = engine.clone();
1128        let epoch_increment_stop_clone = epoch_increment_stop.clone();
1129        let sample_period = guest_profile_config
1130            .as_ref()
1131            .map(|c| c.sample_period)
1132            .unwrap_or(DEFAULT_EPOCH_INTERRUPTION_PERIOD);
1133        let epoch_increment_thread = Some(thread::spawn(move || {
1134            while !epoch_increment_stop_clone.load(Ordering::Relaxed) {
1135                thread::sleep(sample_period);
1136                engine_clone.increment_epoch();
1137            }
1138        }));
1139
1140        Ok(Arc::new(ExecuteCtx {
1141            engine,
1142            instance_pre: Arc::new(instance_pre),
1143            tls_config: TlsConfig::new()?,
1144            next_req_id: Arc::new(AtomicU64::new(0)),
1145            cache: Arc::new(Cache::default()),
1146            pending_reuse: Arc::new(AsyncMutex::new(vec![])),
1147            epoch_increment_thread,
1148            epoch_increment_stop,
1149            guest_profile_config: guest_profile_config.map(Arc::new),
1150            settings,
1151        }))
1152    }
1153
1154    /// Set whether the guest should be compiled with native debug info, so that it can be
1155    /// inspected with a native debugger.
1156    ///
1157    /// Defaults to `false`, as generating debug info both slows compilation down and requires the
1158    /// guest to have been built with debug info of its own.
1159    pub fn with_debug_info(mut self, debug_info: bool) -> Self {
1160        self.debug_info = debug_info;
1161        self
1162    }
1163
1164    /// Set the acls for this execution context.
1165    pub fn with_acls(mut self, acls: Acls) -> Self {
1166        self.settings.acls = acls;
1167        self
1168    }
1169
1170    /// Set the backends for this execution context.
1171    pub fn with_backends(mut self, backends: Backends) -> Self {
1172        self.settings.backends = backends;
1173        self
1174    }
1175
1176    /// Set the device detection mappings for this execution context.
1177    pub fn with_device_detection(mut self, device_detection: DeviceDetection) -> Self {
1178        self.settings.device_detection = device_detection;
1179        self
1180    }
1181
1182    /// Set the geolocation mappings for this execution context.
1183    pub fn with_geolocation(mut self, geolocation: Geolocation) -> Self {
1184        self.settings.geolocation = geolocation;
1185        self
1186    }
1187
1188    /// Set the dictionaries for this execution context.
1189    pub fn with_dictionaries(mut self, dictionaries: Dictionaries) -> Self {
1190        self.settings.dictionaries = dictionaries;
1191        self
1192    }
1193
1194    /// Set the object store for this execution context.
1195    pub fn with_object_stores(mut self, object_store: ObjectStores) -> Self {
1196        self.settings.object_store = object_store;
1197        self
1198    }
1199
1200    /// Set the secret stores for this execution context.
1201    pub fn with_secret_stores(mut self, secret_stores: SecretStores) -> Self {
1202        self.settings.secret_stores = secret_stores;
1203        self
1204    }
1205    /// Set the shielding sites for this execution context.
1206    pub fn with_shielding_sites(mut self, shielding_sites: ShieldingSites) -> Self {
1207        self.settings.shielding_sites = shielding_sites;
1208        self
1209    }
1210
1211    /// Set the valid mock Fastly API keys for this execution context.
1212    pub fn with_fake_valid_fastly_keys(
1213        mut self,
1214        fake_valid_fastly_keys: FakeValidFastlyKeys,
1215    ) -> Self {
1216        self.settings.fake_valid_fastly_keys = fake_valid_fastly_keys;
1217        self
1218    }
1219
1220    /// Set the path to the config for this execution context.
1221    pub fn with_config_path(mut self, config_path: PathBuf) -> Self {
1222        self.settings.config_path = Some(config_path);
1223        self
1224    }
1225
1226    /// Set where to direct logging endpoint messages for this execution
1227    /// context. Defaults to stdout.
1228    pub fn with_capture_logs(mut self, capture_logs: Arc<Mutex<dyn Write + Send>>) -> Self {
1229        self.settings.capture_logs = capture_logs;
1230        self
1231    }
1232
1233    /// Set the stdout logging policy for this execution context.
1234    pub fn with_log_stdout(mut self, log_stdout: bool) -> Self {
1235        self.settings.log_stdout = log_stdout;
1236        self
1237    }
1238
1239    /// Set the stderr logging policy for this execution context.
1240    pub fn with_log_stderr(mut self, log_stderr: bool) -> Self {
1241        self.settings.log_stderr = log_stderr;
1242        self
1243    }
1244
1245    /// Set the local Pushpin proxy port
1246    pub fn with_local_pushpin_proxy_port(mut self, local_pushpin_proxy_port: Option<u16>) -> Self {
1247        self.settings.local_pushpin_proxy_port = local_pushpin_proxy_port;
1248        self
1249    }
1250
1251    /// Set whether WebSocket passthrough is enabled for this execution context.
1252    pub fn with_enable_local_websocket_passthrough(
1253        mut self,
1254        enable_local_websocket_passthrough: bool,
1255    ) -> Self {
1256        self.settings.enable_local_websocket_passthrough = enable_local_websocket_passthrough;
1257        self
1258    }
1259}
1260
1261fn write_profile_to_file(profile: Box<GuestProfiler>, path: &PathBuf) {
1262    match std::fs::File::create(path)
1263        .map_err(anyhow::Error::new)
1264        .and_then(|output| profile.finish(std::io::BufWriter::new(output)))
1265    {
1266        Err(e) => {
1267            event!(
1268                Level::ERROR,
1269                "failed writing profile at {}: {e:#}",
1270                path.display()
1271            );
1272        }
1273        _ => {
1274            event!(
1275                Level::INFO,
1276                "\nProfile written to: {}\nView this profile at https://profiler.firefox.com/.",
1277                path.display()
1278            );
1279        }
1280    }
1281}
1282
1283fn write_profile(store: &mut wasmtime::Store<WasmCtx>, guest_profile_path: Option<&PathBuf>) {
1284    if let (Some(profile), Some(path)) =
1285        (store.data_mut().take_guest_profiler(), guest_profile_path)
1286    {
1287        write_profile_to_file(profile, path);
1288    }
1289}
1290
1291fn write_profile_component(
1292    store: &mut wasmtime::Store<ComponentCtx>,
1293    guest_profile_path: Option<&PathBuf>,
1294) {
1295    if let (Some(profile), Some(path)) =
1296        (store.data_mut().take_guest_profiler(), guest_profile_path)
1297    {
1298        write_profile_to_file(profile, path);
1299    }
1300}
1301
1302fn guest_result_to_response(resp: Response<Body>, err: Option<anyhow::Error>) -> Response<Body> {
1303    err.as_ref().map(anyhow_response).unwrap_or(resp)
1304}
1305
1306fn exec_err_to_response(err: &ExecutionError) -> Response<Body> {
1307    if let ExecutionError::WasmTrap(e) = err {
1308        anyhow_response(e)
1309    } else {
1310        panic!("failed to run guest: {err}")
1311    }
1312}
1313
1314fn anyhow_response(err: &anyhow::Error) -> Response<Body> {
1315    anyhow_response_with_status(err, hyper::StatusCode::INTERNAL_SERVER_ERROR)
1316}
1317
1318fn anyhow_response_with_status(err: &anyhow::Error, status: hyper::StatusCode) -> Response<Body> {
1319    Response::builder()
1320        .status(status)
1321        .body(Body::from(format!("{err:?}").into_bytes()))
1322        .unwrap()
1323}
1324
1325impl Drop for ExecuteCtx {
1326    fn drop(&mut self) {
1327        if let Some(join_handle) = self.epoch_increment_thread.take() {
1328            self.epoch_increment_stop.store(true, Ordering::Relaxed);
1329            join_handle.join().unwrap();
1330        }
1331    }
1332}
1333
1334fn configure_wasmtime(
1335    wasm_features: WasmFeatures,
1336    profiling_strategy: ProfilingStrategy,
1337    debug_info: bool,
1338) -> wasmtime::Config {
1339    use wasmtime::{Config, InstanceAllocationStrategy, WasmBacktraceDetails};
1340
1341    let mut config = Config::new();
1342    // Off by default: translating the guest's DWARF into native debug info costs compile time,
1343    // and is only useful when the guest was built with debug info to begin with.
1344    config.debug_info(debug_info);
1345    config.wasm_backtrace_details(WasmBacktraceDetails::Enable);
1346    config.async_support(true);
1347    config.epoch_interruption(true);
1348    config.profiler(profiling_strategy);
1349
1350    config.allocation_strategy(InstanceAllocationStrategy::OnDemand);
1351
1352    config.wasm_features(wasm_features, true);
1353
1354    // Wasm permits the "relaxed" instructions to be nondeterministic
1355    // between runs, but requires them to be deterministic within runs.
1356    // Snapshotting a program's execution to avoid redundantly running
1357    // initialization code on each request is an important optimization,
1358    // so we enable deterministic lowerings for relaxed SIMD to ensure
1359    // that it works consistently even if the initialization runs on a
1360    // different host architecture.
1361    config.relaxed_simd_deterministic(true);
1362
1363    // Enable the wide-arithmetic feature, which has now been voted
1364    // to phase-4.
1365    config.wasm_wide_arithmetic(true);
1366
1367    config
1368}
1369
1370#[pin_project]
1371struct CpuTimeTracking<F> {
1372    #[pin]
1373    future: F,
1374    time_spent: Arc<AtomicU64>,
1375}
1376
1377impl<F> CpuTimeTracking<F> {
1378    fn new(time_spent: Arc<AtomicU64>, future: F) -> Self {
1379        CpuTimeTracking { future, time_spent }
1380    }
1381}
1382
1383impl<E, F: Future<Output = Result<(), E>>> Future for CpuTimeTracking<F> {
1384    type Output = F::Output;
1385
1386    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1387        let me = self.project();
1388
1389        let start = Instant::now();
1390        let result = me.future.poll(cx);
1391        // 2^64 microseconds is over half a million years, so I'm not terribly
1392        // worried about this cast.
1393        let runtime = start.elapsed().as_micros() as u64;
1394        let _ = me.time_spent.fetch_add(runtime, Ordering::SeqCst);
1395        result
1396    }
1397}