Skip to main content

nu_protocol/ir/
mod.rs

1use crate::{
2    BlockId, DeclId, Filesize, RegId, ShellError, Span, Value, VarId,
3    ast::{CellPath, Expression, Operator, Pattern, RangeInclusion},
4    engine::{EngineState, ScopeBindings},
5};
6use chrono::{DateTime, FixedOffset};
7use serde::{Deserialize, Serialize};
8use std::{fmt, sync::Arc};
9
10mod call;
11mod display;
12
13pub use call::*;
14pub use display::{FmtInstruction, FmtIrBlock};
15
16/// Instruction-index range where a nested block's local command/module bindings apply.
17///
18/// Keyword bodies (`if`/`for`/…) are IR-**inlined** into the parent block and never enter
19/// `eval_ir_block`. The compiler records these regions so `scope` can resolve locals by
20///  comparing the current program counter.
21#[derive(Clone, Debug)]
22pub struct ScopeRegion {
23    /// Inclusive start index into [`IrBlock::instructions`].
24    pub start: usize,
25    /// Exclusive end index into [`IrBlock::instructions`].
26    pub end: usize,
27    pub bindings: Arc<ScopeBindings>,
28}
29
30impl ScopeRegion {
31    pub fn contains(&self, instruction_index: usize) -> bool {
32        self.start <= instruction_index && instruction_index < self.end
33    }
34}
35
36#[derive(Clone, Serialize, Deserialize)]
37pub struct IrBlock {
38    pub instructions: Vec<Instruction>,
39    pub spans: Vec<Span>,
40    #[serde(with = "serde_arc_u8_array")]
41    pub data: Arc<[u8]>,
42    pub ast: Vec<Option<IrAstRef>>,
43    /// Additional information that can be added to help with debugging
44    pub comments: Vec<Box<str>>,
45    pub register_count: u32,
46    pub file_count: u32,
47    /// Local scope regions for inlined nested blocks (see [`ScopeRegion`]).
48    /// Not serialized — only meaningful in the process that compiled the block.
49    #[serde(skip)]
50    pub scope_regions: Vec<ScopeRegion>,
51}
52
53impl fmt::Debug for IrBlock {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        // the ast field is too verbose and doesn't add much
56        f.debug_struct("IrBlock")
57            .field("instructions", &self.instructions)
58            .field("spans", &self.spans)
59            .field("data", &self.data)
60            .field("comments", &self.comments)
61            .field("register_count", &self.register_count)
62            .field("file_count", &self.file_count)
63            .field("scope_regions", &self.scope_regions.len())
64            .finish_non_exhaustive()
65    }
66}
67
68impl IrBlock {
69    /// Returns a value that can be formatted with [`Display`](std::fmt::Display) to show a detailed
70    /// listing of the instructions contained within this [`IrBlock`].
71    pub fn display<'a>(&'a self, engine_state: &'a EngineState) -> FmtIrBlock<'a> {
72        FmtIrBlock {
73            engine_state,
74            ir_block: self,
75        }
76    }
77}
78
79/// A slice into the `data` array of a block. This is a compact and cache-friendly way to store
80/// string data that a block uses.
81#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
82pub struct DataSlice {
83    pub start: u32,
84    pub len: u32,
85}
86
87impl DataSlice {
88    /// A data slice that contains no data. This slice is always valid.
89    pub const fn empty() -> DataSlice {
90        DataSlice { start: 0, len: 0 }
91    }
92}
93
94impl std::ops::Index<DataSlice> for [u8] {
95    type Output = [u8];
96
97    fn index(&self, index: DataSlice) -> &Self::Output {
98        &self[index.start as usize..(index.start as usize + index.len as usize)]
99    }
100}
101
102/// A possible reference into the abstract syntax tree for an instruction. This is not present for
103/// most instructions and is just added when needed.
104#[derive(Debug, Clone)]
105pub struct IrAstRef(pub Arc<Expression>);
106
107impl Serialize for IrAstRef {
108    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
109    where
110        S: serde::Serializer,
111    {
112        self.0.as_ref().serialize(serializer)
113    }
114}
115
116impl<'de> Deserialize<'de> for IrAstRef {
117    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
118    where
119        D: serde::Deserializer<'de>,
120    {
121        Expression::deserialize(deserializer).map(|expr| IrAstRef(Arc::new(expr)))
122    }
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub enum Instruction {
127    /// Unreachable code path (error)
128    Unreachable,
129    /// Load a literal value into the `dst` register
130    LoadLiteral { dst: RegId, lit: Literal },
131    /// Load a clone of a boxed value into the `dst` register (e.g. from const evaluation)
132    LoadValue { dst: RegId, val: Box<Value> },
133    /// Move a register. Value is taken from `src` (used by this instruction).
134    Move { dst: RegId, src: RegId },
135    /// Copy a register (must be a collected value). Value is still in `src` after this instruction.
136    Clone { dst: RegId, src: RegId },
137    /// Collect a stream in a register to a value.
138    /// Because it collects to a value, nushell will ignore the errors in the stream.
139    /// It's important when the stream is from an external command
140    Collect { src_dst: RegId },
141    /// Collect a stream in a register to a value.
142    /// But it's different from `Collect` in that if there is an error in the stream, it will be
143    /// returned as an error instead of being ignored.
144    TryCollect { src_dst: RegId },
145    /// Change the span of the contents of a register to the span of this instruction.
146    Span { src_dst: RegId },
147    /// Drop the value/stream in a register, without draining
148    Drop { src: RegId },
149    /// Drain the value/stream in a register and discard (e.g. semicolon).
150    ///
151    /// If passed a stream from an external command, sets $env.LAST_EXIT_CODE to the resulting exit
152    /// code, and invokes any available error handler with Empty, or if not available, returns an
153    /// exit-code-only stream, leaving the block.
154    Drain { src: RegId },
155    /// Drain the value/stream in a register and discard only if this is the last pipeline element.
156    // TODO: see if it's possible to remove this
157    DrainIfEnd { src: RegId },
158    /// Load the value of a variable into the `dst` register.
159    ///
160    /// When `preserve_origin` is false (the default), the value is re-spanned to this
161    /// instruction's span so error labels point at the use site. When true, the value keeps
162    /// the span from where it was defined (used by `metadata $var`).
163    LoadVariable {
164        dst: RegId,
165        var_id: VarId,
166        #[serde(default)]
167        preserve_origin: bool,
168    },
169    /// Store the value of a variable from the `src` register
170    StoreVariable { var_id: VarId, src: RegId },
171    /// Remove a variable from the stack, freeing up whatever resources were associated with it
172    DropVariable { var_id: VarId },
173    /// Load the value of an environment variable into the `dst` register
174    LoadEnv { dst: RegId, key: DataSlice },
175    /// Load the value of an environment variable into the `dst` register, or `Nothing` if it
176    /// doesn't exist
177    LoadEnvOpt { dst: RegId, key: DataSlice },
178    /// Store the value of an environment variable from the `src` register
179    StoreEnv { key: DataSlice, src: RegId },
180    /// Add a positional arg to the next (internal) call.
181    PushPositional { src: RegId },
182    /// Add a list of args to the next (internal) call (spread/rest).
183    AppendRest { src: RegId },
184    /// Add a named arg with no value to the next (internal) call.
185    PushFlag { name: DataSlice },
186    /// Add a short named arg with no value to the next (internal) call.
187    PushShortFlag { short: DataSlice },
188    /// Add a named arg with a value to the next (internal) call.
189    PushNamed { name: DataSlice, src: RegId },
190    /// Add a short named arg with a value to the next (internal) call.
191    PushShortNamed { short: DataSlice, src: RegId },
192    /// Add parser info to the next (internal) call.
193    PushParserInfo {
194        name: DataSlice,
195        info: Box<Expression>,
196    },
197    /// Set the redirection for stdout for the next call (only).
198    ///
199    /// The register for a file redirection is not consumed.
200    RedirectOut { mode: RedirectMode },
201    /// Set the redirection for stderr for the next call (only).
202    ///
203    /// The register for a file redirection is not consumed.
204    RedirectErr { mode: RedirectMode },
205    /// Throw an error if stderr wasn't redirected in the given stream. `src` is preserved.
206    CheckErrRedirected { src: RegId },
207    /// Open a file for redirection, pushing it onto the file stack.
208    OpenFile {
209        file_num: u32,
210        path: RegId,
211        append: bool,
212    },
213    /// Write data from the register to a file. This is done to finish a file redirection, in case
214    /// an internal command or expression was evaluated rather than an external one.
215    WriteFile { file_num: u32, src: RegId },
216    /// Pop a file used for redirection from the file stack.
217    CloseFile { file_num: u32 },
218    /// Make a call. The input is taken from `src_dst`, and the output is placed in `src_dst`,
219    /// overwriting it. The argument stack is used implicitly and cleared when the call ends.
220    Call { decl_id: DeclId, src_dst: RegId },
221    /// Append a value onto the end of a string. Uses `to_expanded_string(", ", ...)` on the value.
222    /// Used for string interpolation literals. Not the same thing as the `++` operator.
223    StringAppend { src_dst: RegId, val: RegId },
224    /// Convert a string into a glob. Used for glob interpolation and setting glob variables. If the
225    /// value is already a glob, it won't be modified (`no_expand` will have no effect).
226    GlobFrom { src_dst: RegId, no_expand: bool },
227    /// Push a value onto the end of a list. Used to construct list literals.
228    ListPush { src_dst: RegId, item: RegId },
229    /// Spread a value onto the end of a list. Used to construct list literals.
230    ListSpread { src_dst: RegId, items: RegId },
231    /// Insert a key-value pair into a record. Used to construct record literals. Raises an error if
232    /// the key already existed in the record.
233    RecordInsert {
234        src_dst: RegId,
235        key: RegId,
236        val: RegId,
237    },
238    /// Spread a record onto a record. Used to construct record literals. Any existing value for the
239    /// key is overwritten.
240    RecordSpread { src_dst: RegId, items: RegId },
241    /// Negate a boolean.
242    Not { src_dst: RegId },
243    /// Do a binary operation on `lhs_dst` (left) and `rhs` (right) and write the result to
244    /// `lhs_dst`.
245    BinaryOp {
246        lhs_dst: RegId,
247        op: Operator,
248        rhs: RegId,
249    },
250    /// Follow a cell path on the value in `src_dst`, storing the result back to `src_dst`
251    FollowCellPath { src_dst: RegId, path: RegId },
252    /// Clone the value at a cell path in `src`, storing the result to `dst`. The original value
253    /// remains in `src`. Must be a collected value.
254    CloneCellPath { dst: RegId, src: RegId, path: RegId },
255    /// Update/insert a cell path to `new_value` on the value in `src_dst`, storing the modified
256    /// value back to `src_dst`
257    UpsertCellPath {
258        src_dst: RegId,
259        path: RegId,
260        new_value: RegId,
261    },
262    /// Update/insert a cell path directly on a variable in the stack, without cloning the
263    /// variable first. Combines LoadVariable + UpsertCellPath + StoreVariable into a single
264    /// in-place mutation. The variable must be mutable.
265    UpdateVarCellPath {
266        var_id: VarId,
267        cell_path: RegId,
268        new_value: RegId,
269    },
270    /// Jump to an offset in this block
271    Jump { index: usize },
272    /// Branch to an offset in this block if the value of the `cond` register is a true boolean,
273    /// otherwise continue execution
274    BranchIf { cond: RegId, index: usize },
275    /// Branch to an offset in this block if the value of the `src` register is Empty or Nothing,
276    /// otherwise continue execution. The original value in `src` is preserved.
277    BranchIfEmpty { src: RegId, index: usize },
278    /// Match a pattern on `src`. If the pattern matches, branch to `index` after having set any
279    /// variables captured by the pattern. If the pattern doesn't match, continue execution. The
280    /// original value is preserved in `src` through this instruction.
281    Match {
282        pattern: Box<Pattern>,
283        src: RegId,
284        index: usize,
285    },
286    /// Check that a match guard is a boolean, throwing
287    /// [`MatchGuardNotBool`](crate::ShellError::MatchGuardNotBool) if it isn't. Preserves `src`.
288    CheckMatchGuard { src: RegId },
289    /// Iterate on register `stream`, putting the next value in `dst` if present, or jumping to
290    /// `end_index` if the iterator is finished
291    Iterate {
292        dst: RegId,
293        stream: RegId,
294        end_index: usize,
295    },
296    /// Jump to an offset in this block, unwinding through `handlers` entries of the `try`
297    /// handler stack first. This is how `break` and `continue` leave `try` expressions: any
298    /// `finally` block among those handlers runs before the jump completes.
299    UnwindJump { index: usize, handlers: usize },
300    /// Push a `catch` handler, without capturing the error value
301    OnError { index: usize },
302    /// Push a `catch` handler, capturing the error value into `dst`. If the error handler is not
303    /// called, the register should be freed manually.
304    OnErrorInto { index: usize, dst: RegId },
305    /// Push a `finally` handler, capturing the error value into `dst`. If the handler is entered
306    /// for any other reason than an error, `dst` is set to empty instead.
307    FinallyInto { index: usize, dst: RegId },
308    /// Pop a `catch` handler. This is not necessary when control flow is directed to the error
309    /// handler due to an error.
310    PopErrorHandler,
311    /// Mark the `finally` handler on top of the handler stack as running, on the way into its
312    /// block from the `try`/`catch` fall-through path. The unwinder does this itself when it
313    /// enters a `finally` block, so `index` of `finally` points just past this instruction.
314    BeginFinally,
315    /// Leave a `finally` block: pops its running marker and, if the block was entered by
316    /// unwinding (an error, `return`, `exit`, `break`, or `continue`), resumes that unwinding.
317    EndFinally,
318    /// Return early from the block with the value in the register.
319    ///
320    /// Unlike `return`, this runs pending `finally` handlers first (collecting the value in that
321    /// case, like the `try-collect` on the fall-through path), and flags the result as an early
322    /// return. Custom command and closure calls clear that flag; only top-level file evaluation
323    /// reads it, to skip `main`.
324    ReturnEarly { src: RegId },
325    /// Return from the block with the value in the register
326    Return { src: RegId },
327}
328
329impl Instruction {
330    /// Returns a value that can be formatted with [`Display`](std::fmt::Display) to show a detailed
331    /// listing of the instruction.
332    pub fn display<'a>(
333        &'a self,
334        engine_state: &'a EngineState,
335        data: &'a [u8],
336    ) -> FmtInstruction<'a> {
337        FmtInstruction {
338            engine_state,
339            instruction: self,
340            data,
341        }
342    }
343
344    /// Get the output register, for instructions that produce some kind of immediate result.
345    pub fn output_register(&self) -> Option<RegId> {
346        match *self {
347            Instruction::Unreachable => None,
348            Instruction::LoadLiteral { dst, .. } => Some(dst),
349            Instruction::LoadValue { dst, .. } => Some(dst),
350            Instruction::Move { dst, .. } => Some(dst),
351            Instruction::Clone { dst, .. } => Some(dst),
352            Instruction::Collect { src_dst } => Some(src_dst),
353            Instruction::TryCollect { src_dst } => Some(src_dst),
354            Instruction::Span { src_dst } => Some(src_dst),
355            Instruction::Drop { .. } => None,
356            Instruction::Drain { .. } => None,
357            Instruction::DrainIfEnd { .. } => None,
358            Instruction::LoadVariable { dst, .. } => Some(dst),
359            Instruction::StoreVariable { .. } => None,
360            Instruction::DropVariable { .. } => None,
361            Instruction::LoadEnv { dst, .. } => Some(dst),
362            Instruction::LoadEnvOpt { dst, .. } => Some(dst),
363            Instruction::StoreEnv { .. } => None,
364            Instruction::PushPositional { .. } => None,
365            Instruction::AppendRest { .. } => None,
366            Instruction::PushFlag { .. } => None,
367            Instruction::PushShortFlag { .. } => None,
368            Instruction::PushNamed { .. } => None,
369            Instruction::PushShortNamed { .. } => None,
370            Instruction::PushParserInfo { .. } => None,
371            Instruction::RedirectOut { .. } => None,
372            Instruction::RedirectErr { .. } => None,
373            Instruction::CheckErrRedirected { .. } => None,
374            Instruction::OpenFile { .. } => None,
375            Instruction::WriteFile { .. } => None,
376            Instruction::CloseFile { .. } => None,
377            Instruction::Call { src_dst, .. } => Some(src_dst),
378            Instruction::StringAppend { src_dst, .. } => Some(src_dst),
379            Instruction::GlobFrom { src_dst, .. } => Some(src_dst),
380            Instruction::ListPush { src_dst, .. } => Some(src_dst),
381            Instruction::ListSpread { src_dst, .. } => Some(src_dst),
382            Instruction::RecordInsert { src_dst, .. } => Some(src_dst),
383            Instruction::RecordSpread { src_dst, .. } => Some(src_dst),
384            Instruction::Not { src_dst } => Some(src_dst),
385            Instruction::BinaryOp { lhs_dst, .. } => Some(lhs_dst),
386            Instruction::FollowCellPath { src_dst, .. } => Some(src_dst),
387            Instruction::CloneCellPath { dst, .. } => Some(dst),
388            Instruction::UpsertCellPath { src_dst, .. } => Some(src_dst),
389            Instruction::UpdateVarCellPath { .. } => None,
390            Instruction::Jump { .. } => None,
391            Instruction::UnwindJump { .. } => None,
392            Instruction::BranchIf { .. } => None,
393            Instruction::BranchIfEmpty { .. } => None,
394            Instruction::Match { .. } => None,
395            Instruction::CheckMatchGuard { .. } => None,
396            Instruction::Iterate { dst, .. } => Some(dst),
397            Instruction::OnError { .. } => None,
398            Instruction::OnErrorInto { .. } => None,
399            Instruction::FinallyInto { .. } => None,
400            Instruction::PopErrorHandler => None,
401            Instruction::BeginFinally => None,
402            Instruction::EndFinally => None,
403            Instruction::ReturnEarly { .. } => None,
404            Instruction::Return { .. } => None,
405        }
406    }
407
408    /// Returns the branch target index of the instruction if this is a branching instruction.
409    pub fn branch_target(&self) -> Option<usize> {
410        match self {
411            Instruction::Jump { index } => Some(*index),
412            Instruction::UnwindJump { index, handlers: _ } => Some(*index),
413            Instruction::BranchIf { cond: _, index } => Some(*index),
414            Instruction::BranchIfEmpty { src: _, index } => Some(*index),
415            Instruction::Match {
416                pattern: _,
417                src: _,
418                index,
419            } => Some(*index),
420
421            Instruction::Iterate {
422                dst: _,
423                stream: _,
424                end_index,
425            } => Some(*end_index),
426            Instruction::OnError { index } => Some(*index),
427            Instruction::OnErrorInto { index, dst: _ } => Some(*index),
428            Instruction::FinallyInto { index, dst: _ } => Some(*index),
429            _ => None,
430        }
431    }
432
433    /// Sets the branch target of the instruction if this is a branching instruction.
434    ///
435    /// Returns `Err(target_index)` if it isn't a branching instruction.
436    pub fn set_branch_target(&mut self, target_index: usize) -> Result<(), usize> {
437        match self {
438            Instruction::Jump { index } => *index = target_index,
439            Instruction::UnwindJump { index, handlers: _ } => *index = target_index,
440            Instruction::BranchIf { cond: _, index } => *index = target_index,
441            Instruction::BranchIfEmpty { src: _, index } => *index = target_index,
442            Instruction::Match {
443                pattern: _,
444                src: _,
445                index,
446            } => *index = target_index,
447
448            Instruction::Iterate {
449                dst: _,
450                stream: _,
451                end_index,
452            } => *end_index = target_index,
453            Instruction::OnError { index } => *index = target_index,
454            Instruction::OnErrorInto { index, dst: _ } => *index = target_index,
455            Instruction::FinallyInto { index, dst: _ } => *index = target_index,
456            _ => return Err(target_index),
457        }
458        Ok(())
459    }
460
461    /// Check for an interrupt before certain instructions
462    pub fn check_interrupt(
463        &self,
464        engine_state: &EngineState,
465        span: &Span,
466    ) -> Result<(), ShellError> {
467        match self {
468            Instruction::Jump { .. }
469            | Instruction::UnwindJump { .. }
470            | Instruction::Return { .. } => engine_state.signals().check(span),
471            _ => Ok(()),
472        }
473    }
474}
475
476// This is to document/enforce the size of `Instruction` in bytes.
477// We should try to avoid increasing the size of `Instruction`,
478// and PRs that do so will have to change the number below so that it's noted in review.
479const _: () = assert!(std::mem::size_of::<Instruction>() <= 24);
480
481/// A literal value that can be embedded in an instruction.
482#[derive(Debug, Clone, Serialize, Deserialize)]
483pub enum Literal {
484    Bool(bool),
485    Int(i64),
486    Float(f64),
487    Filesize(Filesize),
488    Duration(i64),
489    Binary(DataSlice),
490    Block(BlockId),
491    Closure(BlockId),
492    RowCondition(BlockId),
493    Range {
494        start: RegId,
495        step: RegId,
496        end: RegId,
497        inclusion: RangeInclusion,
498    },
499    List {
500        capacity: usize,
501    },
502    Record {
503        capacity: usize,
504    },
505    Filepath {
506        val: DataSlice,
507        no_expand: bool,
508    },
509    Directory {
510        val: DataSlice,
511        no_expand: bool,
512    },
513    GlobPattern {
514        val: DataSlice,
515        no_expand: bool,
516    },
517    String(DataSlice),
518    RawString(DataSlice),
519    CellPath(Box<CellPath>),
520    Date(Box<DateTime<FixedOffset>>),
521    Nothing,
522    /// Represents an empty pipeline input (distinct from `Nothing` which is the `null` value).
523    /// Used by `load_empty` to initialize registers with no input.
524    Empty,
525}
526
527/// A redirection mode for the next call. See [`OutDest`](crate::OutDest).
528///
529/// This is generated by:
530///
531/// 1. Explicit redirection in a [`PipelineElement`](crate::ast::PipelineElement), or
532/// 2. The [`pipe_redirection()`](crate::engine::Command::pipe_redirection) of the command being
533///    piped into.
534///
535/// Not setting it uses the default, determined by [`Stack`](crate::engine::Stack).
536#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
537pub enum RedirectMode {
538    Pipe,
539    PipeSeparate,
540    Value,
541    Null,
542    Inherit,
543    Print,
544    /// Use the given numbered file.
545    File {
546        file_num: u32,
547    },
548    /// Use the redirection mode requested by the caller, for a pre-return call.
549    Caller,
550}
551
552/// Just a hack to allow `Arc<[u8]>` to be serialized and deserialized
553mod serde_arc_u8_array {
554    use serde::{Deserialize, Serialize};
555    use std::sync::Arc;
556
557    pub fn serialize<S>(data: &Arc<[u8]>, ser: S) -> Result<S::Ok, S::Error>
558    where
559        S: serde::Serializer,
560    {
561        data.as_ref().serialize(ser)
562    }
563
564    pub fn deserialize<'de, D>(de: D) -> Result<Arc<[u8]>, D::Error>
565    where
566        D: serde::Deserializer<'de>,
567    {
568        let data: Vec<u8> = Deserialize::deserialize(de)?;
569        Ok(data.into())
570    }
571}