1use 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
23pub(crate) enum GeneratorState {
27 Running,
29
30 AwaitingValue,
32}
33
34pub enum VMRequest {
40 ForceValue(Value),
44
45 DeepForceValue(Value),
47
48 WithValue(usize),
53
54 CapturedWithValue(usize),
57
58 NixEquality(Box<(Value, Value)>, PointerEquality),
61
62 StackPush(Value),
68
69 StackPop,
71
72 StringCoerce(Value, CoercionKind),
74
75 Call(Value),
78
79 EnterLambda {
82 lambda: Rc<Lambda>,
83 upvalues: Rc<Upvalues>,
84 span: Span,
85 },
86
87 EmitWarning(EvalWarning),
89
90 EmitWarningKind(WarningKind),
93
94 ImportCacheLookup(PathBuf),
97
98 ImportCachePut(PathBuf, Value),
101
102 PathImport(PathBuf),
104
105 OpenFile(PathBuf),
107
108 PathExists(PathBuf),
110
111 ReadDir(PathBuf),
113
114 Span,
116
117 TryForce(Value),
120
121 ReadFileType(PathBuf),
123}
124
125impl 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
184pub enum VMResponse {
186 Empty,
189
190 Value(Value),
192
193 Path(PathBuf),
195
196 Directory(Vec<(bytes::Bytes, FileType)>),
198
199 Span(Span),
201
202 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
225pub 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 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 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 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 let mut message = match (initial_message, state) {
275 (Some(msg), _) => msg,
276 (_, GeneratorState::Running) => VMResponse::Empty,
277
278 (_, GeneratorState::AwaitingValue) => VMResponse::Value(self.stack_pop()),
281 };
282
283 loop {
284 match generator.resume_with(message) {
285 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 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 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 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 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 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
522pub 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
532pub 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
541pub 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
553pub(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
565pub 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
575pub 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
615pub 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
623pub(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
640pub(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
648pub 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
656pub(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
675pub(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
684pub(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
692pub(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
700pub 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
739pub(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 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}