Struct tvix_eval::vm::VM

source ·
struct VM<'o, IO> {
    frames: Vec<Frame>,
    pub(crate) stack: Vec<Value>,
    with_stack: Vec<usize>,
    warnings: Vec<EvalWarning>,
    pub import_cache: ImportCache,
    source: SourceCode,
    nix_search_path: NixSearchPath,
    io_handle: IO,
    observer: &'o mut dyn RuntimeObserver,
    globals: Rc<GlobalsMap>,
    reasonable_span: Span,
    try_eval_frames: Vec<usize>,
}

Fields§

§frames: Vec<Frame>

VM’s frame stack, representing the execution contexts the VM is working through. Elements are usually pushed when functions are called, or thunks are being forced.

§stack: Vec<Value>

The VM’s top-level value stack. Within this stack, each code-executing frame holds a “view” of the stack representing the slice of the top-level stack that is relevant to its operation. This is done to avoid allocating a new Vec for each frame’s stack.

§with_stack: Vec<usize>

Stack indices (absolute indexes into stack) of attribute sets from which variables should be dynamically resolved (with).

§warnings: Vec<EvalWarning>

Runtime warnings collected during evaluation.

§import_cache: ImportCache

Import cache, mapping absolute file paths to the value that they compile to. Note that this reuses thunks, too!

§source: SourceCode

Data structure holding all source code evaluated in this VM, used for pretty error reporting.

§nix_search_path: NixSearchPath

Parsed Nix search path, which is used to resolve <...> references.

§io_handle: IO

Implementation of I/O operations used for impure builtins and features like import.

§observer: &'o mut dyn RuntimeObserver

Runtime observer which can print traces of runtime operations.

§globals: Rc<GlobalsMap>

Strong reference to the globals, guaranteeing that they are kept alive for the duration of evaluation.

This is important because recursive builtins (specifically import) hold a weak reference to the builtins, while the original strong reference is held by the compiler which does not exist anymore at runtime.

§reasonable_span: Span

A reasonably applicable span that can be used for errors in each execution situation.

The VM should update this whenever control flow changes take place (i.e. entering or exiting a frame to yield control somewhere).

§try_eval_frames: Vec<usize>

This field is responsible for handling builtins.tryEval. When that builtin is encountered, it sends a special message to the VM which pushes the frame index that requested to be informed of catchable errors in this field.

The frame stack is then laid out like this:

┌──┬──────────────────────────┐
│ 0│ `Result`-producing frame │
├──┼──────────────────────────┤
│-1│ `builtins.tryEval` frame │
├──┼──────────────────────────┤
│..│ ... other frames ...     │
└──┴──────────────────────────┘

Control is yielded to the outer VM loop, which evaluates the next frame and returns the result itself to the builtins.tryEval frame.

Implementations§

source§

impl<'o, IO> VM<'o, IO>
where IO: AsRef<dyn EvalIO> + 'static,

source

