Box ShellError in Value::Error (#8375)

# Description

Our `ShellError` at the moment has a `std::mem::size_of<ShellError>` of
136 bytes (on AMD64). As a result `Value` directly storing the struct
also required 136 bytes (thanks to alignment requirements).

This change stores the `Value::Error` `ShellError` on the heap.

Pro:
- Value now needs just 80 bytes
- Should be 1 cacheline less (still at least 2 cachelines)

Con:
- More small heap allocations when dealing with `Value::Error`
  - More heap fragmentation
  - Potential for additional required memcopies

# Further code changes

Includes a small refactor of `try` due to a type mismatch in its large
match.

# User-Facing Changes

None for regular users.

Plugin authors may have to update their matches on `Value` if they use
`nu-protocol`

Needs benchmarking to see if there is a benefit in real world workloads.
**Update** small improvements in runtime for workloads with high volume
of values. Significant reduction in maximum resident set size, when many
values are held in memory.

# Tests + Formatting
This commit is contained in:
Stefan Holderbach
2023-03-12 09:57:27 +01:00
committed by GitHub
parent c26d91fb61
commit a52386e837
153 changed files with 648 additions and 520 deletions

View File

@ -90,7 +90,8 @@ fn action(
"binhex" => GeneralPurpose::new(&alphabet::BIN_HEX, NO_PAD),
"crypt" => GeneralPurpose::new(&alphabet::CRYPT, NO_PAD),
"mutf7" => GeneralPurpose::new(&alphabet::IMAP_MUTF7, NO_PAD),
not_valid => return Value::Error { error:ShellError::GenericError(
not_valid => return Value::Error { error:
Box::new(ShellError::GenericError(
"value is not an accepted character set".to_string(),
format!(
"{not_valid} is not a valid character-set.\nPlease use `help encode base64` to see a list of valid character sets."
@ -98,7 +99,7 @@ fn action(
Some(config_character_set.span),
None,
Vec::new(),
)}
))}
};
match input {
// Propagate existing errors.
@ -111,13 +112,13 @@ fn action(
Ok(bytes_written) => bytes_written,
Err(err) => {
return Value::Error {
error: ShellError::GenericError(
error: Box::new(ShellError::GenericError(
"Error encoding data".into(),
err.to_string(),
Some(Span::unknown()),
None,
Vec::new(),
),
)),
}
}
};
@ -125,13 +126,13 @@ fn action(
Value::string(std::str::from_utf8(&enc_vec).unwrap_or(""), command_span)
}
ActionType::Decode => Value::Error {
error: ShellError::UnsupportedInput(
error: Box::new(ShellError::UnsupportedInput(
"Binary data can only be encoded".to_string(),
"value originates from here".into(),
command_span,
// This line requires the Value::Error {} match above.
input.expect_span(),
),
)),
},
},
Value::String {
@ -158,20 +159,20 @@ fn action(
match String::from_utf8(decoded_value) {
Ok(string_value) => Value::string(string_value, command_span),
Err(e) => Value::Error {
error: ShellError::GenericError(
error: Box::new(ShellError::GenericError(
"base64 payload isn't a valid utf-8 sequence"
.to_owned(),
e.to_string(),
Some(*value_span),
Some("consider using the `--binary` flag".to_owned()),
Vec::new(),
),
)),
},
}
}
}
Err(_) => Value::Error {
error: ShellError::GenericError(
error: Box::new(ShellError::GenericError(
"value could not be base64 decoded".to_string(),
format!(
"invalid base64 input for character set {}",
@ -180,17 +181,17 @@ fn action(
Some(command_span),
None,
Vec::new(),
),
)),
},
}
}
}
}
other => Value::Error {
error: ShellError::TypeMismatch {
error: Box::new(ShellError::TypeMismatch {
err_message: format!("string or binary, not {}", other.get_type()),
span: other.span().unwrap_or(command_span),
},
}),
},
}
}

View File

@ -77,7 +77,7 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"#
PipelineData::Value(v, ..) => match v {
Value::Binary { val: bytes, .. } => super::encoding::decode(head, encoding, &bytes)
.map(|val| val.into_pipeline_data()),
Value::Error { error } => Err(error),
Value::Error { error } => Err(*error),
_ => Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "binary".into(),
wrong_type: v.get_type().to_string(),

View File

@ -100,7 +100,7 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"#
super::encoding::encode(head, encoding, &s, span, ignore_errors)
.map(|val| val.into_pipeline_data())
}
Value::Error { error } => Err(error),
Value::Error { error } => Err(*error),
_ => Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: v.get_type().to_string(),

View File

@ -230,7 +230,7 @@ fn format(
}
}
}
Value::Error { error } => return Err(error.clone()),
Value::Error { error } => return Err(*error.clone()),
_ => {
return Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "record".to_string(),
@ -249,7 +249,7 @@ fn format(
}
// Unwrapping this ShellError is a bit unfortunate.
// Ideally, its Span would be preserved.
Value::Error { error } => Err(error),
Value::Error { error } => Err(*error),
_ => Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "record".to_string(),
wrong_type: data_as_value.get_type().to_string(),

View File

@ -106,12 +106,12 @@ fn format_value_impl(val: &Value, arg: &Arguments, span: Span) -> Value {
},
Value::Error { .. } => val.clone(),
_ => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "filesize".into(),
wrong_type: val.get_type().to_string(),
dst_span: span,
src_span: val.expect_span(),
},
}),
},
}
}

