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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user