diff --git a/cranelift/Cargo.toml b/cranelift/Cargo.toml index a8f85c30b7..674db7cb92 100644 --- a/cranelift/Cargo.toml +++ b/cranelift/Cargo.toml @@ -48,7 +48,8 @@ walkdir = "2.2" default = ["disas", "wasm", "cranelift-codegen/all-arch"] disas = ["capstone"] wasm = ["wabt", "cranelift-wasm"] -basic-blocks = ["cranelift-codegen/basic-blocks", "cranelift-frontend/basic-blocks", "cranelift-wasm/basic-blocks"] +basic-blocks = ["cranelift-codegen/basic-blocks", "cranelift-frontend/basic-blocks", +"cranelift-wasm/basic-blocks", "cranelift-filetests/basic-blocks"] # We want debug symbols on release binaries by default since it allows profiling # tools to give more accurate information. We can always strip them out later if diff --git a/cranelift/filetests/Cargo.toml b/cranelift/filetests/Cargo.toml index 2b92833070..8913a8bfab 100644 --- a/cranelift/filetests/Cargo.toml +++ b/cranelift/filetests/Cargo.toml @@ -20,3 +20,6 @@ log = "0.4.6" memmap = "0.7.0" num_cpus = "1.8.0" region = "2.1.2" + +[features] +basic-blocks = [] diff --git a/cranelift/filetests/src/runone.rs b/cranelift/filetests/src/runone.rs index 4a2071b2db..1bb7d1c7f2 100644 --- a/cranelift/filetests/src/runone.rs +++ b/cranelift/filetests/src/runone.rs @@ -8,8 +8,7 @@ use cranelift_codegen::print_errors::pretty_verifier_error; use cranelift_codegen::settings::Flags; use cranelift_codegen::timing; use cranelift_codegen::verify_function; -use cranelift_reader::IsaSpec; -use cranelift_reader::{parse_test, ParseOptions}; +use cranelift_reader::{parse_test, Feature, IsaSpec, ParseOptions}; use log::info; use std::borrow::Cow; use std::fs; @@ -53,6 +52,31 @@ pub fn run(path: &Path, passes: Option<&[String]>, target: Option<&str>) -> Test } }; + for feature in testfile.features.iter() { + let (flag, test_expect) = match feature { + Feature::With(name) => (name, true), + Feature::Without(name) => (name, false), + }; + let cranelift_has = match flag { + // Add any cranelift feature flag here, and make sure that it is forwarded to the + // cranelift-filetest crate in the top-level Cargo.toml. + &"basic-blocks" => cfg!(feature = "basic-blocks"), + _ => { + return Err(format!( + r#"{:?}: Unknown feature flag named "{}""#, + path, flag + )) + } + }; + if cranelift_has != test_expect { + println!( + r#"skipping test {:?}: non-matching feature flag "{}""#, + path, flag + ); + return Ok(started.elapsed()); + } + } + if testfile.functions.is_empty() { return Err("no functions found".to_string()); } diff --git a/cranelift/reader/src/lexer.rs b/cranelift/reader/src/lexer.rs index 76327a7a8d..465e79e97f 100644 --- a/cranelift/reader/src/lexer.rs +++ b/cranelift/reader/src/lexer.rs @@ -27,6 +27,7 @@ pub enum Token<'a> { Dot, // '.' Colon, // ':' Equal, // '=' + Not, // '!' Arrow, // '->' Float(&'a str), // Floating point immediate Integer(&'a str), // Integer immediate @@ -42,6 +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" ... HexSequence(&'a str), // #89AF Identifier(&'a str), // Unrecognized identifier (opcode, enumerator, ...) SourceLoc(&'a str), // @00c7 @@ -401,6 +403,27 @@ impl<'a> Lexer<'a> { token(Token::Name(&self.source[begin..end]), loc) } + /// Scan for a multi-line quoted string with no escape character. + fn scan_string(&mut self) -> Result, LocatedError> { + let loc = self.loc(); + let begin = self.pos + 1; + + assert_eq!(self.lookahead, Some('"')); + + while let Some(c) = self.next_ch() { + if c == '"' { + break; + } + } + + let end = self.pos; + if self.lookahead != Some('"') { + return error(LexError::InvalidChar, self.loc()); + } + self.next_ch(); + token(Token::String(&self.source[begin..end]), loc) + } + fn scan_hex_sequence(&mut self) -> Result, LocatedError> { let loc = self.loc(); let begin = self.pos + 1; @@ -452,6 +475,7 @@ impl<'a> Lexer<'a> { Some('.') => Some(self.scan_char(Token::Dot)), Some(':') => Some(self.scan_char(Token::Colon)), Some('=') => Some(self.scan_char(Token::Equal)), + Some('!') => Some(self.scan_char(Token::Not)), Some('+') => Some(self.scan_number()), Some('-') => { if self.looking_at("->") { @@ -463,6 +487,7 @@ impl<'a> Lexer<'a> { Some(ch) if ch.is_digit(10) => Some(self.scan_number()), Some(ch) if ch.is_alphabetic() => Some(self.scan_word()), Some('%') => Some(self.scan_name()), + Some('"') => Some(self.scan_string()), Some('#') => Some(self.scan_hex_sequence()), Some('@') => Some(self.scan_srcloc()), Some(ch) if ch.is_whitespace() => { @@ -633,6 +658,33 @@ mod tests { assert_eq!(lex.next(), token(Token::Name("_"), 1)); } + #[test] + fn lex_strings() { + let mut lex = Lexer::new( + r#""" "0" "x3""function" "123 abc" "\" "start + and end on + different lines" "#, + ); + + assert_eq!(lex.next(), token(Token::String(""), 1)); + assert_eq!(lex.next(), token(Token::String("0"), 1)); + assert_eq!(lex.next(), token(Token::String("x3"), 1)); + assert_eq!(lex.next(), token(Token::String("function"), 1)); + assert_eq!(lex.next(), token(Token::String("123 abc"), 1)); + assert_eq!(lex.next(), token(Token::String(r#"\"#), 1)); + assert_eq!( + lex.next(), + token( + Token::String( + r#"start + and end on + different lines"# + ), + 1 + ) + ); + } + #[test] fn lex_userrefs() { let mut lex = Lexer::new("u0 u1 u234567890 u9:8765"); diff --git a/cranelift/reader/src/lib.rs b/cranelift/reader/src/lib.rs index 871bd80b2c..f0922bf884 100644 --- a/cranelift/reader/src/lib.rs +++ b/cranelift/reader/src/lib.rs @@ -31,7 +31,7 @@ pub use crate::isaspec::{parse_options, IsaSpec}; pub use crate::parser::{parse_functions, parse_test, ParseOptions}; pub use crate::sourcemap::SourceMap; pub use crate::testcommand::{TestCommand, TestOption}; -pub use crate::testfile::{Comment, Details, TestFile}; +pub use crate::testfile::{Comment, Details, Feature, TestFile}; mod error; mod isaspec; diff --git a/cranelift/reader/src/parser.rs b/cranelift/reader/src/parser.rs index a0fa0f127c..df0fc18a0e 100644 --- a/cranelift/reader/src/parser.rs +++ b/cranelift/reader/src/parser.rs @@ -5,7 +5,7 @@ use crate::isaspec; use crate::lexer::{LexError, Lexer, LocatedError, LocatedToken, Token}; use crate::sourcemap::SourceMap; use crate::testcommand::TestCommand; -use crate::testfile::{Comment, Details, TestFile}; +use crate::testfile::{Comment, Details, Feature, TestFile}; use cranelift_codegen::entity::EntityRef; use cranelift_codegen::ir; use cranelift_codegen::ir::entities::AnyEntity; @@ -82,6 +82,7 @@ pub fn parse_test<'a>(text: &'a str, options: ParseOptions<'a>) -> ParseResult(text: &'a str, options: ParseOptions<'a>) -> ParseResult Parser<'a> { } } + /// Parse a list of expected features that Cranelift should be compiled with, or without. + pub fn parse_cranelift_features(&mut self) -> ParseResult>> { + let mut list = Vec::new(); + while self.token() == Some(Token::Identifier("feature")) { + self.consume(); + let has = !self.optional(Token::Not); + match (self.token(), has) { + (Some(Token::String(flag)), true) => list.push(Feature::With(flag)), + (Some(Token::String(flag)), false) => list.push(Feature::Without(flag)), + (tok, _) => { + return err!( + self.loc, + format!("Expected feature flag string, got {:?}", tok) + ) + } + } + self.consume(); + } + Ok(list) + } + /// Parse a list of function definitions. /// /// This is the top-level parse function matching the whole contents of a file. @@ -2992,12 +3015,14 @@ mod tests { #[test] fn test_file() { let tf = parse_test( - "; before + r#"; before test cfg option=5 test verify set enable_float=false + feature "foo" + feature !"bar" ; still preamble - function %comment() system_v {}", + function %comment() system_v {}"#, ParseOptions::default(), ) .unwrap(); @@ -3011,6 +3036,8 @@ mod tests { } _ => panic!("unexpected ISAs"), } + assert_eq!(tf.features[0], Feature::With(&"foo")); + assert_eq!(tf.features[1], Feature::Without(&"bar")); assert_eq!(tf.preamble_comments.len(), 2); assert_eq!(tf.preamble_comments[0].text, "; before"); assert_eq!(tf.preamble_comments[1].text, "; still preamble"); diff --git a/cranelift/reader/src/testfile.rs b/cranelift/reader/src/testfile.rs index 506694586f..68c7d30a93 100644 --- a/cranelift/reader/src/testfile.rs +++ b/cranelift/reader/src/testfile.rs @@ -20,6 +20,8 @@ pub struct TestFile<'a> { pub commands: Vec>, /// `isa bar ...` lines. pub isa_spec: IsaSpec, + /// `feature ...` lines + pub features: Vec>, /// Comments appearing before the first function. /// These are all tagged as 'Function' scope for lack of a better entity. pub preamble_comments: Vec>, @@ -55,3 +57,17 @@ pub struct Comment<'a> { /// Text of the comment, including the leading `;`. pub text: &'a str, } + +/// A cranelift feature in a test file preamble. +/// +/// This represents the expectation of the test case. Before running any of the +/// functions of the test file, the feature set should be compared with the +/// feature set used to compile Cranelift. If there is any differences, then the +/// test file should be skipped. +#[derive(PartialEq, Eq, Debug)] +pub enum Feature<'a> { + /// `feature "..."` lines + With(&'a str), + /// `feature ! "..."` lines. + Without(&'a str), +}