Skip to main content

submilli_engine/stdlib/
shared.rs

1//! Helpers shared across stdlib libraries.
2
3use std::io::Write;
4use std::sync::Arc;
5
6use crate::runtime::decision::{CallSite, CallTicket, EntryPath, SourceLine};
7use crate::runtime::fs::{ContainError, ContentPath, LinkPath, resolve_content, resolve_link};
8use crate::runtime::fuel;
9use crate::runtime::host::{
10    permission_denied, permission_denied_invariant, permission_denied_read_only,
11    quota_exceeded_error, write_boxed_number_struct,
12};
13use crate::runtime::security::{AuditDecision, CheckOutcome, SecurityCheck};
14use crate::runtime::vfs::{Access, Placement};
15use crate::runtime::{DiskQuota, QuotaCharge, QuotaExceeded, StoreData};
16
17/// The package whose code is executing, read off the innermost wasm frame's owning module.
18///
19/// This answers "whose code is running", which is the question a capability check needs —
20/// not "whose export did we last enter", which is what a shadow stack records. The two
21/// diverge whenever a package invokes caller-supplied code: a vtable-dispatched `toJson`, a
22/// closure passed across a package boundary.
23///
24/// Fails closed on both ways the principal can come back unknown, and neither may fall back
25/// to a permissive default:
26///
27/// - **No frames.** No wasm is executing, so this is an embedder-driven call with no guest
28///   principal to speak of.
29/// - **Frames, but the innermost module has no name.** A module this compiler produced always
30///   carries one. One that does not is not a principal we can name.
31///
32/// Attribution visits the innermost module directly, independently of the
33/// diagnostic backtrace setting, without capturing or symbolizing outer frames.
34pub(crate) fn running_package(
35    store: &impl wasmtime::AsContext<Data = StoreData>,
36) -> Result<String, PrincipalError> {
37    let mut principal = None;
38    wasmtime::WasmBacktrace::visit_modules(store, |module| {
39        principal = Some(match module.name() {
40            Some(name) => owned_principal(name).map_err(PrincipalError::Internal),
41            None => Err(PrincipalError::Unknown(UnknownPrincipal {
42                label: "<unnamed module>",
43                reason: "the running module declares no package name, so its caller cannot be identified",
44            })),
45        });
46        std::ops::ControlFlow::Break(())
47    }).map_err(|error| PrincipalError::Internal(crate::runtime::host::fatal_host_error(error)))?;
48    principal.unwrap_or_else(|| {
49        Err(PrincipalError::Unknown(UnknownPrincipal {
50            label: "<no wasm frame>",
51            reason: "no wasm frame is executing, so the call has no caller to attribute it to",
52        }))
53    })
54}
55
56pub(crate) fn owned_principal(name: &str) -> wasmtime::Result<String> {
57    let mut result = String::new();
58    result
59        .try_reserve_exact(name.len())
60        .map_err(crate::runtime::host::fatal_host_error)?;
61    result.push_str(name);
62    Ok(result)
63}
64
65pub(crate) enum PrincipalError {
66    Unknown(UnknownPrincipal),
67    Internal(wasmtime::Error),
68}
69impl PrincipalError {
70    pub fn into_denial(self, capability: &str) -> wasmtime::Error {
71        match self {
72            Self::Unknown(unknown) => {
73                permission_denied_invariant(unknown.label, capability, unknown.reason)
74            }
75            Self::Internal(error) => error,
76        }
77    }
78
79    pub fn label_or_error(self) -> wasmtime::Result<String> {
80        match self {
81            Self::Unknown(unknown) => owned_principal(unknown.label),
82            Self::Internal(error) => Err(error),
83        }
84    }
85}
86
87/// An unknown principal label cannot collide with an actual package name.
88pub(crate) struct UnknownPrincipal {
89    pub label: &'static str,
90    pub reason: &'static str,
91}
92
93/// Call at the top of each gated host fn body — after argument parsing,
94/// before any I/O. Attributes the call to whoever's code is running, via
95/// [`running_package`]. A denial surfaces to the guest as a catchable
96/// `PermissionDeniedError`.
97///
98/// A capability the catalog marks `main_denial` is refused to `main` here,
99/// ahead of the policy. `submilli:security#check` does not route through this
100/// function, so the invariant does not extend to it; that path attributes to
101/// the caller and would deny anyway.
102pub fn check_security(
103    store: impl wasmtime::AsContextMut<Data = StoreData>,
104    capability: &str,
105    context: serde_json::Value,
106) -> wasmtime::Result<()> {
107    check_security_call(store, capability, context).map(|_| ())
108}
109
110/// [`check_security`], returning the recorder's ticket for the call when one is installed,
111/// for host functions whose later decisions (redirect hops) belong to the same call.
112pub(crate) fn check_security_call(
113    mut store: impl wasmtime::AsContextMut<Data = StoreData>,
114    capability: &str,
115    context: serde_json::Value,
116) -> wasmtime::Result<Option<CallTicket>> {
117    // Direct caller attribution and the policy check have one flat gate charge.
118    fuel::charge_host_fuel(&mut store, fuel::GATE)?;
119    let caller = running_package(&store).map_err(|error| {
120        if let PrincipalError::Unknown(ref unknown) = error {
121            audit_entry_denial(
122                &store,
123                unknown.label,
124                capability,
125                &context,
126                "invariant",
127                unknown.reason,
128            );
129        }
130        error.into_denial(capability)
131    })?;
132    // Uncharged on purpose; see `DecisionLogConfig::max_line_capture_frames`.
133    let ticket = begin_recorded_call(&store, &caller, capability);
134    authorize_capability(
135        &caller,
136        store.as_context().data().security_check.as_ref(),
137        capability,
138        &context,
139        store.as_context().data().vfs.cwd(),
140        CallSite::new(ticket, EntryPath::GatedOp),
141    )?;
142    Ok(ticket)
143}
144
145/// Begins the recorder's call for a host function at its entry, with the line of the
146/// submitted program that led to it. `None` when no recorder is installed.
147pub(crate) fn begin_recorded_call(
148    store: &impl wasmtime::AsContext<Data = StoreData>,
149    caller: &str,
150    capability: &str,
151) -> Option<CallTicket> {
152    let recorder = store.as_context().data().security_check.recorder()?;
153    Some(recorder.begin_call(caller, capability, source_line(store)))
154}
155
156/// The line in the submitted program that is running, or `None` when the lookup fails.
157///
158/// Walks a full backtrace, which costs more than [`running_package`]'s module visit, so
159/// it runs only while a recorder is installed and still wants lines, uncharged to guest
160/// fuel (see `DecisionLogConfig::max_line_capture_frames`). Package frames are skipped:
161/// the line is the user's call into the package, not a line inside it.
162pub(crate) fn source_line(
163    store: &impl wasmtime::AsContext<Data = StoreData>,
164) -> Option<SourceLine> {
165    let recorder = store.as_context().data().security_check.recorder()?;
166    if !recorder.wants_line() {
167        return None;
168    }
169    let backtrace = wasmtime::WasmBacktrace::force_capture(store);
170    recorder.note_line_capture(backtrace.frames().len());
171    let frame = backtrace
172        .frames()
173        .iter()
174        .find(|frame| frame.module().name() == Some(crate::mangle::USER_PACKAGE))?;
175    let symbol = frame.symbols().first()?;
176    Some(SourceLine {
177        line: symbol.line()?,
178        column: symbol.column(),
179    })
180}
181
182/// Marks the latest recorded decision as a denial this host function swallowed to filter a
183/// listing. A no-op without a recorder.
184pub(crate) fn mark_filtered(store: &impl wasmtime::AsContext<Data = StoreData>) {
185    if let Some(recorder) = store.as_context().data().security_check.recorder() {
186        recorder.mark_last_filtered();
187    }
188}
189
190/// [`check_security`] for a caller resolved earlier, such as the principal of a
191/// request whose redirect hops are checked after the host fn has suspended.
192pub(crate) fn authorize_capability(
193    caller: &str,
194    security_check: &dyn SecurityCheck,
195    capability: &str,
196    context: &serde_json::Value,
197    cwd: &str,
198    site: CallSite,
199) -> wasmtime::Result<()> {
200    // The ordering is the invariant. This must precede both the delegation
201    // below and any work the host fn does after we return — a reorder that
202    // consults the policy first makes the refusal conditional on a policy
203    // existing, and the default check allows everything.
204    if caller == crate::mangle::USER_PACKAGE
205        && let Some(reason) =
206            crate::stdlib::capabilities::find_any(capability).and_then(|entry| entry.main_denial)
207    {
208        audit_denial_at(
209            security_check,
210            caller,
211            capability,
212            context,
213            "invariant",
214            reason,
215            site,
216        );
217        return Err(permission_denied_invariant(caller, capability, reason));
218    }
219    check_and_audit(security_check, caller, capability, context, cwd, site)
220        .map_err(|reason| permission_denied(caller, capability, reason))
221}
222
223/// Asks the policy whether `caller` may use `capability`, and audits its answer with the
224/// recorder's explanation. A denial returns the policy's reason.
225pub(crate) fn check_and_audit(
226    security_check: &dyn SecurityCheck,
227    caller: &str,
228    capability: &str,
229    context: &serde_json::Value,
230    cwd: &str,
231    site: CallSite,
232) -> Result<(), String> {
233    let outcome = security_check.check_with_cwd(caller, capability, context, cwd);
234    // The explanation is for the recorder alone; enforcement never reads it.
235    let explanation = security_check
236        .recorder()
237        .and_then(|_| security_check.explain(caller, capability, context, cwd));
238    let audit_context = security_check.audit_context(capability, context, cwd);
239    let context = audit_context.as_ref();
240    match outcome {
241        CheckOutcome::Allow { rule } => {
242            security_check.audit(
243                AuditDecision::new(caller, capability, context, true, "policy", rule, None)
244                    .with_explanation(explanation.as_ref())
245                    .with_site(site),
246            );
247            Ok(())
248        }
249        CheckOutcome::Deny { reason, rule } => {
250            security_check.audit(
251                AuditDecision::new(
252                    caller,
253                    capability,
254                    context,
255                    false,
256                    "policy",
257                    rule,
258                    Some(&reason),
259                )
260                .with_explanation(explanation.as_ref())
261                .with_site(site),
262            );
263            Err(reason)
264        }
265    }
266}
267
268pub(crate) fn audit_denial_at(
269    security: &dyn SecurityCheck,
270    caller: &str,
271    capability: &str,
272    context: &serde_json::Value,
273    source: &str,
274    reason: &str,
275    site: CallSite,
276) {
277    security.audit(
278        AuditDecision::new(
279            caller,
280            capability,
281            context,
282            false,
283            source,
284            None,
285            Some(reason),
286        )
287        .with_site(site),
288    );
289}
290
291/// Audits a refusal at the entry of a host call as a call of its own, with the program
292/// line that led to it. For a refusal after the call's gate, see [`audit_denial_in`].
293pub(crate) fn audit_entry_denial(
294    store: &impl wasmtime::AsContext<Data = StoreData>,
295    caller: &str,
296    capability: &str,
297    context: &serde_json::Value,
298    source: &str,
299    reason: &str,
300) {
301    let security = store.as_context().data().security_check.as_ref();
302    let ticket = security
303        .recorder()
304        .map(|recorder| recorder.begin_call(caller, capability, source_line(store)));
305    audit_denial_at(
306        security,
307        caller,
308        capability,
309        context,
310        source,
311        reason,
312        CallSite::new(ticket, EntryPath::GatedOp),
313    );
314}
315
316/// Audits a further refusal inside the host call `ticket` names, such as a read-only or
317/// quota refusal after the policy allowed it. Without a ticket the refusal is its own call.
318pub(crate) fn audit_denial_in(
319    store: &impl wasmtime::AsContext<Data = StoreData>,
320    ticket: Option<CallTicket>,
321    caller: &str,
322    capability: &str,
323    context: &serde_json::Value,
324    source: &str,
325    reason: &str,
326) {
327    audit_denial_at(
328        store.as_context().data().security_check.as_ref(),
329        caller,
330        capability,
331        context,
332        source,
333        reason,
334        CallSite::new(ticket, EntryPath::GatedOp),
335    );
336}
337
338/// Resolve a guest path for an operation that reaches its contents — every component,
339/// including the last, traversed under containment.
340pub fn resolve_content_or_trap(
341    data: &StoreData,
342    guest_path: &str,
343    op: &str,
344) -> wasmtime::Result<ContentPath> {
345    resolve_content(&data.vfs, data.vfs.cwd(), guest_path)
346        .map_err(|err| contain_trap(op, guest_path, &err))
347}
348
349/// Resolve a guest path for an operation that acts on the link itself — the final
350/// component is never followed.
351pub fn resolve_link_or_trap(
352    data: &StoreData,
353    guest_path: &str,
354    op: &str,
355) -> wasmtime::Result<LinkPath> {
356    resolve_link(&data.vfs, data.vfs.cwd(), guest_path)
357        .map_err(|err| contain_trap(op, guest_path, &err))
358}
359
360/// Refuse a write into a volume mounted read-only, attributed to the running
361/// package. Call after the capability check and resolution, before any other
362/// work, so the policy sees every attempt and a refused call changes nothing.
363/// `ticket` is the host call's gate, which the refusal continues.
364pub(crate) fn require_writable(
365    store: impl wasmtime::AsContext<Data = StoreData>,
366    ticket: Option<CallTicket>,
367    placement: &Placement,
368    capability: &str,
369    guest_path: &str,
370) -> wasmtime::Result<()> {
371    if placement.access() == Access::ReadWrite {
372        return Ok(());
373    }
374    let caller = running_package(&store).map_err(|error| {
375        if let PrincipalError::Unknown(ref unknown) = error {
376            audit_denial_in(
377                &store,
378                ticket,
379                unknown.label,
380                capability,
381                &serde_json::json!({ "path": guest_path }),
382                "invariant",
383                unknown.reason,
384            );
385        }
386        error.into_denial(capability)
387    })?;
388    audit_denial_in(
389        &store,
390        ticket,
391        &caller,
392        capability,
393        &serde_json::json!({ "path": guest_path }),
394        "read_only",
395        "the destination volume is read-only",
396    );
397    Err(read_only_denial(
398        &caller,
399        capability,
400        guest_path,
401        placement.mount_point(),
402    ))
403}
404
405/// Refuse a write aimed at the VFS root or a mount point: each is a directory,
406/// and replacing one would take a volume with it.
407pub(crate) fn refuse_volume_root(
408    resolved: &ContentPath,
409    op: &str,
410    guest_path: &str,
411) -> wasmtime::Result<()> {
412    if resolved.is_root() {
413        wasmtime::bail!(
414            "{op} {guest_path}: the VFS root or a mount point is a directory, not a file"
415        );
416    }
417    Ok(())
418}
419
420fn read_only_denial(
421    caller: &str,
422    capability: &str,
423    guest_path: &str,
424    mount: &str,
425) -> wasmtime::Error {
426    permission_denied_read_only(
427        caller,
428        capability,
429        format!("{guest_path} is in the volume mounted read-only at {mount}"),
430    )
431}
432
433/// The single translation from a containment failure to a guest-visible trap. Every
434/// escape reaches the guest as the same diagnostic regardless of which operation hit it,
435/// so an LLM reading one recognises the rest.
436///
437/// A read-only refusal that got past [`require_writable`] still surfaces as a
438/// `PermissionDeniedError`, attributed to `<vfs>` since the caller is not at hand.
439pub fn contain_trap(op: &str, guest_path: &str, err: &ContainError) -> wasmtime::Error {
440    if let ContainError::ReadOnly(mount) = err {
441        return read_only_denial("<vfs>", op, guest_path, mount);
442    }
443    wasmtime::Error::msg(format!("{op} {guest_path}: {err}"))
444}
445
446/// Distinguish a missing parent from an escaping one at the point a write first touches
447/// the filesystem.
448///
449/// Both surface as a failure to create, and keeping them apart is what an LLM needs: one
450/// is fixable with `mkdir`, the other can never succeed. The `parent.is_dir()` pre-check
451/// this replaced conflated them the other way — it reported every escape as a missing
452/// parent, costing a turn on a `mkdir` that could never work.
453pub fn write_target_trap(op: &str, guest_path: &str, err: &ContainError) -> wasmtime::Error {
454    match err {
455        ContainError::Io(e) if e.kind() == std::io::ErrorKind::NotFound => {
456            wasmtime::Error::msg(format!(
457                "{op} {guest_path}: parent directory does not exist \
458                 (create it with mkdir(path, true))"
459            ))
460        }
461        _ => contain_trap(op, guest_path, err),
462    }
463}
464
465/// A write refused because it would pass the VFS's size limit, as a `QuotaExceededError`
466/// the program can catch.
467pub(crate) fn quota_refusal(
468    op: &str,
469    guest_path: &str,
470    exceeded: QuotaExceeded,
471) -> wasmtime::Error {
472    quota_exceeded_error(format!("{op} {guest_path}: {exceeded}"))
473}
474
475/// No program code runs before the rename, so the write draws on the size of the file it
476/// replaces and reserves only the growth: a rewrite that ends within the size limit
477/// succeeds even though the old file and the temporary one briefly coexist.
478pub(crate) fn atomic_write(
479    final_path: &ContentPath,
480    bytes: &[u8],
481    permissions: Option<cap_std::fs::Permissions>,
482    guest_path: &str,
483    op: &str,
484    quota: Option<Arc<DiskQuota>>,
485) -> wasmtime::Result<()> {
486    let mut disk_charge = QuotaCharge::new(quota, final_path.regular_file());
487    disk_charge
488        .reserve(bytes.len() as u64)
489        .map_err(|exceeded| quota_refusal(op, guest_path, exceeded))?;
490    write_then_rename(final_path, bytes, permissions, guest_path, op)?;
491    disk_charge.commit();
492    Ok(())
493}
494
495/// No `parent.is_dir()` pre-check: `Dir` reports a missing parent as `NotFound` the way
496/// `std` does, and the old gate turned an escape into "parent directory does not exist" —
497/// costing an LLM a turn on a `mkdir` that could never succeed.
498fn write_then_rename(
499    final_path: &ContentPath,
500    bytes: &[u8],
501    permissions: Option<cap_std::fs::Permissions>,
502    guest_path: &str,
503    op: &str,
504) -> wasmtime::Result<()> {
505    let tmp = final_path.temp_sibling();
506    let mut f = tmp
507        .create_new()
508        .map_err(|e| write_target_trap(op, guest_path, &e))?;
509    let result = (|| {
510        if let Some(permissions) = permissions {
511            f.set_permissions(permissions).map_err(|e| {
512                wasmtime::Error::msg(format!("{op} {guest_path}: permissions: {e}"))
513            })?;
514        }
515        f.write_all(bytes)
516            .map_err(|e| wasmtime::Error::msg(format!("{op} {guest_path}: {e}")))?;
517        f.sync_all()
518            .map_err(|e| wasmtime::Error::msg(format!("{op} {guest_path}: fsync: {e}")))?;
519        drop(f);
520        tmp.rename_to(final_path)
521            .map_err(|e| contain_trap(op, guest_path, &e))
522    })();
523    if result.is_err() {
524        let _ = tmp.remove_file();
525    }
526    result
527}
528
529// --- shared by the model-listing libraries (`submilli:llm`, `submilli:embedding`) ---
530
531/// Characters a `Model.description` may carry into a guest model's
532/// model-selection reasoning.
533///
534/// The bound is not the whole defense — [`sanitize_description`] strips control
535/// characters and collapses the text to one line first, because a length bound
536/// alone does not stop an injection short enough to fit. The bound is what stops
537/// a long one, and what keeps a listing's size independent of how much prose an
538/// operator wrote.
539pub const MAX_DESCRIPTION_CHARS: usize = 280;
540
541/// Whether a per-candidate check's answer removes the candidate (`Ok(false)`),
542/// keeps it (`Ok(true)`), or must propagate (`Err`).
543///
544/// Only the policy's own answer filters. An invariant denial means the check
545/// could not be made at all — the caller could not be named, or a runtime rule
546/// refused ahead of the policy — and swallowing it would turn a runtime refusal
547/// into a silently short listing that reads as "the operator configured fewer
548/// models."
549pub(crate) fn filters_candidate(checked: wasmtime::Result<()>) -> wasmtime::Result<bool> {
550    let Err(err) = checked else {
551        return Ok(true);
552    };
553    match err.downcast_ref::<crate::runtime::host::PermissionDenied>() {
554        Some(denial) if denial.is_policy() => Ok(false),
555        _ => Err(err),
556    }
557}
558
559/// The candidates of a listing the caller may use. `gate` runs the capability
560/// check one candidate's use would; a policy denial leaves the candidate out and
561/// marks the recorded decision filtered, and any other refusal propagates, the
562/// [`filters_candidate`] rule.
563pub(crate) fn permitted_candidates<T>(
564    caller: &mut wasmtime::Caller<'_, StoreData>,
565    candidates: Vec<T>,
566    name_of: impl Fn(&T) -> &str,
567    mut gate: impl FnMut(&mut wasmtime::Caller<'_, StoreData>, &str) -> wasmtime::Result<()>,
568) -> wasmtime::Result<Vec<T>> {
569    let mut permitted = Vec::with_capacity(candidates.len());
570    for candidate in candidates {
571        if filters_candidate(gate(caller, name_of(&candidate)))? {
572            permitted.push(candidate);
573        } else {
574            mark_filtered(&*caller);
575        }
576    }
577    Ok(permitted)
578}
579
580/// At most the first 200 bytes of a program-supplied value, for an error that
581/// echoes it: a refused value may be refused for its size.
582pub(crate) fn truncated(value: &str) -> std::borrow::Cow<'_, str> {
583    const MAX_ECHO_BYTES: usize = 200;
584    if value.len() <= MAX_ECHO_BYTES {
585        return std::borrow::Cow::Borrowed(value);
586    }
587    let end = value.floor_char_boundary(MAX_ECHO_BYTES);
588    std::borrow::Cow::Owned(format!("{}…", value.get(..end).unwrap_or_default()))
589}
590
591/// Reduce an operator-authored `description` to inert single-line data.
592///
593/// This is sanitization, not merely a bound. The text flows verbatim into a
594/// guest model's model-selection reasoning, so an injection that redirects which
595/// model a program calls fits comfortably inside any length limit — a bound
596/// alone stops only the long ones. Control characters and line breaks are what
597/// let injected text present itself as a new instruction block, so they are
598/// removed first; the bound then stops the rest.
599pub(crate) fn sanitize_description(description: &str) -> Option<String> {
600    let flattened: String = description
601        .chars()
602        .map(|c| if c.is_control() { ' ' } else { c })
603        .collect();
604    let collapsed = flattened.split_whitespace().collect::<Vec<_>>().join(" ");
605    if collapsed.is_empty() {
606        return None;
607    }
608    Some(collapsed.chars().take(MAX_DESCRIPTION_CHARS).collect())
609}
610
611/// Charge for the check and establish caller attribution even for an empty
612/// catalog, without asking policy about a model that does not exist.
613/// `count_field` names the filter context's count (`prompt_count`, `input_count`).
614pub(crate) fn preflight_models(
615    caller: &mut wasmtime::Caller<'_, StoreData>,
616    capability: &str,
617    count_field: &str,
618) -> wasmtime::Result<()> {
619    preflight_listing(
620        caller,
621        capability,
622        &serde_json::json!({ "model": "", count_field: 0 }),
623    )
624}
625
626/// [`preflight_models`] for any listing filtered per candidate: `context` is what
627/// an invariant denial is audited with, the context of an empty candidate.
628pub(crate) fn preflight_listing(
629    caller: &mut wasmtime::Caller<'_, StoreData>,
630    capability: &str,
631    context: &serde_json::Value,
632) -> wasmtime::Result<()> {
633    fuel::charge_host_fuel(&mut *caller, fuel::GATE)?;
634    running_package(caller).map(|_| ()).map_err(|error| {
635        if let PrincipalError::Unknown(ref unknown) = error {
636            audit_entry_denial(
637                &*caller,
638                unknown.label,
639                capability,
640                context,
641                "invariant",
642                unknown.reason,
643            );
644        }
645        error.into_denial(capability)
646    })
647}
648
649/// Audit a token-budget refusal against the running package, inside the host call
650/// `ticket` names. `Err` is the denial to throw instead when the package cannot
651/// be named.
652pub(crate) fn audit_quota_denial(
653    caller: &wasmtime::Caller<'_, StoreData>,
654    ticket: Option<CallTicket>,
655    capability: &str,
656    model: &str,
657    reason: &str,
658) -> Result<(), wasmtime::Error> {
659    let who = running_package(caller).or_else(PrincipalError::label_or_error)?;
660    audit_denial_in(
661        caller,
662        ticket,
663        &who,
664        capability,
665        &serde_json::json!({ "model": model }),
666        "quota",
667        reason,
668    );
669    Ok(())
670}
671
672/// A boxed number, or `undefined` when absent.
673pub(crate) fn optional_number(
674    caller: &mut wasmtime::Caller<'_, StoreData>,
675    number: Option<f64>,
676) -> wasmtime::Result<wasmtime::Val> {
677    match number {
678        Some(number) => Ok(wasmtime::Val::AnyRef(Some(
679            write_boxed_number_struct(caller, number)?.to_anyref(),
680        ))),
681        None => crate::runtime::prelude::undefined::value(caller),
682    }
683}