tvix_eval/vm/
generators.rs

1//! This module implements generator logic for the VM. Generators are functions
2//! used during evaluation which can suspend their execution during their
3//! control flow, and request that the VM do something.
4//!
5//! This is used to keep the VM's stack size constant even when evaluating
6//! deeply nested recursive data structures.
7//!
8//! We implement generators using the [`genawaiter`] crate.
9
10use core::pin::Pin;
11use genawaiter::rc::Co;
12pub use genawaiter::rc::Gen;
13use std::fmt::Display;
14use std::future::Future;
15
16use crate::value::PointerEquality;
17use crate::warnings::{EvalWarning, WarningKind};
18use crate::FileType;
19use crate::NixString;
20
21use super::*;
22
23// -- Implementation of generic generator logic.
24
25/// States that a generator can be in while being driven by the VM.
26pub(crate) enum GeneratorState {
27    /// Normal execution of the generator.
28    Running,
29
30    /// Generator is awaiting the result of a forced value.
31    AwaitingValue,
32}
33
34/// Messages that can be sent from generators *to* the VM. In most
35/// cases, the VM will suspend the generator when receiving a message
36/// and enter some other frame to process the request.
37///
38/// Responses are returned to generators via the [`GeneratorResponse`] type.
39pub enum VMRequest {
40    /// Request that the VM forces this value. This message is first sent to the
41    /// VM with the unforced value, then returned to the generator with the
42    /// forced result.
43    ForceValue(Value),
44
45    /// Request that the VM deep-forces the value.
46    DeepForceValue(Value),
47
48    /// Request the value at the given index from the VM's with-stack, in forced
49    /// state.
50    ///
51    /// The value is returned in the `ForceValue` message.
52    WithValue(usize),
53
54    /// Request the value at the given index from the *captured* with-stack, in
55    /// forced state.
56    CapturedWithValue(usize),
57
58    /// Request that the two values be compared for Nix equality. The result is
59    /// returned in the `ForceValue` message.
60    NixEquality(Box<(Value, Value)>, PointerEquality),
61
62    /// Push the given value to the VM's stack. This is used to prepare the
63    /// stack for requesting a function call from the VM.
64    ///
65    /// The VM does not respond to this request, so the next message received is
66    /// `Empty`.
67    StackPush(Value),
68
69    /// Pop a value from the stack and return it to the generator.
70    StackPop,
71
72    /// Request that the VM coerces this value to a string.
73    StringCoerce(Value, CoercionKind),
74
75    /// Request that the VM calls the given value, with arguments already
76    /// prepared on the stack. Value must already be forced.
77    Call(Value),
78
79    /// Request a call frame entering the given lambda immediately. This can be
80    /// used to force thunks.
81    EnterLambda {
82        lambda: Rc<Lambda>,
83        upvalues: Rc<Upvalues>,
84        span: Span,
85    },
86
87    /// Emit a runtime warning (already containing a span) through the VM.
88    EmitWarning(EvalWarning),
89
90    /// Emit a runtime warning through the VM. The span of the current generator
91    /// is used for the final warning.
92    EmitWarningKind(WarningKind),
93
94    /// Request a lookup in the VM's import cache, which tracks the
95    /// thunks yielded by previously imported files.
96    ImportCacheLookup(PathBuf),
97
98    /// Provide the VM with an imported value for a given path, which
99    /// it can populate its input cache with.
100    ImportCachePut(PathBuf, Value),
101
102    /// Request that the VM imports the given path through its I/O interface.
103    PathImport(PathBuf),
104
105    /// Request that the VM opens the specified file and provides a reader.
106    OpenFile(PathBuf),
107
108    /// Request that the VM checks whether the given path exists.
109    PathExists(PathBuf),
110
111    /// Request that the VM reads the given path.
112    ReadDir(PathBuf),
113
114    /// Request a reasonable span from the VM.
115    Span,
116
117    /// Request evaluation of `builtins.tryEval` from the VM. See
118    /// [`VM::catch_result`] for an explanation of how this works.
119    TryForce(Value),
120
121    /// Request the VM for the file type of the given path.
122    ReadFileType(PathBuf),
123}
124
125/// Human-readable representation of a generator message, used by observers.
126impl Display for VMRequest {
127    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128        match self {
129            VMRequest::ForceValue(v) => write!(f, "force_value({})", v.type_of()),
130            VMRequest::DeepForceValue(v) => {
131                write!(f, "deep_force_value({})", v.type_of())
132            }
133            VMRequest::WithValue(_) => write!(f, "with_value"),
134            VMRequest::CapturedWithValue(_) => write!(f, "captured_with_value"),
135            VMRequest::NixEquality(values, ptr_eq) => {
136                write!(
137                    f,
138                    "nix_eq({}, {}, PointerEquality::{:?})",
139                    values.0.type_of(),
140                    values.1.type_of(),
141                    ptr_eq
142                )
143            }
144            VMRequest::StackPush(v) => write!(f, "stack_push({})", v.type_of()),
145            VMRequest::StackPop => write!(f, "stack_pop"),
146            VMRequest::StringCoerce(
147                v,
148                CoercionKind {
149                    strong,
150                    import_paths,
151                },
152            ) => write!(
153                f,
154                "{}_{}importing_string_coerce({})",
155                if *strong { "strong" } else { "weak" },
156                if *import_paths { "" } else { "non_" },
157                v.type_of()
158            ),
159            VMRequest::Call(v) => write!(f, "call({v})"),
160            VMRequest::EnterLambda { lambda, .. } => {
161                write!(f, "enter_lambda({:p})", *lambda)
162            }
163            VMRequest::EmitWarning(_) => write!(f, "emit_warning"),
164            VMRequest::EmitWarningKind(_) => write!(f, "emit_warning_kind"),
165            VMRequest::ImportCacheLookup(p) => {
166                write!(f, "import_cache_lookup({})", p.to_string_lossy())
167            }
168            VMRequest::ImportCachePut(p, _) => {
169                write!(f, "import_cache_put({})", p.to_string_lossy())
170            }
171            VMRequest::PathImport(p) => write!(f, "path_import({})", p.to_string_lossy()),
172            VMRequest::OpenFile(p) => {
173                write!(f, "open_file({})", p.to_string_lossy())
174            }
175            VMRequest::PathExists(p) => write!(f, "path_exists({})", p.to_string_lossy()),
176            VMRequest::ReadDir(p) => write!(f, "read_dir({})", p.to_string_lossy()),
177            VMRequest::Span => write!(f, "span"),
178            VMRequest::TryForce(v) => write!(f, "try_force({})", v.type_of()),
179            VMRequest::ReadFileType(p) => write!(f, "read_file_type({})", p.to_string_lossy()),
180        }
181    }
182}
183
184/// Responses returned to generators *from* the VM.
185pub enum VMResponse {
186    /// Empty message. Passed to the generator as the first message,
187    /// or when return values were optional.
188    Empty,
189
190    /// Value produced by the VM and returned to the generator.
191    Value(Value),
192
193    /// Path produced by the VM in response to some IO operation.
194    Path(PathBuf),
195
196    /// VM response with the contents of a directory.
197    Directory(Vec<(bytes::Bytes, FileType)>),
198
199    /// VM response with a span to use at the current point.
200    Span(Span),
201
202    /// [std::io::Reader] produced by the VM in response to some IO operation.
203    Reader(Box<dyn std::io::Read>),
204
205    FileType(FileType),
206}
207
208impl Display for VMResponse {
209    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210        match self {
211            VMResponse::Empty => write!(f, "empty"),
212            VMResponse::Value(v) => write!(f, "value({v})"),
213            VMResponse::Path(p) => write!(f, "path({})", p.to_string_lossy()),
214            VMResponse::Directory(d) => write!(f, "dir(len = {})", d.len()),
215            VMResponse::Span(_) => write!(f, "span"),
216            VMResponse::Reader(_) => write!(f, "reader"),
217            VMResponse::FileType(t) => write!(f, "file_type({t})"),
218        }
219    }
220}
221
222pub(crate) type Generator =
223    Gen<VMRequest, VMResponse, Pin<Box<dyn Future<Output = Result<Value, ErrorKind>>>>>;
224
225/// Helper function to provide type annotations which are otherwise difficult to
226/// infer.
227pub fn pin_generator(
228    f: impl Future<Output = Result<Value, ErrorKind>> + 'static,
229) -> Pin<Box<dyn Future<Output = Result<Value, ErrorKind>>>> {
230    Box::pin(f)
231}
232
233impl VM<'_> {
234    /// Helper function to re-enqueue the current generator while it
235    /// is awaiting a value.
236    fn reenqueue_generator(&mut self, name: &'static str, span: Span, generator: Generator) {
237        self.frames.push(Frame::Generator {
238            name,
239            generator,
240            span,
241            state: GeneratorState::AwaitingValue,
242        });
243    }
244
245    /// Helper function to enqueue a new generator.
246    pub(super) fn enqueue_generator<F, G>(&mut self, name: &'static str, span: Span, gen: G)
247    where
248        F: Future<Output = Result<Value, ErrorKind>> + 'static,
249        G: FnOnce(GenCo) -> F,
250    {
251        self.frames.push(Frame::Generator {
252            name,
253            span,
254            state: GeneratorState::Running,
255            generator: Gen::new(|co| pin_generator(gen(co))),
256        });
257    }
258
259    /// Run a generator frame until it yields to the outer control loop, or runs
260    /// to completion.
261    ///
262    /// The return value indicates whether the generator has completed (true),
263    /// or was suspended (false).
264    pub(crate) fn run_generator(
265        &mut self,
266        name: &'static str,
267        span: Span,
268        frame_id: usize,
269        state: GeneratorState,
270        mut generator: Generator,
271        initial_message: Option<VMResponse>,
272    ) -> EvalResult<bool> {
273        // Determine what to send to the generator based on its state.
274        let mut message = match (initial_message, state) {
275            (Some(msg), _) => msg,
276            (_, GeneratorState::Running) => VMResponse::Empty,
277
278            // If control returned here, and the generator is
279            // awaiting a value, send it the top of the stack.
280            (_, GeneratorState::AwaitingValue) => VMResponse::Value(self.stack_pop()),
281        };
282
283        loop {
284            match generator.resume_with(message) {
285                // If the generator yields, it contains an instruction
286                // for what the VM should do.
287                genawaiter::GeneratorState::Yielded(request) => {
288                    self.observer.observe_generator_request(name, &request);
289
290                    match request {
291                        VMRequest::StackPush(value) => {
292                            self.stack.push(value);
293                            message = VMResponse::Empty;
294                        }
295
296                        VMRequest::StackPop => {
297                            message = VMResponse::Value(self.stack_pop());
298                        }
299
300                        // Generator has requested a force, which means that
301                        // this function prepares the frame stack and yields
302                        // back to the outer VM loop.
303                        VMRequest::ForceValue(value) => {
304                            self.reenqueue_generator(name, span, generator);
305                            self.enqueue_generator("force", span, |co| {
306                                value.force_owned_genco(co, span)
307                            });
308                            return Ok(false);
309                        }
310
311                        // Generator has requested a deep-force.
312                        VMRequest::DeepForceValue(value) => {
313                            self.reenqueue_generator(name, span, generator);
314                            self.enqueue_generator("deep_force", span, |co| {
315                                value.deep_force(co, span)
316                            });
317                            return Ok(false);
318                        }
319
320                        // Generator has requested a value from the with-stack.
321                        // Logic is similar to `ForceValue`, except with the
322                        // value being taken from that stack.
323                        VMRequest::WithValue(idx) => {
324                            self.reenqueue_generator(name, span, generator);
325
326                            let value = self.stack[self.with_stack[idx]].clone();
327                            self.enqueue_generator("force", span, |co| {
328                                value.force_owned_genco(co, span)
329                            });
330
331                            return Ok(false);
332                        }
333
334                        // Generator has requested a value from the *captured*
335                        // with-stack. Logic is same as above, except for the
336                        // value being from that stack.
337                        VMRequest::CapturedWithValue(idx) => {
338                            self.reenqueue_generator(name, span, generator);
339
340                            let call_frame = self.last_call_frame()
341                                .expect("Tvix bug: generator requested captured with-value, but there is no call frame");
342
343                            let value = call_frame.upvalues.with_stack().unwrap()[idx].clone();
344                            self.enqueue_generator("force", span, |co| {
345                                value.force_owned_genco(co, span)
346                            });
347
348                            return Ok(false);
349                        }
350
351                        VMRequest::NixEquality(values, ptr_eq) => {
352                            let values = *values;
353                            self.reenqueue_generator(name, span, generator);
354                            self.enqueue_generator("nix_eq", span, |co| {
355                                values.0.nix_eq_owned_genco(values.1, co, ptr_eq, span)
356                            });
357                            return Ok(false);
358                        }
359
360                        VMRequest::StringCoerce(val, kind) => {
361                            self.reenqueue_generator(name, span, generator);
362                            self.enqueue_generator("coerce_to_string", span, |co| {
363                                val.coerce_to_string(co, kind, span)
364                            });
365                            return Ok(false);
366                        }
367
368                        VMRequest::Call(callable) => {
369                            self.reenqueue_generator(name, span, generator);
370                            self.call_value(span, None, callable)?;
371                            return Ok(false);
372                        }
373
374                        VMRequest::EnterLambda {
375                            lambda,
376                            upvalues,
377                            span,
378                        } => {
379                            self.reenqueue_generator(name, span, generator);
380
381                            self.frames.push(Frame::CallFrame {
382                                span,
383                                call_frame: CallFrame {
384                                    lambda,
385                                    upvalues,
386                                    ip: CodeIdx(0),
387                                    stack_offset: self.stack.len(),
388                                },
389                            });
390
391                            return Ok(false);
392                        }
393
394                        VMRequest::EmitWarning(warning) => {
395                            self.push_warning(warning);
396                            message = VMResponse::Empty;
397                        }
398
399                        VMRequest::EmitWarningKind(kind) => {
400                            self.emit_warning(kind);
401                            message = VMResponse::Empty;
402                        }
403
404                        VMRequest::ImportCacheLookup(path) => {
405                            if let Some(cached) = self.import_cache.get(path) {
406                                message = VMResponse::Value(cached.clone());
407                            } else {
408                                message = VMResponse::Empty;
409                            }
410                        }
411
412                        VMRequest::ImportCachePut(path, value) => {
413                            self.import_cache.insert(path, value);
414                            message = VMResponse::Empty;
415                        }
416
417                        VMRequest::PathImport(path) => {
418                            let imported = self
419                                .io_handle
420                                .as_ref()
421                                .import_path(&path)
422                                .map_err(|e| ErrorKind::IO {
423                                    path: Some(path),
424                                    error: e.into(),
425                                })
426                                .with_span(span, self)?;
427
428                            message = VMResponse::Path(imported);
429                        }
430
431                        VMRequest::OpenFile(path) => {
432                            let reader = self
433                                .io_handle
434                                .as_ref()
435                                .open(&path)
436                                .map_err(|e| ErrorKind::IO {
437                                    path: Some(path),
438                                    error: e.into(),
439                                })
440                                .with_span(span, self)?;
441
442                            message = VMResponse::Reader(reader)
443                        }
444
445                        VMRequest::PathExists(path) => {
446                            let exists = self
447                                .io_handle
448                                .as_ref()
449                                .path_exists(&path)
450                                .map_err(|e| ErrorKind::IO {
451                                    path: Some(path),
452                                    error: e.into(),
453                                })
454                                .map(Value::Bool)
455                                .with_span(span, self)?;
456
457                            message = VMResponse::Value(exists);
458                        }
459
460                        VMRequest::ReadDir(path) => {
461                            let dir = self
462                                .io_handle
463                                .as_ref()
464                                .read_dir(&path)
465                                .map_err(|e| ErrorKind::IO {
466                                    path: Some(path),
467                                    error: e.into(),
468                                })
469                                .with_span(span, self)?;
470                            message = VMResponse::Directory(dir);
471                        }
472
473                        VMRequest::Span => {
474                            message = VMResponse::Span(self.reasonable_span);
475                        }
476
477                        VMRequest::TryForce(value) => {
478                            self.try_eval_frames.push(frame_id);
479                            self.reenqueue_generator(name, span, generator);
480
481                            debug_assert!(
482                                self.frames.len() == frame_id + 1,
483                                "generator should be reenqueued with the same frame ID"
484                            );
485
486                            self.enqueue_generator("force", span, |co| {
487                                value.force_owned_genco(co, span)
488                            });
489                            return Ok(false);
490                        }
491
492                        VMRequest::ReadFileType(path) => {
493                            let file_type = self
494                                .io_handle
495                                .as_ref()
496                                .file_type(&path)
497                                .map_err(|e| ErrorKind::IO {
498                                    path: Some(path),
499                                    error: e.into(),
500                                })
501                                .with_span(span, self)?;
502
503                            message = VMResponse::FileType(file_type);
504                        }
505                    }
506                }
507
508                // Generator has completed, and its result value should
509                // be left on the stack.
510                genawaiter::GeneratorState::Complete(result) => {
511                    let value = result.with_span(span, self)?;
512                    self.stack.push(value);
513                    return Ok(true);
514                }
515            }
516        }
517    }
518}
519
520pub type GenCo = Co<VMRequest, VMResponse>;
521
522// -- Implementation of concrete generator use-cases.
523
524/// Request that the VM place the given value on its stack.
525pub async fn request_stack_push(co: &GenCo, val: Value) {
526    match co.yield_(VMRequest::StackPush(val)).await {
527        VMResponse::Empty => {}
528        msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
529    }
530}
531
532/// Request that the VM pop a value from the stack and return it to the
533/// generator.
534pub async fn request_stack_pop(co: &GenCo) -> Value {
535    match co.yield_(VMRequest::StackPop).await {
536        VMResponse::Value(value) => value,
537        msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
538    }
539}
540
541/// Force any value and return the evaluated result from the VM.
542pub async fn request_force(co: &GenCo, val: Value) -> Value {
543    if let Value::Thunk(_) = val {
544        match co.yield_(VMRequest::ForceValue(val)).await {
545            VMResponse::Value(value) => value,
546            msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
547        }
548    } else {
549        val
550    }
551}
552
553/// Force a value
554pub(crate) async fn request_try_force(co: &GenCo, val: Value) -> Value {
555    if let Value::Thunk(_) = val {
556        match co.yield_(VMRequest::TryForce(val)).await {
557            VMResponse::Value(value) => value,
558            msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
559        }
560    } else {
561        val
562    }
563}
564
565/// Call the given value as a callable. The argument(s) must already be prepared
566/// on the stack.
567pub async fn request_call(co: &GenCo, val: Value) -> Value {
568    let val = request_force(co, val).await;
569    match co.yield_(VMRequest::Call(val)).await {
570        VMResponse::Value(value) => value,
571        msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
572    }
573}
574
575/// Helper function to call the given value with the provided list of arguments.
576/// This uses the StackPush and Call messages under the hood.
577pub async fn request_call_with<I>(co: &GenCo, mut callable: Value, args: I) -> Value
578where
579    I: IntoIterator<Item = Value>,
580    I::IntoIter: DoubleEndedIterator,
581{
582    let mut num_args = 0_usize;
583    for arg in args.into_iter().rev() {
584        num_args += 1;
585        request_stack_push(co, arg).await;
586    }
587
588    debug_assert!(num_args > 0, "call_with called with an empty list of args");
589
590    while num_args > 0 {
591        callable = request_call(co, callable).await;
592        num_args -= 1;
593    }
594
595    callable
596}
597
598pub async fn request_string_coerce(
599    co: &GenCo,
600    val: Value,
601    kind: CoercionKind,
602) -> Result<NixString, CatchableErrorKind> {
603    match val {
604        Value::String(s) => Ok(s),
605        _ => match co.yield_(VMRequest::StringCoerce(val, kind)).await {
606            VMResponse::Value(Value::Catchable(c)) => Err(*c),
607            VMResponse::Value(value) => Ok(value
608                .to_contextful_str()
609                .expect("coerce_to_string always returns a string")),
610            msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
611        },
612    }
613}
614
615/// Deep-force any value and return the evaluated result from the VM.
616pub async fn request_deep_force(co: &GenCo, val: Value) -> Value {
617    match co.yield_(VMRequest::DeepForceValue(val)).await {
618        VMResponse::Value(value) => value,
619        msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
620    }
621}
622
623/// Ask the VM to compare two values for equality.
624pub(crate) async fn check_equality(
625    co: &GenCo,
626    a: Value,
627    b: Value,
628    ptr_eq: PointerEquality,
629) -> Result<Result<bool, CatchableErrorKind>, ErrorKind> {
630    match co
631        .yield_(VMRequest::NixEquality(Box::new((a, b)), ptr_eq))
632        .await
633    {
634        VMResponse::Value(Value::Bool(b)) => Ok(Ok(b)),
635        VMResponse::Value(Value::Catchable(cek)) => Ok(Err(*cek)),
636        msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
637    }
638}
639
640/// Emit a fully constructed runtime warning.
641pub(crate) async fn emit_warning(co: &GenCo, warning: EvalWarning) {
642    match co.yield_(VMRequest::EmitWarning(warning)).await {
643        VMResponse::Empty => {}
644        msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
645    }
646}
647
648/// Emit a runtime warning with the span of the current generator.
649pub async fn emit_warning_kind(co: &GenCo, kind: WarningKind) {
650    match co.yield_(VMRequest::EmitWarningKind(kind)).await {
651        VMResponse::Empty => {}
652        msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
653    }
654}
655
656/// Request that the VM enter the given lambda.
657pub(crate) async fn request_enter_lambda(
658    co: &GenCo,
659    lambda: Rc<Lambda>,
660    upvalues: Rc<Upvalues>,
661    span: Span,
662) -> Value {
663    let msg = VMRequest::EnterLambda {
664        lambda,
665        upvalues,
666        span,
667    };
668
669    match co.yield_(msg).await {
670        VMResponse::Value(value) => value,
671        msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
672    }
673}
674
675/// Request a lookup in the VM's import cache.
676pub(crate) async fn request_import_cache_lookup(co: &GenCo, path: PathBuf) -> Option<Value> {
677    match co.yield_(VMRequest::ImportCacheLookup(path)).await {
678        VMResponse::Value(value) => Some(value),
679        VMResponse::Empty => None,
680        msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
681    }
682}
683
684/// Request that the VM populate its input cache for the given path.
685pub(crate) async fn request_import_cache_put(co: &GenCo, path: PathBuf, value: Value) {
686    match co.yield_(VMRequest::ImportCachePut(path, value)).await {
687        VMResponse::Empty => {}
688        msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
689    }
690}
691
692/// Request that the VM import the given path.
693pub(crate) async fn request_path_import(co: &GenCo, path: PathBuf) -> PathBuf {
694    match co.yield_(VMRequest::PathImport(path)).await {
695        VMResponse::Path(path) => path,
696        msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
697    }
698}
699
700/// Request that the VM open a [std::io::Read] for the specified file.
701pub async fn request_open_file(co: &GenCo, path: PathBuf) -> Box<dyn std::io::Read> {
702    match co.yield_(VMRequest::OpenFile(path)).await {
703        VMResponse::Reader(value) => value,
704        msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
705    }
706}
707
708#[cfg_attr(not(feature = "impure"), allow(unused))]
709pub(crate) async fn request_path_exists(co: &GenCo, path: PathBuf) -> Value {
710    match co.yield_(VMRequest::PathExists(path)).await {
711        VMResponse::Value(value) => value,
712        msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
713    }
714}
715
716#[cfg_attr(not(feature = "impure"), allow(unused))]
717pub(crate) async fn request_read_dir(co: &GenCo, path: PathBuf) -> Vec<(bytes::Bytes, FileType)> {
718    match co.yield_(VMRequest::ReadDir(path)).await {
719        VMResponse::Directory(dir) => dir,
720        msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
721    }
722}
723
724pub(crate) async fn request_span(co: &GenCo) -> Span {
725    match co.yield_(VMRequest::Span).await {
726        VMResponse::Span(span) => span,
727        msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
728    }
729}
730
731#[cfg_attr(not(feature = "impure"), allow(unused))]
732pub(crate) async fn request_read_file_type(co: &GenCo, path: PathBuf) -> FileType {
733    match co.yield_(VMRequest::ReadFileType(path)).await {
734        VMResponse::FileType(file_type) => file_type,
735        msg => panic!("Tvix bug: VM responded with incorrect generator message: {msg}"),
736    }
737}
738
739/// Call the given value as if it was an attribute set containing a functor. The
740/// arguments must already be prepared on the stack when a generator frame from
741/// this function is invoked.
742///
743pub(crate) async fn call_functor(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
744    let attrs = value.to_attrs()?;
745
746    match attrs.select_str("__functor") {
747        None => Err(ErrorKind::NotCallable("set without `__functor` attribute")),
748        Some(functor) => {
749            // The functor receives the set itself as its first argument and
750            // needs to be called with it.
751            let functor = request_force(&co, functor.clone()).await;
752            let primed = request_call_with(&co, functor, [value]).await;
753            Ok(request_call(&co, primed).await)
754        }
755    }
756}