View File

@ -346,11 +346,11 @@ impl Iterator for ParseStreamer {
&mut self.excess,
),
Err(_) => Some(Value::Error {
error: ShellError::PipelineMismatch {
error: Box::new(ShellError::PipelineMismatch {
exp_input_type: "string".into(),
dst_span: self.span,
src_span: v.span().unwrap_or(self.span),
},
}),
}),
}
} else {
@ -386,15 +386,17 @@ impl Iterator for ParseStreamerExternal {
&mut self.excess,
),
Err(_) => Some(Value::Error {
error: ShellError::PipelineMismatch {
error: Box::new(ShellError::PipelineMismatch {
exp_input_type: "string".into(),
dst_span: self.span,
src_span: self.span,
},
}),
}),
}
} else if let Some(Err(err)) = v {
Some(Value::Error { error: err })
Some(Value::Error {
error: Box::new(err),
})
} else {
None
}
@ -416,13 +418,13 @@ fn stream_helper(
Ok(c) => c,
Err(e) => {
return Some(Value::Error {
error: ShellError::GenericError(
error: Box::new(ShellError::GenericError(
"Error with regular expression captures".into(),
e.to_string(),
None,
None,
Vec::new(),
),
)),
})
}
};

View File

@ -124,18 +124,18 @@ fn size(
move |v| {
// First, obtain the span. If this fails, propagate the error that results.
let value_span = match v.span() {
Err(v) => return Value::Error { error: v },
Err(v) => return Value::Error { error: Box::new(v) },
Ok(v) => v,
};
// Now, check if it's a string.
match v.as_string() {
Ok(s) => counter(&s, span),
Err(_) => Value::Error {
error: ShellError::PipelineMismatch {
error: Box::new(ShellError::PipelineMismatch {
exp_input_type: "string".into(),
dst_span: span,
src_span: value_span,
},
}),
},
}
},

View File

@ -110,15 +110,17 @@ fn split_chars_helper(v: &Value, name: Span, graphemes: bool) -> Vec<Value> {
}
} else {
vec![Value::Error {
error: ShellError::PipelineMismatch {
error: Box::new(ShellError::PipelineMismatch {
exp_input_type: "string".into(),
dst_span: name,
src_span: v_span,
},
}),
}]
}
}
Err(error) => vec![Value::Error { error }],
Err(error) => vec![Value::Error {
error: Box::new(error),
}],
}
}

View File

@ -182,13 +182,15 @@ fn split_column_helper(
} else {
match v.span() {
Ok(span) => vec![Value::Error {
error: ShellError::PipelineMismatch {
error: Box::new(ShellError::PipelineMismatch {
exp_input_type: "string".into(),
dst_span: head,
src_span: span,
},
}),
}],
Err(error) => vec![Value::Error {
error: Box::new(error),
}],
Err(error) => vec![Value::Error { error }],
}
}
}

View File

@ -132,15 +132,17 @@ fn split_row_helper(
}
} else {
vec![Value::Error {
error: ShellError::PipelineMismatch {
error: Box::new(ShellError::PipelineMismatch {
exp_input_type: "string".into(),
dst_span: name,
src_span: v_span,
},
}),
}]
}
}
Err(error) => vec![Value::Error { error }],
Err(error) => vec![Value::Error {
error: Box::new(error),
}],
}
}

View File

@ -185,15 +185,17 @@ fn split_words_helper(
.collect::<Vec<Value>>()
} else {
vec![Value::Error {
error: ShellError::PipelineMismatch {
error: Box::new(ShellError::PipelineMismatch {
exp_input_type: "string".into(),
dst_span: span,
src_span: v_span,
},
}),
}]
}
}
Err(error) => vec![Value::Error { error }],
Err(error) => vec![Value::Error {
error: Box::new(error),
}],
}
}

View File

@ -89,7 +89,9 @@ fn operate(
let r =
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
if let Err(error) = r {
return Value::Error { error };
return Value::Error {
error: Box::new(error),
};
}
}
ret
@ -107,12 +109,12 @@ fn action(input: &Value, head: Span) -> Value {
},
Value::Error { .. } => input.clone(),
_ => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: input.get_type().to_string(),
dst_span: head,
src_span: input.expect_span(),
},
}),
},
}
}

View File

@ -101,7 +101,9 @@ fn operate(
let r =
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
if let Err(error) = r {
return Value::Error { error };
return Value::Error {
error: Box::new(error),
};
}
}
ret
@ -119,12 +121,12 @@ fn action(input: &Value, head: Span) -> Value {
},
Value::Error { .. } => input.clone(),
_ => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: input.get_type().to_string(),
dst_span: head,
src_span: input.expect_span(),
},
}),
},
}
}

