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 LoadVariable { dst: RegId, var_id: VarId },
160 /// Store the value of a variable from the `src` register
161 StoreVariable { var_id: VarId, src: RegId },
162 /// Remove a variable from the stack, freeing up whatever resources were associated with it
163 DropVariable { var_id: VarId },
164 /// Load the value of an environment variable into the `dst` register
165 LoadEnv { dst: RegId, key: DataSlice },
166 /// Load the value of an environment variable into the `dst` register, or `Nothing` if it
167 /// doesn't exist
168 LoadEnvOpt { dst: RegId, key: DataSlice },
169 /// Store the value of an environment variable from the `src` register
170 StoreEnv { key: DataSlice, src: RegId },
171 /// Add a positional arg to the next (internal) call.
172 PushPositional { src: RegId },
173 /// Add a list of args to the next (internal) call (spread/rest).
174 AppendRest { src: RegId },
175 /// Add a named arg with no value to the next (internal) call.
176 PushFlag { name: DataSlice },
177 /// Add a short named arg with no value to the next (internal) call.
178 PushShortFlag { short: DataSlice },
179 /// Add a named arg with a value to the next (internal) call.
180 PushNamed { name: DataSlice, src: RegId },
181 /// Add a short named arg with a value to the next (internal) call.
182 PushShortNamed { short: DataSlice, src: RegId },
183 /// Add parser info to the next (internal) call.
184 PushParserInfo {
185 name: DataSlice,
186 info: Box<Expression>,
187 },
188 /// Set the redirection for stdout for the next call (only).
189 ///
190 /// The register for a file redirection is not consumed.
191 RedirectOut { mode: RedirectMode },
192 /// Set the redirection for stderr for the next call (only).
193 ///
194 /// The register for a file redirection is not consumed.
195 RedirectErr { mode: RedirectMode },
196 /// Throw an error if stderr wasn't redirected in the given stream. `src` is preserved.
197 CheckErrRedirected { src: RegId },
198 /// Open a file for redirection, pushing it onto the file stack.
199 OpenFile {
200 file_num: u32,
201 path: RegId,
202 append: bool,
203 },
204 /// Write data from the register to a file. This is done to finish a file redirection, in case
205 /// an internal command or expression was evaluated rather than an external one.
206 WriteFile { file_num: u32, src: RegId },
207 /// Pop a file used for redirection from the file stack.
208 CloseFile { file_num: u32 },
209 /// Make a call. The input is taken from `src_dst`, and the output is placed in `src_dst`,
210 /// overwriting it. The argument stack is used implicitly and cleared when the call ends.
211 Call { decl_id: DeclId, src_dst: RegId },
212 /// Append a value onto the end of a string. Uses `to_expanded_string(", ", ...)` on the value.
213 /// Used for string interpolation literals. Not the same thing as the `++` operator.
214 StringAppend { src_dst: RegId, val: RegId },
215 /// Convert a string into a glob. Used for glob interpolation and setting glob variables. If the
216 /// value is already a glob, it won't be modified (`no_expand` will have no effect).
217 GlobFrom { src_dst: RegId, no_expand: bool },
218 /// Push a value onto the end of a list. Used to construct list literals.
219 ListPush { src_dst: RegId, item: RegId },
220 /// Spread a value onto the end of a list. Used to construct list literals.
221 ListSpread { src_dst: RegId, items: RegId },
222 /// Insert a key-value pair into a record. Used to construct record literals. Raises an error if
223 /// the key already existed in the record.
224 RecordInsert {
225 src_dst: RegId,
226 key: RegId,
227 val: RegId,
228 },
229 /// Spread a record onto a record. Used to construct record literals. Any existing value for the
230 /// key is overwritten.
231 RecordSpread { src_dst: RegId, items: RegId },
232 /// Negate a boolean.
233 Not { src_dst: RegId },
234 /// Do a binary operation on `lhs_dst` (left) and `rhs` (right) and write the result to
235 /// `lhs_dst`.
236 BinaryOp {
237 lhs_dst: RegId,
238 op: Operator,
239 rhs: RegId,
240 },
241 /// Follow a cell path on the value in `src_dst`, storing the result back to `src_dst`
242 FollowCellPath { src_dst: RegId, path: RegId },
243 /// Clone the value at a cell path in `src`, storing the result to `dst`. The original value
244 /// remains in `src`. Must be a collected value.
245 CloneCellPath { dst: RegId, src: RegId, path: RegId },
246 /// Update/insert a cell path to `new_value` on the value in `src_dst`, storing the modified
247 /// value back to `src_dst`
248 UpsertCellPath {
249 src_dst: RegId,
250 path: RegId,
251 new_value: RegId,
252 },
253 /// Update/insert a cell path directly on a variable in the stack, without cloning the
254 /// variable first. Combines LoadVariable + UpsertCellPath + StoreVariable into a single
255 /// in-place mutation. The variable must be mutable.
256 UpdateVarCellPath {
257 var_id: VarId,
258 cell_path: RegId,
259 new_value: RegId,
260 },
261 /// Jump to an offset in this block
262 Jump { index: usize },
263 /// Branch to an offset in this block if the value of the `cond` register is a true boolean,
264 /// otherwise continue execution
265 BranchIf { cond: RegId, index: usize },
266 /// Branch to an offset in this block if the value of the `src` register is Empty or Nothing,
267 /// otherwise continue execution. The original value in `src` is preserved.
268 BranchIfEmpty { src: RegId, index: usize },
269 /// Match a pattern on `src`. If the pattern matches, branch to `index` after having set any
270 /// variables captured by the pattern. If the pattern doesn't match, continue execution. The
271 /// original value is preserved in `src` through this instruction.
272 Match {
273 pattern: Box<Pattern>,
274 src: RegId,
275 index: usize,
276 },
277 /// Check that a match guard is a boolean, throwing
278 /// [`MatchGuardNotBool`](crate::ShellError::MatchGuardNotBool) if it isn't. Preserves `src`.
279 CheckMatchGuard { src: RegId },
280 /// Iterate on register `stream`, putting the next value in `dst` if present, or jumping to
281 /// `end_index` if the iterator is finished
282 Iterate {
283 dst: RegId,
284 stream: RegId,
285 end_index: usize,
286 },
287 /// Push an error handler, without capturing the error value
288 OnError { index: usize },
289 /// Push an error handler, capturing the error value into `dst`. If the error handler is not
290 /// called, the register should be freed manually.
291 OnErrorInto { index: usize, dst: RegId },
292 /// Push an finally handler, without capturing the error value
293 Finally { index: usize },
294 /// Push an finally handler, capturing the error value into `dst`. If the finally handler is not
295 /// called, the register should be freed manually.
296 FinallyInto { index: usize, dst: RegId },
297 /// Pop an error handler. This is not necessary when control flow is directed to the error
298 /// handler due to an error.
299 PopErrorHandler,
300 /// Pop an finally handler.
301 PopFinallyRun,
302 /// Return early from the block with the value in the register.
303 ///
304 /// Unlike `return`, this runs pending `finally` handlers first (collecting the value in that
305 /// case, like the `try-collect` on the fall-through path), and flags the result as an early
306 /// return. Custom command and closure calls clear that flag; only top-level file evaluation
307 /// reads it, to skip `main`.
308 ReturnEarly { src: RegId },
309 /// Return from the block with the value in the register
310 Return { src: RegId },
311}
312
313impl Instruction {
314 /// Returns a value that can be formatted with [`Display`](std::fmt::Display) to show a detailed
315 /// listing of the instruction.
316 pub fn display<'a>(
317 &'a self,
318 engine_state: &'a EngineState,
319 data: &'a [u8],
320 ) -> FmtInstruction<'a> {
321 FmtInstruction {
322 engine_state,
323 instruction: self,
324 data,
325 }
326 }
327
328 /// Get the output register, for instructions that produce some kind of immediate result.
329 pub fn output_register(&self) -> Option<RegId> {
330 match *self {
331 Instruction::Unreachable => None,
332 Instruction::LoadLiteral { dst, .. } => Some(dst),
333 Instruction::LoadValue { dst, .. } => Some(dst),
334 Instruction::Move { dst, .. } => Some(dst),
335 Instruction::Clone { dst, .. } => Some(dst),
336 Instruction::Collect { src_dst } => Some(src_dst),
337 Instruction::TryCollect { src_dst } => Some(src_dst),
338 Instruction::Span { src_dst } => Some(src_dst),
339 Instruction::Drop { .. } => None,
340 Instruction::Drain { .. } => None,
341 Instruction::DrainIfEnd { .. } => None,
342 Instruction::LoadVariable { dst, .. } => Some(dst),
343 Instruction::StoreVariable { .. } => None,
344 Instruction::DropVariable { .. } => None,
345 Instruction::LoadEnv { dst, .. } => Some(dst),
346 Instruction::LoadEnvOpt { dst, .. } => Some(dst),
347 Instruction::StoreEnv { .. } => None,
348 Instruction::PushPositional { .. } => None,
349 Instruction::AppendRest { .. } => None,
350 Instruction::PushFlag { .. } => None,
351 Instruction::PushShortFlag { .. } => None,
352 Instruction::PushNamed { .. } => None,
353 Instruction::PushShortNamed { .. } => None,
354 Instruction::PushParserInfo { .. } => None,
355 Instruction::RedirectOut { .. } => None,
356 Instruction::RedirectErr { .. } => None,
357 Instruction::CheckErrRedirected { .. } => None,
358 Instruction::OpenFile { .. } => None,
359 Instruction::WriteFile { .. } => None,
360 Instruction::CloseFile { .. } => None,
361 Instruction::Call { src_dst, .. } => Some(src_dst),
362 Instruction::StringAppend { src_dst, .. } => Some(src_dst),
363 Instruction::GlobFrom { src_dst, .. } => Some(src_dst),
364 Instruction::ListPush { src_dst, .. } => Some(src_dst),
365 Instruction::ListSpread { src_dst, .. } => Some(src_dst),
366 Instruction::RecordInsert { src_dst, .. } => Some(src_dst),
367 Instruction::RecordSpread { src_dst, .. } => Some(src_dst),
368 Instruction::Not { src_dst } => Some(src_dst),
369 Instruction::BinaryOp { lhs_dst, .. } => Some(lhs_dst),
370 Instruction::FollowCellPath { src_dst, .. } => Some(src_dst),
371 Instruction::CloneCellPath { dst, .. } => Some(dst),
372 Instruction::UpsertCellPath { src_dst, .. } => Some(src_dst),
373 Instruction::UpdateVarCellPath { .. } => None,
374 Instruction::Jump { .. } => None,
375 Instruction::BranchIf { .. } => None,
376 Instruction::BranchIfEmpty { .. } => None,
377 Instruction::Match { .. } => None,
378 Instruction::CheckMatchGuard { .. } => None,
379 Instruction::Iterate { dst, .. } => Some(dst),
380 Instruction::OnError { .. } => None,
381 Instruction::Finally { .. } => None,
382 Instruction::OnErrorInto { .. } => None,
383 Instruction::FinallyInto { .. } => None,
384 Instruction::PopErrorHandler => None,
385 Instruction::PopFinallyRun => None,
386 Instruction::ReturnEarly { .. } => None,
387 Instruction::Return { .. } => None,
388 }
389 }
390
391 /// Returns the branch target index of the instruction if this is a branching instruction.
392 pub fn branch_target(&self) -> Option<usize> {
393 match self {
394 Instruction::Jump { index } => Some(*index),
395 Instruction::BranchIf { cond: _, index } => Some(*index),
396 Instruction::BranchIfEmpty { src: _, index } => Some(*index),
397 Instruction::Match {
398 pattern: _,
399 src: _,
400 index,
401 } => Some(*index),
402
403 Instruction::Iterate {
404 dst: _,
405 stream: _,
406 end_index,
407 } => Some(*end_index),
408 Instruction::OnError { index } => Some(*index),
409 Instruction::OnErrorInto { index, dst: _ } => Some(*index),
410 Instruction::Finally { index } => Some(*index),
411 Instruction::FinallyInto { index, dst: _ } => Some(*index),
412 _ => None,
413 }
414 }
415
416 /// Sets the branch target of the instruction if this is a branching instruction.
417 ///
418 /// Returns `Err(target_index)` if it isn't a branching instruction.
419 pub fn set_branch_target(&mut self, target_index: usize) -> Result<(), usize> {
420 match self {
421 Instruction::Jump { index } => *index = target_index,
422 Instruction::BranchIf { cond: _, index } => *index = target_index,
423 Instruction::BranchIfEmpty { src: _, index } => *index = target_index,
424 Instruction::Match {
425 pattern: _,
426 src: _,
427 index,
428 } => *index = target_index,
429
430 Instruction::Iterate {
431 dst: _,
432 stream: _,
433 end_index,
434 } => *end_index = target_index,
435 Instruction::OnError { index } => *index = target_index,
436 Instruction::OnErrorInto { index, dst: _ } => *index = target_index,
437 Instruction::Finally { index } => *index = target_index,
438 Instruction::FinallyInto { index, dst: _ } => *index = target_index,
439 _ => return Err(target_index),
440 }
441 Ok(())
442 }
443
444 /// Check for an interrupt before certain instructions
445 pub fn check_interrupt(
446 &self,
447 engine_state: &EngineState,
448 span: &Span,
449 ) -> Result<(), ShellError> {
450 match self {
451 Instruction::Jump { .. } | Instruction::Return { .. } => {
452 engine_state.signals().check(span)
453 }
454 _ => Ok(()),
455 }
456 }
457}
458
459// This is to document/enforce the size of `Instruction` in bytes.
460// We should try to avoid increasing the size of `Instruction`,
461// and PRs that do so will have to change the number below so that it's noted in review.
462const _: () = assert!(std::mem::size_of::<Instruction>() <= 24);
463
464/// A literal value that can be embedded in an instruction.
465#[derive(Debug, Clone, Serialize, Deserialize)]
466pub enum Literal {
467 Bool(bool),
468 Int(i64),
469 Float(f64),
470 Filesize(Filesize),
471 Duration(i64),
472 Binary(DataSlice),
473 Block(BlockId),
474 Closure(BlockId),
475 RowCondition(BlockId),
476 Range {
477 start: RegId,
478 step: RegId,
479 end: RegId,
480 inclusion: RangeInclusion,
481 },
482 List {
483 capacity: usize,
484 },
485 Record {
486 capacity: usize,
487 },
488 Filepath {
489 val: DataSlice,
490 no_expand: bool,
491 },
492 Directory {
493 val: DataSlice,
494 no_expand: bool,
495 },
496 GlobPattern {
497 val: DataSlice,
498 no_expand: bool,
499 },
500 String(DataSlice),
501 RawString(DataSlice),
502 CellPath(Box<CellPath>),
503 Date(Box<DateTime<FixedOffset>>),
504 Nothing,
505 /// Represents an empty pipeline input (distinct from `Nothing` which is the `null` value).
506 /// Used by `load_empty` to initialize registers with no input.
507 Empty,
508}
509
510/// A redirection mode for the next call. See [`OutDest`](crate::OutDest).
511///
512/// This is generated by:
513///
514/// 1. Explicit redirection in a [`PipelineElement`](crate::ast::PipelineElement), or
515/// 2. The [`pipe_redirection()`](crate::engine::Command::pipe_redirection) of the command being
516/// piped into.
517///
518/// Not setting it uses the default, determined by [`Stack`](crate::engine::Stack).
519#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
520pub enum RedirectMode {
521 Pipe,
522 PipeSeparate,
523 Value,
524 Null,
525 Inherit,
526 Print,
527 /// Use the given numbered file.
528 File {
529 file_num: u32,
530 },
531 /// Use the redirection mode requested by the caller, for a pre-return call.
532 Caller,
533}
534
535/// Just a hack to allow `Arc<[u8]>` to be serialized and deserialized
536mod serde_arc_u8_array {
537 use serde::{Deserialize, Serialize};
538 use std::sync::Arc;
539
540 pub fn serialize<S>(data: &Arc<[u8]>, ser: S) -> Result<S::Ok, S::Error>
541 where
542 S: serde::Serializer,
543 {
544 data.as_ref().serialize(ser)
545 }
546
547 pub fn deserialize<'de, D>(de: D) -> Result<Arc<[u8]>, D::Error>
548 where
549 D: serde::Deserializer<'de>,
550 {
551 let data: Vec<u8> = Deserialize::deserialize(de)?;
552 Ok(data.into())
553 }
554}