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>
impl<'o, IO> VM<'o, IO>
sourcefn reenqueue_generator(
&mut self,
name: &'static str,
span: Span,
generator: Gen<VMRequest, VMResponse, Pin<Box<dyn Future<Output = Result<Value, ErrorKind>>>>>,
)
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.
sourcepub(super) fn enqueue_generator<F, G>(
&mut self,
name: &'static str,
span: Span,
gen: G,
)
pub(super) fn enqueue_generator<F, G>( &mut self, name: &'static str, span: Span, gen: G, )
Helper function to enqueue a new generator.
sourcepub(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>
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>
impl<'o, IO> VM<'o, IO>
pub fn new( nix_search_path: NixSearchPath, io_handle: IO, observer: &'o mut dyn RuntimeObserver, source: SourceCode, globals: Rc<GlobalsMap>, reasonable_span: Span, ) -> Self
sourcefn push_call_frame(&mut self, span: Span, call_frame: CallFrame)
fn push_call_frame(&mut self, span: Span, call_frame: CallFrame)
Push a call frame onto the frame stack.
sourcefn execute(self) -> EvalResult<RuntimeResult>
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.
sourcefn execute_bytecode(&mut self, span: Span, frame: CallFrame) -> EvalResult<bool>
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:
-
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.
-
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.
-
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>
impl<'o, IO> VM<'o, IO>
Implementation of helper functions for the runtime logic above.
pub(crate) fn stack_pop(&mut self) -> Value
fn stack_peek(&self, offset: usize) -> &Value
fn run_attrset(&mut self, count: usize, frame: &CallFrame) -> EvalResult<()>
sourcefn last_call_frame(&self) -> Option<&CallFrame>
fn last_call_frame(&self) -> Option<&CallFrame>
Access the last call frame present in the frame stack.
sourcepub fn push_warning(&mut self, warning: EvalWarning)
pub fn push_warning(&mut self, warning: EvalWarning)
Push an already constructed warning.
sourcepub fn emit_warning(&mut self, kind: WarningKind)
pub fn emit_warning(&mut self, kind: WarningKind)
Emit a warning with the given WarningKind and the source span of the current instruction.
sourcefn run_interpolate(&mut self, count: u64, frame: &CallFrame) -> EvalResult<()>
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.
sourcefn call_builtin(&mut self, span: Span, builtin: Builtin) -> EvalResult<()>
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.
fn call_value( &mut self, span: Span, parent: Option<(Span, CallFrame)>, callable: Value, ) -> EvalResult<()>
sourcefn populate_upvalues(
&mut self,
frame: &mut CallFrame,
count: u64,
upvalues: impl DerefMut<Target = Upvalues>,
) -> EvalResult<()>
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.