View File

@ -69,12 +69,12 @@ where
},
Value::Error { .. } => input.clone(),
_ => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: input.get_type().to_string(),
dst_span: head,
src_span: input.expect_span(),
},
}),
},
}
}

View File

@ -68,7 +68,9 @@ fn operate(
let r =
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
if let Err(error) = r {
return Value::Error { error };
return Value::Error {
error: Box::new(error),
};
}
}
ret
@ -86,12 +88,12 @@ fn action(input: &Value, head: Span) -> Value {
},
Value::Error { .. } => input.clone(),
_ => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: input.get_type().to_string(),
dst_span: head,
src_span: input.expect_span(),
},
}),
},
}
}

View File

@ -48,7 +48,7 @@ impl Command for StrCollect {
for value in input {
match value {
Value::Error { error } => {
return Err(error);
return Err(*error);
}
value => {
strings.push(value.debug_string("\n", config));

View File

@ -183,12 +183,12 @@ fn action(
),
Value::Error { .. } => input.clone(),
_ => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: input.get_type().to_string(),
dst_span: head,
src_span: input.expect_span(),
},
}),
},
}
}

View File

@ -99,12 +99,12 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
}
Value::Error { .. } => input.clone(),
_ => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: input.get_type().to_string(),
dst_span: head,
src_span: input.expect_span(),
},
}),
},
}
}

View File

@ -98,12 +98,12 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
}
Value::Error { .. } => input.clone(),
_ => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: input.get_type().to_string(),
dst_span: head,
src_span: input.expect_span(),
},
}),
},
}
}

View File

@ -155,7 +155,7 @@ fn action(
Value::String { val: s, .. } => {
let (start_index, end_index) = match r {
Ok(r) => (r.0 as usize, r.1 as usize),
Err(e) => return Value::Error { error: e },
Err(e) => return Value::Error { error: Box::new(e) },
};
// When the -e flag is present, search using rfind instead of find.s
@ -186,12 +186,12 @@ fn action(
}
Value::Error { .. } => input.clone(),
_ => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: input.get_type().to_string(),
dst_span: head,
src_span: input.expect_span(),
},
}),
},
}
}
@ -235,7 +235,7 @@ fn process_range(
Ok((start_index, end_index))
}
}
Value::Error { error } => Err(error.clone()),
Value::Error { error } => Err(*error.clone()),
_ => Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: input.get_type().to_string(),

View File

@ -52,7 +52,7 @@ impl Command for StrJoin {
for value in input {
match value {
Value::Error { error } => {
return Err(error);
return Err(*error);
}
value => {
strings.push(value.debug_string("\n", config));

View File

@ -108,12 +108,12 @@ fn action(input: &Value, arg: &Arguments, head: Span) -> Value {
),
Value::Error { .. } => input.clone(),
_ => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: input.get_type().to_string(),
dst_span: head,
src_span: input.expect_span(),
},
}),
},
}
}

View File

@ -209,22 +209,22 @@ fn action(
}
}
Err(e) => Value::Error {
error: ShellError::IncorrectValue {
error: Box::new(ShellError::IncorrectValue {
msg: format!("Regex error: {e}"),
span: find.span,
},
}),
},
}
}
}
Value::Error { .. } => input.clone(),
_ => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: input.get_type().to_string(),
dst_span: head,
src_span: input.expect_span(),
},
}),
},
}
}

View File

@ -77,12 +77,12 @@ fn action(input: &Value, _arg: &CellPathOnlyArgs, head: Span) -> Value {
},
Value::Error { .. } => input.clone(),
_ => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: input.get_type().to_string(),
dst_span: head,
src_span: input.expect_span(),
},
}),
},
}
}

View File

@ -114,12 +114,12 @@ fn action(
}
Value::Error { .. } => input.clone(),
_ => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: input.get_type().to_string(),
dst_span: head,
src_span: input.expect_span(),
},
}),
},
}
}

View File

@ -155,10 +155,10 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
match start.cmp(&end) {
Ordering::Equal => Value::string("", head),
Ordering::Greater => Value::Error {
error: ShellError::TypeMismatch {
error: Box::new(ShellError::TypeMismatch {
err_message: "End must be greater than or equal to Start".to_string(),
span: head,
},
}),
},
Ordering::Less => Value::String {
val: {
@ -200,13 +200,13 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error {
error: ShellError::UnsupportedInput(
error: Box::new(ShellError::UnsupportedInput(
"Only string values are supported".into(),
format!("input type: {:?}", other.get_type()),
head,
// This line requires the Value::Error match above.
other.expect_span(),
),
)),
},
}
}

View File

@ -191,13 +191,13 @@ fn action(input: &Value, arg: &Arguments, head: Span) -> Value {
},
ActionMode::Local => {
Value::Error {
error: ShellError::UnsupportedInput(
error: Box::new(ShellError::UnsupportedInput(
"Only string values are supported".into(),
format!("input type: {:?}", other.get_type()),
head,
// This line requires the Value::Error match above.
other.expect_span(),
),
)),
}
}
},