cranelift: Add heap support to filetest infrastructure (#3154)

* cranelift: Add heap support to filetest infrastructure

* cranelift: Explicit heap pointer placement in filetest annotations

* cranelift: Add documentation about the Heap directive

* cranelift: Clarify that heap filetests pointers must be laid out sequentially

* cranelift: Use wrapping add when computing bound pointer

* cranelift: Better error messages when invalid signatures are found for heap file tests.
This commit is contained in:
Afonso Bordado
2021-08-24 17:28:41 +01:00
committed by GitHub
parent 3f6b889067
commit f4ff7c350a
8 changed files with 631 additions and 5 deletions

View File

@@ -1,6 +1,7 @@
//! Parser for .clif files.
use crate::error::{Location, ParseError, ParseResult};
use crate::heap_command::{HeapCommand, HeapType};
use crate::isaspec;
use crate::lexer::{LexError, Lexer, LocatedError, LocatedToken, Token};
use crate::run_command::{Comparison, Invocation, RunCommand};
@@ -140,6 +141,24 @@ pub fn parse_run_command<'a>(text: &str, signature: &Signature) -> ParseResult<O
}
}
/// Parse a CLIF comment `text` as a heap command.
///
/// Return:
/// - `Ok(None)` if the comment is not intended to be a `HeapCommand` (i.e. does not start with `heap`
/// - `Ok(Some(heap))` if the comment is intended as a `HeapCommand` and can be parsed to one
/// - `Err` otherwise.
pub fn parse_heap_command<'a>(text: &str) -> ParseResult<Option<HeapCommand>> {
let _tt = timing::parse_text();
// We remove leading spaces and semi-colons for convenience here instead of at the call sites
// since this function will be attempting to parse a HeapCommand from a CLIF comment.
let trimmed_text = text.trim_start_matches(|c| c == ' ' || c == ';');
let mut parser = Parser::new(trimmed_text);
match parser.token() {
Some(Token::Identifier("heap")) => parser.parse_heap_command().map(|c| Some(c)),
Some(_) | None => Ok(None),
}
}
pub struct Parser<'a> {
lex: Lexer<'a>,
@@ -2559,6 +2578,86 @@ impl<'a> Parser<'a> {
Ok(args)
}
/// Parse a vmctx offset annotation
///
/// vmctx-offset ::= "vmctx" "+" UImm64(offset)
fn parse_vmctx_offset(&mut self) -> ParseResult<Uimm64> {
self.match_token(Token::Identifier("vmctx"), "expected a 'vmctx' token")?;
// The '+' token here gets parsed as part of the integer text, so we can't just match_token it
// and `match_uimm64` doesn't support leading '+' tokens, so we can't use that either.
match self.token() {
Some(Token::Integer(text)) if text.starts_with('+') => {
self.consume();
text[1..]
.parse()
.map_err(|_| self.error("expected u64 decimal immediate"))
}
token => err!(
self.loc,
format!("Unexpected token {:?} after vmctx", token)
),
}
}
/// Parse a CLIF heap command.
///
/// heap-command ::= "heap" ":" heap-type { "," heap-attr }
/// heap-attr ::= "size" "=" UImm64(bytes)
fn parse_heap_command(&mut self) -> ParseResult<HeapCommand> {
self.match_token(Token::Identifier("heap"), "expected a 'heap:' command")?;
self.match_token(Token::Colon, "expected a ':' after heap command")?;
let mut heap_command = HeapCommand {
heap_type: self.parse_heap_type()?,
size: Uimm64::new(0),
ptr_offset: None,
bound_offset: None,
};
while self.optional(Token::Comma) {
let identifier = self.match_any_identifier("expected heap attribute name")?;
self.match_token(Token::Equal, "expected '=' after heap attribute name")?;
match identifier {
"size" => {
heap_command.size = self.match_uimm64("expected integer size")?;
}
"ptr" => {
heap_command.ptr_offset = Some(self.parse_vmctx_offset()?);
}
"bound" => {
heap_command.bound_offset = Some(self.parse_vmctx_offset()?);
}
t => return err!(self.loc, "unknown heap attribute '{}'", t),
}
}
if heap_command.size == Uimm64::new(0) {
return err!(self.loc, self.error("Expected a heap size to be specified"));
}
Ok(heap_command)
}
/// Parse a heap type.
///
/// heap-type ::= "static" | "dynamic"
fn parse_heap_type(&mut self) -> ParseResult<HeapType> {
match self.token() {
Some(Token::Identifier("static")) => {
self.consume();
Ok(HeapType::Static)
}
Some(Token::Identifier("dynamic")) => {
self.consume();
Ok(HeapType::Dynamic)
}
_ => Err(self.error("expected a heap type, e.g. static or dynamic")),
}
}
/// Parse a CLIF run command.
///
/// run-command ::= "run" [":" invocation comparison expected]
@@ -2618,9 +2717,22 @@ impl<'a> Parser<'a> {
"expected invocation parentheses, e.g. %fn(...)",
)?;
let args = self.parse_data_value_list(
&sig.params.iter().map(|a| a.value_type).collect::<Vec<_>>(),
)?;
let arg_types = sig
.params
.iter()
.enumerate()
.filter_map(|(i, p)| {
// The first argument being VMCtx indicates that this is a argument that is going
// to be passed in with info about the test environment, and should not be passed
// in the run params.
if p.purpose == ir::ArgumentPurpose::VMContext && i == 0 {
None
} else {
Some(p.value_type)
}
})
.collect::<Vec<_>>();
let args = self.parse_data_value_list(&arg_types)?;
self.match_token(
Token::RPar,
@@ -3965,6 +4077,45 @@ mod tests {
assert!(parse("run: ", &sig(&[], &[])).is_err());
}
#[test]
fn parse_heap_commands() {
fn parse(text: &str) -> ParseResult<HeapCommand> {
Parser::new(text).parse_heap_command()
}
// Check that we can parse and display the same set of heap commands.
fn assert_roundtrip(text: &str) {
assert_eq!(parse(text).unwrap().to_string(), text);
}
assert_roundtrip("heap: static, size=10");
assert_roundtrip("heap: dynamic, size=10");
assert_roundtrip("heap: static, size=10, ptr=vmctx+10");
assert_roundtrip("heap: static, size=10, bound=vmctx+11");
assert_roundtrip("heap: static, size=10, ptr=vmctx+10, bound=vmctx+10");
assert_roundtrip("heap: dynamic, size=10, ptr=vmctx+10");
assert_roundtrip("heap: dynamic, size=10, bound=vmctx+11");
assert_roundtrip("heap: dynamic, size=10, ptr=vmctx+10, bound=vmctx+10");
let static_heap = parse("heap: static, size=10, ptr=vmctx+8, bound=vmctx+2").unwrap();
assert_eq!(static_heap.size, Uimm64::new(10));
assert_eq!(static_heap.heap_type, HeapType::Static);
assert_eq!(static_heap.ptr_offset, Some(Uimm64::new(8)));
assert_eq!(static_heap.bound_offset, Some(Uimm64::new(2)));
let dynamic_heap = parse("heap: dynamic, size=0x10").unwrap();
assert_eq!(dynamic_heap.size, Uimm64::new(16));
assert_eq!(dynamic_heap.heap_type, HeapType::Dynamic);
assert_eq!(dynamic_heap.ptr_offset, None);
assert_eq!(dynamic_heap.bound_offset, None);
assert!(parse("heap: static").is_err());
assert!(parse("heap: dynamic").is_err());
assert!(parse("heap: static size=0").is_err());
assert!(parse("heap: dynamic size=0").is_err());
assert!(parse("heap: static, size=10, ptr=10").is_err());
assert!(parse("heap: static, size=10, bound=vmctx-10").is_err());
}
#[test]
fn parse_data_values() {
fn parse(text: &str, ty: Type) -> DataValue {