Add transitive use_ir field to Stack and add --use-ir option to nu

This commit is contained in:
Devyn Cairns 2024-06-26 17:28:00 -07:00
parent 73b092bfdc
commit fd0b99554d
No known key found for this signature in database
9 changed files with 102 additions and 34 deletions

View File

@ -1,6 +1,4 @@
use nu_engine::{ use nu_engine::{command_prelude::*, get_eval_block_with_early_return, redirect_env};
command_prelude::*, get_eval_block_with_early_return, get_eval_ir_block, redirect_env,
};
use nu_protocol::{ use nu_protocol::{
engine::Closure, engine::Closure,
process::{ChildPipe, ChildProcess, ExitStatus}, process::{ChildPipe, ChildProcess, ExitStatus},
@ -94,12 +92,13 @@ impl Command for Do {
bind_args_to(&mut callee_stack, &block.signature, rest, head)?; bind_args_to(&mut callee_stack, &block.signature, rest, head)?;
let eval_block_with_early_return = get_eval_block_with_early_return(engine_state); let eval_block_with_early_return = get_eval_block_with_early_return(engine_state);
let eval_ir_block = get_eval_ir_block(engine_state);
let result = if use_ir { // Applies to all block evaluation once set true
eval_ir_block(engine_state, &mut callee_stack, block, input) if use_ir {
} else { caller_stack.use_ir = true;
eval_block_with_early_return(engine_state, &mut callee_stack, block, input) }
};
let result = eval_block_with_early_return(engine_state, &mut callee_stack, block, input);
if has_env { if has_env {
// Merge the block's environment to the current stack // Merge the block's environment to the current stack

View File

@ -1,3 +1,5 @@
use std::borrow::Cow;
use nu_engine::{command_prelude::*, compile}; use nu_engine::{command_prelude::*, compile};
use nu_protocol::engine::Closure; use nu_protocol::engine::Closure;
@ -31,7 +33,15 @@ impl Command for ViewIr {
let closure: Closure = call.req(engine_state, stack, 0)?; let closure: Closure = call.req(engine_state, stack, 0)?;
let block = engine_state.get_block(closure.block_id); let block = engine_state.get_block(closure.block_id);
let ir_block = compile(&StateWorkingSet::new(engine_state), &block)?; // Use the pre-compiled block if available, otherwise try to compile it
// This helps display the actual compilation error
let ir_block = match &block.ir_block {
Some(ir_block) => Cow::Borrowed(ir_block),
None => Cow::Owned(
compile(&StateWorkingSet::new(engine_state), &block)
.map_err(|err| err.to_shell_error(block.span))?,
),
};
let formatted = format!("{}", ir_block.display(engine_state)); let formatted = format!("{}", ir_block.display(engine_state));
Ok(Value::string(formatted, call.head).into_pipeline_data()) Ok(Value::string(formatted, call.head).into_pipeline_data())

View File

@ -1,6 +1,6 @@
use nu_protocol::{ use nu_protocol::{
ast::{ ast::{
Argument, Block, Call, CellPath, Expr, Expression, ListItem, Math, Operator, PathMember, Argument, Block, Call, CellPath, Expr, Expression, ListItem, Operator, PathMember,
Pipeline, PipelineRedirection, RecordItem, RedirectionSource, RedirectionTarget, Pipeline, PipelineRedirection, RecordItem, RedirectionSource, RedirectionTarget,
}, },
engine::StateWorkingSet, engine::StateWorkingSet,
@ -12,7 +12,7 @@ const BLOCK_INPUT: RegId = RegId(0);
/// Compile Nushell pipeline abstract syntax tree (AST) to internal representation (IR) instructions /// Compile Nushell pipeline abstract syntax tree (AST) to internal representation (IR) instructions
/// for evaluation. /// for evaluation.
pub fn compile(working_set: &StateWorkingSet, block: &Block) -> Result<IrBlock, ShellError> { pub fn compile(working_set: &StateWorkingSet, block: &Block) -> Result<IrBlock, CompileError> {
let mut builder = BlockBuilder::new(); let mut builder = BlockBuilder::new();
compile_block( compile_block(
@ -22,16 +22,13 @@ pub fn compile(working_set: &StateWorkingSet, block: &Block) -> Result<IrBlock,
RedirectModes::default(), RedirectModes::default(),
Some(BLOCK_INPUT), Some(BLOCK_INPUT),
BLOCK_INPUT, BLOCK_INPUT,
) )?;
.map_err(|err| err.to_shell_error(block.span))?;
// A complete block has to end with a `return` // A complete block has to end with a `return`
builder builder.push(
.push( Instruction::Return { src: BLOCK_INPUT }
Instruction::Return { src: BLOCK_INPUT } .into_spanned(block.span.unwrap_or(Span::unknown())),
.into_spanned(block.span.unwrap_or(Span::unknown())), )?;
)
.map_err(|err| err.to_shell_error(block.span))?;
Ok(builder.finish()) Ok(builder.finish())
} }
@ -369,7 +366,7 @@ fn compile_expression(
.into_spanned(expr.span), .into_spanned(expr.span),
)?; )?;
} }
ListItem::Spread(spread_span, expr) => { ListItem::Spread(spread_span, _) => {
// Spread the list using list-spread // Spread the list using list-spread
builder.push( builder.push(
Instruction::ListSpread { Instruction::ListSpread {
@ -795,7 +792,7 @@ fn compile_load_env(
/// An internal compiler error, generally means a Nushell bug rather than an issue with user error /// An internal compiler error, generally means a Nushell bug rather than an issue with user error
/// since parsing and typechecking has already passed. /// since parsing and typechecking has already passed.
#[derive(Debug)] #[derive(Debug)]
enum CompileError { pub enum CompileError {
RegisterOverflow, RegisterOverflow,
RegisterUninitialized(RegId), RegisterUninitialized(RegId),
DataOverflow, DataOverflow,
@ -807,8 +804,8 @@ enum CompileError {
} }
impl CompileError { impl CompileError {
fn to_shell_error(self, mut span: Option<Span>) -> ShellError { pub fn message(&self) -> String {
let message = match self { match self {
CompileError::RegisterOverflow => format!("register overflow"), CompileError::RegisterOverflow => format!("register overflow"),
CompileError::RegisterUninitialized(reg_id) => { CompileError::RegisterUninitialized(reg_id) => {
format!("register {reg_id} is uninitialized when used, possibly reused") format!("register {reg_id} is uninitialized when used, possibly reused")
@ -821,15 +818,25 @@ impl CompileError {
} }
CompileError::Garbage => "encountered garbage, likely due to parse error".into(), CompileError::Garbage => "encountered garbage, likely due to parse error".into(),
CompileError::UnsupportedOperatorExpression => "unsupported operator expression".into(), CompileError::UnsupportedOperatorExpression => "unsupported operator expression".into(),
CompileError::AccessEnvByInt(local_span) => { CompileError::AccessEnvByInt(_) => "attempted access of $env by integer path".into(),
span = Some(local_span);
"attempted access of $env by integer path".into()
}
CompileError::Todo(msg) => { CompileError::Todo(msg) => {
format!("TODO: {msg}") format!("TODO: {msg}")
} }
}; }
ShellError::IrCompileError { msg: message, span } }
pub fn span(&self) -> Option<Span> {
match self {
CompileError::AccessEnvByInt(span) => Some(*span),
_ => None,
}
}
pub fn to_shell_error(self, span: Option<Span>) -> ShellError {
ShellError::IrCompileError {
msg: self.message(),
span: self.span().or(span),
}
} }
} }

View File

@ -1,3 +1,4 @@
use crate::eval_ir_block;
#[allow(deprecated)] #[allow(deprecated)]
use crate::{current_dir, get_config, get_full_help}; use crate::{current_dir, get_config, get_full_help};
use nu_path::expand_path_with; use nu_path::expand_path_with;
@ -509,6 +510,11 @@ pub fn eval_block<D: DebugContext>(
block: &Block, block: &Block,
mut input: PipelineData, mut input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
// Remove once IR is the default.
if stack.use_ir {
return eval_ir_block::<D>(engine_state, stack, block, input);
}
D::enter_block(engine_state, block); D::enter_block(engine_state, block);
let num_pipelines = block.len(); let num_pipelines = block.len();

View File

@ -11,8 +11,8 @@ use itertools::Itertools;
use log::trace; use log::trace;
use nu_engine::DIR_VAR_PARSER_INFO; use nu_engine::DIR_VAR_PARSER_INFO;
use nu_protocol::{ use nu_protocol::{
ast::*, engine::StateWorkingSet, eval_const::eval_constant, report_error, BlockId, DidYouMean, ast::*, engine::StateWorkingSet, eval_const::eval_constant, BlockId, DidYouMean, Flag,
Flag, ParseError, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, VarId, ParseError, ParseWarning, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, VarId,
ENV_VARIABLE_ID, IN_VARIABLE_ID, ENV_VARIABLE_ID, IN_VARIABLE_ID,
}; };
use std::{ use std::{
@ -5834,12 +5834,19 @@ pub fn parse_block(
working_set.parse_errors.extend_from_slice(&errors); working_set.parse_errors.extend_from_slice(&errors);
} }
if !is_subexpression && errors.is_empty() { // Do not try to compile blocks that are subexpressions, or when we've already had a parse
// failure as that definitely will fail to compile
if !is_subexpression && working_set.parse_errors.is_empty() {
match nu_engine::compile(working_set, &block) { match nu_engine::compile(working_set, &block) {
Ok(ir_block) => { Ok(ir_block) => {
block.ir_block = Some(ir_block); block.ir_block = Some(ir_block);
} }
Err(err) => report_error(working_set, &err), Err(err) => working_set
.parse_warnings
.push(ParseWarning::IrCompileError {
msg: err.message(),
span,
}),
} }
} }

View File

@ -47,6 +47,8 @@ pub struct Stack {
pub register_buf_cache: RegisterBufCache, pub register_buf_cache: RegisterBufCache,
/// Argument stack for IR evaluation /// Argument stack for IR evaluation
pub argument_stack: ArgumentStack, pub argument_stack: ArgumentStack,
/// Set true to always use IR mode
pub use_ir: bool,
pub recursion_count: u64, pub recursion_count: u64,
pub parent_stack: Option<Arc<Stack>>, pub parent_stack: Option<Arc<Stack>>,
/// Variables that have been deleted (this is used to hide values from parent stack lookups) /// Variables that have been deleted (this is used to hide values from parent stack lookups)
@ -76,6 +78,7 @@ impl Stack {
active_overlays: vec![DEFAULT_OVERLAY_NAME.to_string()], active_overlays: vec![DEFAULT_OVERLAY_NAME.to_string()],
register_buf_cache: RegisterBufCache::new(), register_buf_cache: RegisterBufCache::new(),
argument_stack: ArgumentStack::new(), argument_stack: ArgumentStack::new(),
use_ir: false,
recursion_count: 0, recursion_count: 0,
parent_stack: None, parent_stack: None,
parent_deletions: vec![], parent_deletions: vec![],
@ -95,6 +98,7 @@ impl Stack {
active_overlays: parent.active_overlays.clone(), active_overlays: parent.active_overlays.clone(),
register_buf_cache: RegisterBufCache::new(), register_buf_cache: RegisterBufCache::new(),
argument_stack: ArgumentStack::new(), argument_stack: ArgumentStack::new(),
use_ir: parent.use_ir,
recursion_count: parent.recursion_count, recursion_count: parent.recursion_count,
vars: vec![], vars: vec![],
parent_deletions: vec![], parent_deletions: vec![],
@ -266,6 +270,7 @@ impl Stack {
active_overlays: self.active_overlays.clone(), active_overlays: self.active_overlays.clone(),
register_buf_cache: RegisterBufCache::new(), register_buf_cache: RegisterBufCache::new(),
argument_stack: ArgumentStack::new(), argument_stack: ArgumentStack::new(),
use_ir: self.use_ir,
recursion_count: self.recursion_count, recursion_count: self.recursion_count,
parent_stack: None, parent_stack: None,
parent_deletions: vec![], parent_deletions: vec![],
@ -298,6 +303,7 @@ impl Stack {
active_overlays: self.active_overlays.clone(), active_overlays: self.active_overlays.clone(),
register_buf_cache: RegisterBufCache::new(), register_buf_cache: RegisterBufCache::new(),
argument_stack: ArgumentStack::new(), argument_stack: ArgumentStack::new(),
use_ir: self.use_ir,
recursion_count: self.recursion_count, recursion_count: self.recursion_count,
parent_stack: None, parent_stack: None,
parent_deletions: vec![], parent_deletions: vec![],

View File

@ -14,12 +14,29 @@ pub enum ParseWarning {
span: Span, span: Span,
url: String, url: String,
}, },
/// An error occurred with the IR compiler.
///
/// ## Resolution
///
/// The IR compiler is in very early development, so code that can't be compiled is quite
/// expected. If you think it should be working, please report it to us.
#[error("internal compiler error: {msg}")]
#[diagnostic(
help("this is a bug, please report it at https://github.com/nushell/nushell/issues/new along with the code you were compiling if able")
)]
IrCompileError {
msg: String,
#[label = "while compiling this code"]
span: Span,
},
} }
impl ParseWarning { impl ParseWarning {
pub fn span(&self) -> Span { pub fn span(&self) -> Span {
match self { match self {
ParseWarning::DeprecatedWarning { span, .. } => *span, ParseWarning::DeprecatedWarning { span, .. } => *span,
ParseWarning::IrCompileError { span, .. } => *span,
} }
} }
} }

View File

@ -107,6 +107,7 @@ pub(crate) fn parse_commandline_args(
let error_style: Option<Value> = let error_style: Option<Value> =
call.get_flag(engine_state, &mut stack, "error-style")?; call.get_flag(engine_state, &mut stack, "error-style")?;
let no_newline = call.get_named_arg("no-newline"); let no_newline = call.get_named_arg("no-newline");
let use_ir = call.has_flag(engine_state, &mut stack, "use-ir")?;
// ide flags // ide flags
let lsp = call.has_flag(engine_state, &mut stack, "lsp")?; let lsp = call.has_flag(engine_state, &mut stack, "lsp")?;
@ -251,6 +252,7 @@ pub(crate) fn parse_commandline_args(
table_mode, table_mode,
error_style, error_style,
no_newline, no_newline,
use_ir,
}); });
} }
} }
@ -292,6 +294,7 @@ pub(crate) struct NushellCliArgs {
pub(crate) ide_complete: Option<Value>, pub(crate) ide_complete: Option<Value>,
pub(crate) ide_check: Option<Value>, pub(crate) ide_check: Option<Value>,
pub(crate) ide_ast: Option<Spanned<String>>, pub(crate) ide_ast: Option<Spanned<String>>,
pub(crate) use_ir: bool,
} }
#[derive(Clone)] #[derive(Clone)]
@ -368,6 +371,7 @@ impl Command for Nu {
"start with an alternate environment config file", "start with an alternate environment config file",
None, None,
) )
.switch("use-ir", "EXPERIMENTAL: use the IR evaluation engine on launch", None)
.switch( .switch(
"lsp", "lsp",
"start nu's language server protocol", "start nu's language server protocol",

View File

@ -26,6 +26,10 @@ pub(crate) fn run_commands(
let mut stack = Stack::new(); let mut stack = Stack::new();
let start_time = std::time::Instant::now(); let start_time = std::time::Instant::now();
if parsed_nu_cli_args.use_ir {
stack.use_ir = true;
}
// if the --no-config-file(-n) option is NOT passed, load the plugin file, // if the --no-config-file(-n) option is NOT passed, load the plugin file,
// load the default env file or custom (depending on parsed_nu_cli_args.env_file), // load the default env file or custom (depending on parsed_nu_cli_args.env_file),
// and maybe a custom config file (depending on parsed_nu_cli_args.config_file) // and maybe a custom config file (depending on parsed_nu_cli_args.config_file)
@ -144,6 +148,10 @@ pub(crate) fn run_file(
trace!("run_file"); trace!("run_file");
let mut stack = Stack::new(); let mut stack = Stack::new();
if parsed_nu_cli_args.use_ir {
stack.use_ir = true;
}
// if the --no-config-file(-n) option is NOT passed, load the plugin file, // if the --no-config-file(-n) option is NOT passed, load the plugin file,
// load the default env file or custom (depending on parsed_nu_cli_args.env_file), // load the default env file or custom (depending on parsed_nu_cli_args.env_file),
// and maybe a custom config file (depending on parsed_nu_cli_args.config_file) // and maybe a custom config file (depending on parsed_nu_cli_args.config_file)
@ -254,6 +262,10 @@ pub(crate) fn run_repl(
let mut stack = Stack::new(); let mut stack = Stack::new();
let start_time = std::time::Instant::now(); let start_time = std::time::Instant::now();
if parsed_nu_cli_args.use_ir {
stack.use_ir = true;
}
if parsed_nu_cli_args.no_config_file.is_none() { if parsed_nu_cli_args.no_config_file.is_none() {
setup_config( setup_config(
engine_state, engine_state,