diff --git a/cranelift/filetests/filetests/isa/x86/simd-lane-access-run.clif b/cranelift/filetests/filetests/isa/x86/simd-lane-access-run.clif index a99660f8fde2..95bee4339b44 100644 --- a/cranelift/filetests/filetests/isa/x86/simd-lane-access-run.clif +++ b/cranelift/filetests/filetests/isa/x86/simd-lane-access-run.clif @@ -177,7 +177,7 @@ block0: v5 = vall_true v4 return v5 } -; run: +; run function %swizzle_with_overflow() -> b1 { block0: @@ -190,4 +190,4 @@ block0: v5 = vall_true v4 return v5 } -; run: +; run diff --git a/cranelift/filetests/src/test_run.rs b/cranelift/filetests/src/test_run.rs index 6e34bfebfadb..2fbb657aa78d 100644 --- a/cranelift/filetests/src/test_run.rs +++ b/cranelift/filetests/src/test_run.rs @@ -6,7 +6,9 @@ use crate::function_runner::FunctionRunner; use crate::subtest::{Context, SubTest, SubtestResult}; use cranelift_codegen; use cranelift_codegen::ir; +use cranelift_reader::parse_run_command; use cranelift_reader::TestCommand; +use log::trace; use std::borrow::Cow; struct TestRun; @@ -36,6 +38,12 @@ impl SubTest for TestRun { fn run(&self, func: Cow, context: &Context) -> SubtestResult<()> { for comment in context.details.comments.iter() { if comment.text.contains("run") { + let trimmed_comment = comment.text.trim_start_matches(|c| c == ' ' || c == ';'); + let command = parse_run_command(trimmed_comment, &func.signature) + .map_err(|e| format!("{}", e))?; + trace!("Parsed run command: {}", command); + // TODO in following changes we will use the parsed command to alter FunctionRunner's behavior. + let runner = FunctionRunner::with_host_isa(func.clone().into_owned(), context.flags.clone()); runner.run()? diff --git a/cranelift/reader/src/lexer.rs b/cranelift/reader/src/lexer.rs index 314c1bcf17a1..20556dc0bd58 100644 --- a/cranelift/reader/src/lexer.rs +++ b/cranelift/reader/src/lexer.rs @@ -43,7 +43,7 @@ pub enum Token<'a> { SigRef(u32), // sig2 UserRef(u32), // u345 Name(&'a str), // %9arbitrary_alphanum, %x3, %0, %function ... - String(&'a str), // "abritrary quoted string with no escape" ... + String(&'a str), // "arbitrary quoted string with no escape" ... HexSequence(&'a str), // #89AF Identifier(&'a str), // Unrecognized identifier (opcode, enumerator, ...) SourceLoc(&'a str), // @00c7 diff --git a/cranelift/reader/src/lib.rs b/cranelift/reader/src/lib.rs index f0922bf8843e..c352b7c910ae 100644 --- a/cranelift/reader/src/lib.rs +++ b/cranelift/reader/src/lib.rs @@ -28,7 +28,8 @@ pub use crate::error::{Location, ParseError, ParseResult}; pub use crate::isaspec::{parse_options, IsaSpec}; -pub use crate::parser::{parse_functions, parse_test, ParseOptions}; +pub use crate::parser::{parse_functions, parse_run_command, parse_test, ParseOptions}; +pub use crate::run_command::{Comparison, DataValue, Invocation, RunCommand}; pub use crate::sourcemap::SourceMap; pub use crate::testcommand::{TestCommand, TestOption}; pub use crate::testfile::{Comment, Details, Feature, TestFile}; @@ -37,6 +38,7 @@ mod error; mod isaspec; mod lexer; mod parser; +mod run_command; mod sourcemap; mod testcommand; mod testfile; diff --git a/cranelift/reader/src/parser.rs b/cranelift/reader/src/parser.rs index ea9814ef624f..465701155e29 100644 --- a/cranelift/reader/src/parser.rs +++ b/cranelift/reader/src/parser.rs @@ -3,6 +3,7 @@ use crate::error::{Location, ParseError, ParseResult}; use crate::isaspec; use crate::lexer::{LexError, Lexer, LocatedError, LocatedToken, Token}; +use crate::run_command::{Comparison, DataValue, Invocation, RunCommand}; use crate::sourcemap::SourceMap; use crate::testcommand::TestCommand; use crate::testfile::{Comment, Details, Feature, TestFile}; @@ -113,6 +114,13 @@ pub fn parse_test<'a>(text: &'a str, options: ParseOptions<'a>) -> ParseResult(text: &str, signature: &Signature) -> ParseResult { + let _tt = timing::parse_text(); + let mut parser = Parser::new(text); + parser.parse_run_command(signature) +} + pub struct Parser<'a> { lex: Lexer<'a>, @@ -683,6 +691,49 @@ impl<'a> Parser<'a> { } } + // Match and consume an i8 immediate. + fn match_imm8(&mut self, err_msg: &str) -> ParseResult { + if let Some(Token::Integer(text)) = self.token() { + self.consume(); + let negative = text.starts_with('-'); + let positive = text.starts_with('+'); + let text = if negative || positive { + // Strip sign prefix. + &text[1..] + } else { + text + }; + + // Parse the text value; the lexer gives us raw text that looks like an integer. + let value = if text.starts_with("0x") { + // Skip underscores. + let text = text.replace("_", ""); + // Parse it as a i8 in hexadecimal form. + u8::from_str_radix(&text[2..], 16) + .map_err(|_| self.error("unable to parse i8 as a hexadecimal immediate"))? + } else { + // Parse it as a i8 to check for overflow and other issues. + text.parse() + .map_err(|_| self.error("expected i8 decimal immediate"))? + }; + + // Apply sign if necessary. + let signed = if negative { + let value = value.wrapping_neg() as i8; + if value > 0 { + return Err(self.error("negative number too small")); + } + value + } else { + value as i8 + }; + + Ok(signed) + } else { + err!(self.loc, err_msg) + } + } + // Match and consume a signed 16-bit immediate. fn match_imm16(&mut self, err_msg: &str) -> ParseResult { if let Some(Token::Integer(text)) = self.token() { @@ -695,27 +746,32 @@ impl<'a> Parser<'a> { } else { text }; - let mut value; - // Lexer just gives us raw text that looks like an integer. - if text.starts_with("0x") { + + // Parse the text value; the lexer gives us raw text that looks like an integer. + let value = if text.starts_with("0x") { // Skip underscores. let text = text.replace("_", ""); // Parse it as a i16 in hexadecimal form. - value = u16::from_str_radix(&text[2..], 16) - .map_err(|_| self.error("unable to parse i16 as a hexadecimal immediate"))?; + u16::from_str_radix(&text[2..], 16) + .map_err(|_| self.error("unable to parse i16 as a hexadecimal immediate"))? } else { // Parse it as a i16 to check for overflow and other issues. - value = text - .parse() - .map_err(|_| self.error("expected i16 decimal immediate"))?; - } - if negative { - value = Ok(value.wrapping_neg())?; - if value as i16 > 0 { + text.parse() + .map_err(|_| self.error("expected i16 decimal immediate"))? + }; + + // Apply sign if necessary. + let signed = if negative { + let value = value.wrapping_neg() as i16; + if value > 0 { return Err(self.error("negative number too small")); } - } - Ok(value as i16) + value + } else { + value as i16 + }; + + Ok(signed) } else { err!(self.loc, err_msg) } @@ -734,27 +790,32 @@ impl<'a> Parser<'a> { } else { text }; - let mut value; - // Lexer just gives us raw text that looks like an integer. - if text.starts_with("0x") { + + // Parse the text value; the lexer gives us raw text that looks like an integer. + let value = if text.starts_with("0x") { // Skip underscores. let text = text.replace("_", ""); // Parse it as a i32 in hexadecimal form. - value = u32::from_str_radix(&text[2..], 16) - .map_err(|_| self.error("unable to parse i32 as a hexadecimal immediate"))?; + u32::from_str_radix(&text[2..], 16) + .map_err(|_| self.error("unable to parse i32 as a hexadecimal immediate"))? } else { // Parse it as a i32 to check for overflow and other issues. - value = text - .parse() - .map_err(|_| self.error("expected i32 decimal immediate"))?; - } - if negative { - value = Ok(value.wrapping_neg())?; - if value as i32 > 0 { + text.parse() + .map_err(|_| self.error("expected i32 decimal immediate"))? + }; + + // Apply sign if necessary. + let signed = if negative { + let value = value.wrapping_neg() as i32; + if value > 0 { return Err(self.error("negative number too small")); } - } - Ok(value as i32) + value + } else { + value as i32 + }; + + Ok(signed) } else { err!(self.loc, err_msg) } @@ -2339,6 +2400,160 @@ impl<'a> Parser<'a> { Ok(args) } + /// Parse a CLIF run command. + /// + /// run-command ::= "run" [":" invocation comparison expected] + /// \ "print" [":" invocation] + fn parse_run_command(&mut self, sig: &Signature) -> ParseResult { + // skip semicolon + match self.token() { + Some(Token::Identifier("run")) => { + self.consume(); + if self.optional(Token::Colon) { + let invocation = self.parse_run_invocation(sig)?; + let comparison = self.parse_run_comparison()?; + let expected = self.parse_run_returns(sig)?; + Ok(RunCommand::Run(invocation, comparison, expected)) + } else if sig.params.is_empty() + && sig.returns.len() == 1 + && sig.returns[0].value_type.is_bool() + { + // To match the existing run behavior that does not require an explicit + // invocation, we create an invocation from a function like `() -> b*` and + // compare it to `true`. + let invocation = Invocation::new("default", vec![]); + let expected = vec![DataValue::B(true)]; + let comparison = Comparison::Equals; + Ok(RunCommand::Run(invocation, comparison, expected)) + } else { + Err(self.error("unable to parse the run command")) + } + } + Some(Token::Identifier("print")) => { + self.consume(); + if self.optional(Token::Colon) { + Ok(RunCommand::Print(self.parse_run_invocation(sig)?)) + } else if sig.params.is_empty() { + // To allow printing of functions like `() -> *`, we create a no-arg invocation. + let invocation = Invocation::new("default", vec![]); + Ok(RunCommand::Print(invocation)) + } else { + Err(self.error("unable to parse the print command")) + } + } + _ => Err(self.error("expected a 'run:' or 'print:' command")), + } + } + + /// Parse the invocation of a CLIF function. + /// + /// This is different from parsing a CLIF `call`; it is used in parsing run commands like + /// `run: %fn(42, 4.2) == false`. + /// + /// invocation ::= name "(" [data-value-list] ")" + fn parse_run_invocation(&mut self, sig: &Signature) -> ParseResult { + if let Some(Token::Name(name)) = self.token() { + self.consume(); + self.match_token( + Token::LPar, + "expected invocation parentheses, e.g. %fn(...)", + )?; + + let args = self.parse_data_value_list( + &sig.params.iter().map(|a| a.value_type).collect::>(), + )?; + + self.match_token( + Token::RPar, + "expected invocation parentheses, e.g. %fn(...)", + )?; + Ok(Invocation::new(name, args)) + } else { + Err(self.error("expected a function name, e.g. %my_fn")) + } + } + + /// Parse a comparison operator for run commands. + /// + /// comparison ::= "==" | "!=" + fn parse_run_comparison(&mut self) -> ParseResult { + if self.optional(Token::Equal) { + self.match_token(Token::Equal, "expected another =")?; + Ok(Comparison::Equals) + } else if self.optional(Token::Not) { + self.match_token(Token::Equal, "expected a =")?; + Ok(Comparison::NotEquals) + } else { + Err(self.error("unable to parse a valid comparison operator")) + } + } + + /// Parse the expected return values of a run invocation. + /// + /// expected ::= "[" "]" + /// | data-value + /// | "[" data-value-list "]" + fn parse_run_returns(&mut self, sig: &Signature) -> ParseResult> { + if sig.returns.len() != 1 { + self.match_token(Token::LBracket, "expected a left bracket [")?; + } + + let returns = self + .parse_data_value_list(&sig.returns.iter().map(|a| a.value_type).collect::>())?; + + if sig.returns.len() != 1 { + self.match_token(Token::RBracket, "expected a right bracket ]")?; + } + Ok(returns) + } + + /// Parse a comma-separated list of data values. + /// + /// data-value-list ::= [data-value {"," data-value-list}] + fn parse_data_value_list(&mut self, types: &[Type]) -> ParseResult> { + let mut values = vec![]; + for ty in types.iter().take(1) { + values.push(self.parse_data_value(*ty)?); + } + for ty in types.iter().skip(1) { + self.match_token( + Token::Comma, + "expected a comma between invocation arguments", + )?; + values.push(self.parse_data_value(*ty)?); + } + Ok(values) + } + + /// Parse a data value; e.g. `42`, `4.2`, `true`. + /// + /// data-value-list ::= [data-value {"," data-value-list}] + fn parse_data_value(&mut self, ty: Type) -> ParseResult { + let dv = match ty { + I8 => DataValue::from(self.match_imm8("expected a i8")?), + I16 => DataValue::from(self.match_imm16("expected an i16")?), + I32 => DataValue::from(self.match_imm32("expected an i32")?), + I64 => DataValue::from(Into::::into(self.match_imm64("expected an i64")?)), + F32 => DataValue::from(f32::from_bits(self.match_ieee32("expected an f32")?.bits())), + F64 => DataValue::from(f64::from_bits(self.match_ieee64("expected an f64")?.bits())), + _ if ty.is_vector() => { + let as_vec = self.match_constant_data(ty)?.into_vec(); + if as_vec.len() == 16 { + let mut as_array = [0; 16]; + as_array.copy_from_slice(&as_vec[..16]); + DataValue::from(as_array) + } else { + return Err(self.error("only 128-bit vectors are currently supported")); + } + } + _ if ty.is_bool() && !ty.is_vector() => { + DataValue::from(self.match_bool("expected a boolean")?) + } + _ => return Err(self.error(&format!("don't know how to parse data values of: {}", ty))), + }; + Ok(dv) + } + // Parse the operands following the instruction opcode. // This depends on the format of the opcode. fn parse_inst_operands( @@ -3476,4 +3691,76 @@ mod tests { [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0] ) } + + #[test] + fn parse_run_commands() { + // Helper for creating signatures. + fn sig(ins: &[Type], outs: &[Type]) -> Signature { + let mut sig = Signature::new(CallConv::Fast); + for i in ins { + sig.params.push(AbiParam::new(*i)); + } + for o in outs { + sig.returns.push(AbiParam::new(*o)); + } + sig + } + + // Helper for parsing run commands. + fn parse(text: &str, sig: &Signature) -> ParseResult { + Parser::new(text).parse_run_command(sig) + } + + // Check that we can parse and display the same set of run commands. + fn assert_roundtrip(text: &str, sig: &Signature) { + assert_eq!(parse(text, sig).unwrap().to_string(), text); + } + assert_roundtrip("run: %fn0() == 42", &sig(&[], &[I32])); + assert_roundtrip( + "run: %fn0(8, 16, 32, 64) == true", + &sig(&[I8, I16, I32, I64], &[B8]), + ); + assert_roundtrip( + "run: %my_func(true) == 0x0f0e0d0c0b0a09080706050403020100", + &sig(&[B32], &[I8X16]), + ); + + // Verify that default invocations are created when not specified. + assert_eq!( + parse("run", &sig(&[], &[B32])).unwrap().to_string(), + "run: %default() == true" + ); + assert_eq!( + parse("print", &sig(&[], &[F32X4, I16X8])) + .unwrap() + .to_string(), + "print: %default()" + ); + + // Demonstrate some unparseable cases. + assert!(parse("print", &sig(&[I32], &[B32])).is_err()); + assert!(parse("run", &sig(&[], &[I32])).is_err()); + assert!(parse("print:", &sig(&[], &[])).is_err()); + assert!(parse("run: ", &sig(&[], &[])).is_err()); + } + + #[test] + fn parse_data_values() { + fn parse(text: &str, ty: Type) -> DataValue { + Parser::new(text).parse_data_value(ty).unwrap() + } + + assert_eq!(parse("8", I8).to_string(), "8"); + assert_eq!(parse("16", I16).to_string(), "16"); + assert_eq!(parse("32", I32).to_string(), "32"); + assert_eq!(parse("64", I64).to_string(), "64"); + assert_eq!(parse("0x32.32", F32).to_string(), "0x1.919000p5"); + assert_eq!(parse("0x64.64", F64).to_string(), "0x1.9190000000000p6"); + assert_eq!(parse("true", B1).to_string(), "true"); + assert_eq!(parse("false", B64).to_string(), "false"); + assert_eq!( + parse("[0 1 2 3]", I32X4).to_string(), + "0x03000000020000000100000000" + ); + } } diff --git a/cranelift/reader/src/run_command.rs b/cranelift/reader/src/run_command.rs new file mode 100644 index 000000000000..4f106d956482 --- /dev/null +++ b/cranelift/reader/src/run_command.rs @@ -0,0 +1,148 @@ +//! Run commands. +//! +//! Functions in a `.clif` file can have *run commands* appended that control how a function is +//! invoked and tested within the `test run` context. The general syntax is: +//! +//! - `; run`: this assumes the function has a signature like `() -> b*`. +//! - `; run: %fn(42, 4.2) == false`: this syntax specifies the parameters and return values. + +use cranelift_codegen::ir::immediates::{Ieee32, Ieee64}; +use cranelift_codegen::ir::ConstantData; +use std::fmt::{Display, Formatter, Result}; + +/// A run command appearing in a test file. +/// +/// For parsing, see [Parser::parse_run_command]. +#[derive(PartialEq, Debug)] +pub enum RunCommand { + /// Invoke a function and print its result. + Print(Invocation), + /// Invoke a function and compare its result to a value sequence. + Run(Invocation, Comparison, Vec), +} + +impl Display for RunCommand { + fn fmt(&self, f: &mut Formatter) -> Result { + match self { + RunCommand::Print(invocation) => write!(f, "print: {}", invocation), + RunCommand::Run(invocation, comparison, expected) => { + write!(f, "run: {} {} ", invocation, comparison)?; + if expected.len() == 1 { + write!(f, "{}", expected[0]) + } else { + write!(f, "[")?; + write_data_value_list(f, expected)?; + write!(f, "]") + } + } + } + } +} + +/// Represent a function call; [RunCommand]s invoke a CLIF function using an [Invocation]. +#[derive(Debug, PartialEq)] +pub struct Invocation { + /// The name of the function to call. Note: this field is for mostly included for informational + /// purposes and may not always be necessary for identifying which function to call. + pub func: String, + /// The arguments to be passed to the function when invoked. + pub args: Vec, +} + +impl Invocation { + pub(crate) fn new(func: &str, args: Vec) -> Self { + let func = func.to_string(); + Self { func, args } + } +} + +impl Display for Invocation { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "%{}(", self.func)?; + write_data_value_list(f, &self.args)?; + write!(f, ")") + } +} + +/// Represent a data value. Where [Value] is an SSA reference, [DataValue] is the type + value +/// that would be referred to by a [Value]. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq)] +pub enum DataValue { + B(bool), + I8(i8), + I16(i16), + I32(i32), + I64(i64), + F32(f32), + F64(f64), + V128([u8; 16]), +} + +/// Helper for creating [From] implementations for [DataValue] +macro_rules! from_data { + ( $ty:ty, $variant:ident ) => { + impl From<$ty> for DataValue { + fn from(data: $ty) -> Self { + DataValue::$variant(data) + } + } + }; +} +from_data!(bool, B); +from_data!(i8, I8); +from_data!(i16, I16); +from_data!(i32, I32); +from_data!(i64, I64); +from_data!(f32, F32); +from_data!(f64, F64); +from_data!([u8; 16], V128); + +impl Display for DataValue { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + DataValue::B(dv) => write!(f, "{}", dv), + DataValue::I8(dv) => write!(f, "{}", dv), + DataValue::I16(dv) => write!(f, "{}", dv), + DataValue::I32(dv) => write!(f, "{}", dv), + DataValue::I64(dv) => write!(f, "{}", dv), + // Use the Ieee* wrappers here to maintain a consistent syntax. + DataValue::F32(dv) => write!(f, "{}", Ieee32::from(*dv)), + DataValue::F64(dv) => write!(f, "{}", Ieee64::from(*dv)), + // Again, for syntax consistency, use ConstantData, which in this case displays as hex. + DataValue::V128(dv) => write!(f, "{}", ConstantData::from(&dv[..])), + } + } +} + +/// Helper function for displaying `Vec`. +fn write_data_value_list(f: &mut Formatter<'_>, list: &[DataValue]) -> Result { + match list.len() { + 0 => Ok(()), + 1 => write!(f, "{}", list[0]), + _ => { + write!(f, "{}", list[0])?; + for dv in list.iter().skip(1) { + write!(f, ", {}", dv)?; + } + Ok(()) + } + } +} + +/// A CLIF comparison operation; e.g. `==`. +#[allow(missing_docs)] +#[derive(Debug, PartialEq)] +pub enum Comparison { + Equals, + NotEquals, +} + +impl Display for Comparison { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + Comparison::Equals => write!(f, "=="), + Comparison::NotEquals => write!(f, "!="), + } + } +} diff --git a/cranelift/src/run.rs b/cranelift/src/run.rs index 0f553c8702c7..37c65adf70ff 100644 --- a/cranelift/src/run.rs +++ b/cranelift/src/run.rs @@ -92,7 +92,9 @@ fn run_file_contents(file_contents: String) -> Result<(), String> { let test_file = parse_test(&file_contents, options).map_err(|e| e.to_string())?; for (func, Details { comments, .. }) in test_file.functions { if comments.iter().any(|c| c.text.contains("run")) { + // TODO in following changes we will parse this comment to alter the FunctionRunner's behavior. let isa = create_target_isa(&test_file.isa_spec)?; + // TODO the following no longer makes sense; use FunctionRunner::with_host_isa(...) instead FunctionRunner::new(func, isa).run()? } }