fn reenqueue_generator( &mut self, name: &'static str, span: Span, generator: Gen<VMRequest, VMResponse, Pin<Box<dyn Future<Output = Result<Value, ErrorKind>>>>>, )

Helper function to re-enqueue the current generator while it is awaiting a value.

source

pub(super) fn enqueue_generator<F, G>( &mut self, name: &'static str, span: Span, gen: G, )
where F: Future<Output = Result<Value, ErrorKind>> + 'static, G: FnOnce(GenCo) -> F,

Helper function to enqueue a new generator.

source

pub(crate) fn run_generator( &mut self, name: &'static str, span: Span, frame_id: usize, state: GeneratorState, generator: Gen<VMRequest, VMResponse, Pin<Box<dyn Future<Output = Result<Value, ErrorKind>>>>>, initial_message: Option<VMResponse>, ) -> EvalResult<bool>

Run a generator frame until it yields to the outer control loop, or runs to completion.

The return value indicates whether the generator has completed (true), or was suspended (false).

source§

impl<'o, IO> VM<'o, IO>
where IO: AsRef<dyn EvalIO> + 'static,

source

pub fn new( nix_search_path: NixSearchPath, io_handle: IO, observer: &'o mut dyn RuntimeObserver, source: SourceCode, globals: Rc<GlobalsMap>, reasonable_span: Span, ) -> Self

source

fn push_call_frame(&mut self, span: Span, call_frame: CallFrame)

Push a call frame onto the frame stack.

source

fn execute(self) -> EvalResult<RuntimeResult>

Run the VM’s primary (outer) execution loop, continuing execution based on the current frame at the top of the frame stack.

source

fn execute_bytecode(&mut self, span: Span, frame: CallFrame) -> EvalResult<bool>

Run the VM’s inner execution loop, processing Tvix bytecode from a chunk. This function returns if:

  1. The code has run to the end, and has left a value on the top of the stack. In this case, the frame is not returned to the frame stack.

  2. The code encounters a generator, in which case the frame in its current state is pushed back on the stack, and the generator is left on top of it for the outer loop to execute.

  3. An error is encountered.

This function must ensure that it leaves the frame stack in the correct order, especially when re-enqueuing a frame to execute.

The return value indicates whether the bytecode has been executed to completion, or whether it has been suspended in favour of a generator.

source§

impl<'o, IO> VM<'o, IO>
where IO: AsRef<dyn EvalIO> + 'static,

Implementation of helper functions for the runtime logic above.

source

pub(crate) fn stack_pop(&mut self) -> Value

source

fn stack_peek(&self, offset: usize) -> &Value

source

fn run_attrset(&mut self, count: usize, frame: &CallFrame) -> EvalResult<()>

source

fn last_call_frame(&self) -> Option<&CallFrame>

Access the last call frame present in the frame stack.

source

pub fn push_warning(&mut self, warning: EvalWarning)

Push an already constructed warning.

source

pub fn emit_warning(&mut self, kind: WarningKind)

Emit a warning with the given WarningKind and the source span of the current instruction.

source

fn run_interpolate(&mut self, count: u64, frame: &CallFrame) -> EvalResult<()>

Interpolate string fragments by popping the specified number of fragments of the stack, evaluating them to strings, and pushing the concatenated result string back on the stack.

source

fn call_builtin(&mut self, span: Span, builtin: Builtin) -> EvalResult<()>

Apply an argument from the stack to a builtin, and attempt to call it.

All calls are tail-calls in Tvix, as every function application is a separate thunk and OpCall is thus the last result in the thunk.

Due to this, once control flow exits this function, the generator will automatically be run by the VM.

source

fn call_value( &mut self, span: Span, parent: Option<(Span, CallFrame)>, callable: Value, ) -> EvalResult<()>

source

fn populate_upvalues( &mut self, frame: &mut CallFrame, count: u64, upvalues: impl DerefMut<Target = Upvalues>, ) -> EvalResult<()>

Populate the upvalue fields of a thunk or closure under construction.

See the closely tied function emit_upvalue_data in the compiler implementation for details on the argument processing.

Trait Implementations§

source§

impl<'o, IO> GetSpan for &VM<'o, IO>

Auto Trait Implementations§

§

impl<'o, IO> Freeze for VM<'o, IO>
where IO: Freeze,

§

impl<'o, IO> !RefUnwindSafe for VM<'o, IO>

§

impl<'o, IO> !Send for VM<'o, IO>

§

impl<'o, IO> !Sync for VM<'o, IO>

§

impl<'o, IO> Unpin for VM<'o, IO>
where IO: Unpin,

§

impl<'o, IO> !UnwindSafe for VM<'o, IO>

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> IntoEither for T

source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
source§

impl<T> Same for T

source§

type Output = T

Should always be Self
source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

source§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

source§

fn vzip(self) -> V