From 04b10b3fde529518477369a71393110599e2184d Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Tue, 27 Aug 2019 14:25:21 +0200 Subject: [PATCH] Add feature flags to test files. Cranelift can be compiled with feature flags which can change its output. To accomodate changes of output related to feature flags, test file can now include `feature "..."` and `feature ! "..."` directives in the preamble of the test file. The test runner would skip the test if the flag does not match the expectation of the test case. --- cranelift/Cargo.toml | 3 +- cranelift/filetests/Cargo.toml | 3 ++ cranelift/filetests/src/runone.rs | 28 +++++++++++++++-- cranelift/reader/src/lexer.rs | 52 +++++++++++++++++++++++++++++++ cranelift/reader/src/lib.rs | 2 +- cranelift/reader/src/parser.rs | 33 ++++++++++++++++++-- cranelift/reader/src/testfile.rs | 16 ++++++++++ 7 files changed, 130 insertions(+), 7 deletions(-) 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), +}