diff --git a/cranelift/docs/testing.md b/cranelift/docs/testing.md index 4dc60548084c..e7e56bf0a847 100644 --- a/cranelift/docs/testing.md +++ b/cranelift/docs/testing.md @@ -389,7 +389,7 @@ This indicates the following: * `static`: We have requested a non-resizable and non-movable static heap. * `size=0x1000`: It has to have a size of 4096 bytes. * `ptr=vmctx+0`: The pointer to the address to the start of this heap is placed at offset 0 in the `vmctx` struct -* `bound=vmctx+8`: The pointer to the address to the end of this heap is placed at offset 8 in the `vmctx` struct +* `bound=vmctx+8`: The bound of this heap (size in bytes) is placed at offset 8 in the `vmctx` struct The `ptr` and `bound` arguments make explicit the placement of the pointers to the start and end of the heap memory in the environment struct. `vmctx+0` means that at offset 0 of the environment struct there will be the pointer to the start @@ -412,11 +412,11 @@ See the diagram below, on how the `vmctx` struct ends up if with multiple heaps: ┌─────────────────────┐ vmctx+0 │heap0: start address │ ├─────────────────────┤ vmctx+8 - │heap0: end address │ + │heap0: bound │ ├─────────────────────┤ vmctx+16 │heap1: start address │ ├─────────────────────┤ vmctx+24 - │heap1: end address │ + │heap1: bound │ ├─────────────────────┤ vmctx+32 │etc... │ └─────────────────────┘ @@ -443,6 +443,21 @@ block0(v0: i64, v1: i64, v2: i32): ; run: %heap_load_store(0, 1) == 1 ``` +##### `table` directive + +The `table` directive allows a test to request a table to be allocated and passed to the test via the environment struct. + +A sample table annotation is the following: +``` +; table: count=10, entry_size=8, ptr=vmctx+0, bound=vmctx+8 +``` + +This indicates the following: +* `count=10`: The table should have 10 entries. +* `entry_size=8`: Each entry should have a space of 8 bytes. +* `ptr=vmctx+0`: The pointer to the address to the start of this table is placed at offset 0 in the `vmctx` struct +* `bound=vmctx+8`: The bound of this table (number of entries) is placed at offset 8 in the `vmctx` struct + ### `test interpret` diff --git a/cranelift/filetests/filetests/runtests/tables.clif b/cranelift/filetests/filetests/runtests/tables.clif new file mode 100644 index 000000000000..5194f3d1a9a8 --- /dev/null +++ b/cranelift/filetests/filetests/runtests/tables.clif @@ -0,0 +1,163 @@ +test run +target x86_64 +target s390x +target aarch64 + +function %set_get_i64(i64 vmctx, i64, i64) -> i64 { + gv0 = vmctx + gv1 = load.i64 notrap aligned gv0 + gv2 = load.i64 notrap aligned gv0 +8 + table0 = dynamic gv1, element_size 8, bound gv2, index_type i64 + +block0(v0: i64, v1: i64, v2: i64): + v3 = table_addr.i64 table0, v1, +0 + store.i64 v2, v3 + v4 = load.i64 v3 + return v4 +} +; table: count=10, entry_size=8, ptr=vmctx+0, bound=vmctx+8 +; run: %set_get_i64(0, 1) == 1 +; run: %set_get_i64(0, 10) == 10 +; run: %set_get_i64(1, 1) == 1 +; run: %set_get_i64(1, 0xC0FFEEEE_DECAFFFF) == 0xC0FFEEEE_DECAFFFF +; run: %set_get_i64(10, 1) == 1 +; run: %set_get_i64(10, 0xC0FFEEEE_DECAFFFF) == 0xC0FFEEEE_DECAFFFF + +function %set_get_i32(i64 vmctx, i64, i32) -> i32 { + gv0 = vmctx + gv1 = load.i64 notrap aligned gv0 + gv2 = load.i64 notrap aligned gv0 +8 + table0 = dynamic gv1, element_size 8, bound gv2, index_type i64 + +block0(v0: i64, v1: i64, v2: i32): + ;; Note here the offset +4 + v3 = table_addr.i64 table0, v1, +4 + store.i32 v2, v3 + v4 = load.i32 v3 + return v4 +} +; table: count=10, entry_size=8, ptr=vmctx+0, bound=vmctx+8 +; run: %set_get_i32(0, 1) == 1 +; run: %set_get_i32(0, 10) == 10 +; run: %set_get_i32(1, 1) == 1 +; run: %set_get_i32(1, 0xC0FFEEEE) == 0xC0FFEEEE +; run: %set_get_i32(10, 1) == 1 +; run: %set_get_i32(10, 0xC0FFEEEE) == 0xC0FFEEEE + + +function %set_get_i8(i64 vmctx, i64, i8) -> i8 { + gv0 = vmctx + gv1 = load.i64 notrap aligned gv0 + gv2 = load.i64 notrap aligned gv0 +8 + table0 = dynamic gv1, element_size 1, bound gv2, index_type i64 + +block0(v0: i64, v1: i64, v2: i8): + v3 = table_addr.i64 table0, v1, +0 + store.i8 v2, v3 + v4 = load.i8 v3 + return v4 +} +; table: count=2, entry_size=1, ptr=vmctx+0, bound=vmctx+8 +; run: %set_get_i8(0, 1) == 1 +; run: %set_get_i8(0, 0xC0) == 0xC0 +; run: %set_get_i8(1, 1) == 1 +; run: %set_get_i8(1, 0xFF) == 0xFF + + + +function %large_elm_size(i64 vmctx, i64, i64, i8) -> i8 { + gv0 = vmctx + gv1 = load.i64 notrap aligned gv0 + gv2 = load.i64 notrap aligned gv0 +8 + table0 = dynamic gv1, element_size 10240, bound gv2, index_type i64 + +block0(v0: i64, v1: i64, v2: i64, v3: i8): + v4 = table_addr.i64 table0, v1, +0 + v5 = iadd.i64 v4, v2 + store.i8 v3, v5 + v6 = load.i8 v5 + return v6 +} +; table: count=5, entry_size=10240, ptr=vmctx+0, bound=vmctx+8 +; run: %large_elm_size(0, 0, 1) == 1 +; run: %large_elm_size(1, 0, 0xC0) == 0xC0 +; run: %large_elm_size(0, 1, 1) == 1 +; run: %large_elm_size(1, 1, 0xFF) == 0xFF +; run: %large_elm_size(0, 127, 1) == 1 +; run: %large_elm_size(1, 127, 0xFF) == 0xFF +; run: %large_elm_size(0, 10239, 1) == 1 +; run: %large_elm_size(1, 10239, 0xBB) == 0xBB + + +; Tests writing a i64 which covers 8 table entries at once +; Loads the first byte and the last to confirm that the slots were written +function %multi_elm_write(i64 vmctx, i64, i64) -> i8, i8 { + gv0 = vmctx + gv1 = load.i64 notrap aligned gv0 + gv2 = load.i64 notrap aligned gv0 +8 + table0 = dynamic gv1, element_size 1, bound gv2, index_type i64 + +block0(v0: i64, v1: i64, v2: i64): + v3 = table_addr.i64 table0, v1, +0 + v4 = table_addr.i64 table0, v1, +7 + store.i64 v2, v3 + v5 = load.i8 v3 + v6 = load.i8 v4 + return v5, v6 +} +; table: count=16, entry_size=1, ptr=vmctx+0, bound=vmctx+8 + +;; When writing these test cases keep in mind that s390x is big endian! +;; We just make sure that the first and last byte are the same to deal with that. +; run: %multi_elm_write(0, 0xC0FFEEEE_FFEEEEC0) == [0xC0, 0xC0] +; run: %multi_elm_write(1, 0xAABBCCDD_EEFF00AA) == [0xAA, 0xAA] + + + +; Tests requesting multiple tables and heaps. This is mostly to test the runtest context +; It also tests functions with a bunch of global values and tables and heaps +function %multi_tbl_heap(i64 vmctx, i64, i16) -> i16 { + gv0 = vmctx + gv1 = load.i64 notrap aligned gv0 +0 + gv2 = load.i64 notrap aligned gv0 +8 + gv3 = load.i64 notrap aligned gv0 +16 + gv4 = load.i64 notrap aligned gv0 +24 + gv5 = load.i64 notrap aligned gv0 +32 + gv6 = load.i64 notrap aligned gv0 +40 + gv7 = load.i64 notrap aligned gv0 +48 + gv8 = load.i64 notrap aligned gv0 +56 + gv9 = load.i64 notrap aligned gv0 +64 + gv10 = load.i64 notrap aligned gv0 +72 + gv11 = load.i64 notrap aligned gv0 +80 + gv12 = load.i64 notrap aligned gv0 +88 + gv13 = load.i64 notrap aligned gv0 +96 + gv14 = load.i64 notrap aligned gv0 +104 + + table0 = dynamic gv1, element_size 1, bound gv2, index_type i64 + table1 = dynamic gv3, element_size 2, bound gv4, index_type i64 + heap0 = static gv5, min 0x10, bound 0x10, offset_guard 0, index_type i64 + heap1 = dynamic gv7, bound gv8, offset_guard 0, index_type i64 + table2 = dynamic gv9, element_size 3, bound gv10, index_type i64 + heap2 = dynamic gv11, bound gv12, offset_guard 0, index_type i64 + table3 = dynamic gv13, element_size 3, bound gv14, index_type i64 + + +block0(v0: i64, v1: i64, v2: i16): + v3 = table_addr.i64 table3, v1, +0; + store.i16 v2, v3 + v4 = load.i16 v3 + return v4 +} +; table: count=1, entry_size=1, ptr=vmctx+0, bound=vmctx+8 +; table: count=23, entry_size=2, ptr=vmctx+16, bound=vmctx+24 +; heap: static, size=0x10, ptr=vmctx+32, bound=vmctx+40 +; heap: dynamic, size=0x20, ptr=vmctx+48, bound=vmctx+56 +; table: count=9, entry_size=3, ptr=vmctx+64, bound=vmctx+72 +; heap: dynamic, size=0x3000, ptr=vmctx+80, bound=vmctx+88 +; table: count=9, entry_size=3, ptr=vmctx+96, bound=vmctx+104 +; run: %multi_tbl_heap(0, 1) == 1 +; run: %multi_tbl_heap(0, 0xC0FF) == 0xC0FF +; run: %multi_tbl_heap(1, 1) == 1 +; run: %multi_tbl_heap(1, 0xC0FF) == 0xC0FF +; run: %multi_tbl_heap(9, 1) == 1 +; run: %multi_tbl_heap(9, 0xC0FF) == 0xC0FF diff --git a/cranelift/filetests/src/runtest_environment.rs b/cranelift/filetests/src/runtest_environment.rs index 5a7fe1564492..f8ae0bfb381c 100644 --- a/cranelift/filetests/src/runtest_environment.rs +++ b/cranelift/filetests/src/runtest_environment.rs @@ -1,39 +1,78 @@ use anyhow::anyhow; +use cranelift_codegen::ir::immediates::Uimm64; use cranelift_codegen::ir::{ArgumentPurpose, Function}; -use cranelift_reader::parse_heap_command; -use cranelift_reader::{Comment, HeapCommand}; +use cranelift_reader::{parse_heap_command, parse_table_command}; +use cranelift_reader::{Comment, HeapCommand, TableCommand}; + +#[derive(Debug, Clone)] +pub enum RuntestEntry { + Heap(HeapCommand), + Table(TableCommand), +} + +impl RuntestEntry { + /// Tries to parse an entry from a comment, returning None if it isn't possible + pub fn parse_from_comment(comment: &Comment) -> anyhow::Result> { + if let Some(heap_command) = parse_heap_command(comment.text)? { + return Ok(Some(RuntestEntry::Heap(heap_command))); + } + if let Some(table_command) = parse_table_command(comment.text)? { + return Ok(Some(RuntestEntry::Table(table_command))); + } + Ok(None) + } + + pub fn ptr_offset(&self) -> Option { + match self { + RuntestEntry::Heap(heap) => heap.ptr_offset, + RuntestEntry::Table(table) => table.ptr_offset, + } + } + + pub fn bound_offset(&self) -> Option { + match self { + RuntestEntry::Heap(heap) => heap.bound_offset, + RuntestEntry::Table(table) => table.bound_offset, + } + } +} /// Stores info about the expected environment for a test function. #[derive(Debug, Clone)] pub struct RuntestEnvironment { - pub heaps: Vec, + pub entries: Vec, } impl RuntestEnvironment { /// Parse the environment from a set of comments pub fn parse(comments: &[Comment]) -> anyhow::Result { - let mut env = RuntestEnvironment { heaps: Vec::new() }; + let mut env = RuntestEnvironment { + entries: Vec::new(), + }; + // The order of the VMCtx memory is going to be dictated by the order of the comments + // we also enforce the correct vmctx offsets on the comments based on that. for comment in comments.iter() { - if let Some(heap_command) = parse_heap_command(comment.text)? { - let heap_index = env.heaps.len() as u64; - let expected_ptr = heap_index * 16; - if Some(expected_ptr) != heap_command.ptr_offset.map(|p| p.into()) { + let entry = env.entries.len() as u64; + let expected_ptr = entry * 16; + let expected_bound = (entry * 16) + 8; + + if let Some(entry) = RuntestEntry::parse_from_comment(comment)? { + if Some(expected_ptr) != entry.ptr_offset().map(|p| p.into()) { return Err(anyhow!( "Invalid ptr offset, expected vmctx+{}", expected_ptr )); } - let expected_bound = (heap_index * 16) + 8; - if Some(expected_bound) != heap_command.bound_offset.map(|p| p.into()) { + if Some(expected_bound) != entry.bound_offset().map(|p| p.into()) { return Err(anyhow!( "Invalid bound offset, expected vmctx+{}", expected_bound )); } - env.heaps.push(heap_command); + env.entries.push(entry); }; } @@ -41,18 +80,7 @@ impl RuntestEnvironment { } pub fn is_active(&self) -> bool { - !self.heaps.is_empty() - } - - /// Allocates memory for heaps - pub fn allocate_memory(&self) -> Vec { - self.heaps - .iter() - .map(|cmd| { - let size: u64 = cmd.size.into(); - vec![0u8; size as usize] - }) - .collect() + !self.entries.is_empty() } /// Validates the signature of a [Function] ensuring that if this environment is active, the @@ -75,6 +103,34 @@ impl RuntestEnvironment { Ok(()) } + + /// Allocates a struct to be injected into the test. + pub fn runtime_struct( + &self, + mut alloc_heap: impl FnMut(u64) -> u64, + mut alloc_table: impl FnMut(u64, u64) -> u64, + ) -> Vec { + let context_struct = self + .entries + .iter() + .flat_map(|entry| match entry { + RuntestEntry::Heap(heap) => { + let size: u64 = heap.size.into(); + [alloc_heap(size), size] + } + RuntestEntry::Table(table) => { + let entry_size: u64 = table.entry_size.into(); + let entry_count: u64 = table.entry_count.into(); + let bytes = entry_size * entry_count; + + [alloc_table(entry_size, entry_count), bytes] + } + }) + .collect(); + + context_struct + } } pub(crate) type HeapMemory = Vec; +pub(crate) type TableMemory = Vec; diff --git a/cranelift/filetests/src/test_interpret.rs b/cranelift/filetests/src/test_interpret.rs index 6232978948f0..3b3b084c4a1d 100644 --- a/cranelift/filetests/src/test_interpret.rs +++ b/cranelift/filetests/src/test_interpret.rs @@ -82,28 +82,24 @@ pub fn register_heaps<'a>( state: &mut InterpreterState<'a>, test_env: &RuntestEnvironment, ) -> DataValue { - let mem = test_env.allocate_memory(); - let vmctx_struct = mem - .into_iter() - // This memory layout (a contiguous list of base + bound ptrs) - // is enforced by the RuntestEnvironment when parsing the heap - // directives. So we are safe to replicate that here. - .flat_map(|mem| { - let heap_len = mem.len() as u64; + let vmctx_struct = test_env.runtime_struct( + |size| { + let mem = vec![0u8; size as usize]; let heap = state.register_heap(HeapInit::FromBacking(mem)); - [ - state.get_heap_address(I64, heap, 0).unwrap(), - state.get_heap_address(I64, heap, heap_len).unwrap(), - ] - }) - .map(|addr| { + let addr = state.get_heap_address(I64, heap, 0).unwrap(); + let mut mem = [0u8; 8]; addr.write_to_slice(&mut mem[..]); - mem - }) - .flatten() + u64::from_ne_bytes(mem) + }, + |_size, _count| unimplemented!(), + ); + + let vmctx_mem = vmctx_struct + .into_iter() + .flat_map(|e| e.to_ne_bytes()) .collect(); - let vmctx_heap = state.register_heap(HeapInit::FromBacking(vmctx_struct)); + let vmctx_heap = state.register_heap(HeapInit::FromBacking(vmctx_mem)); state.get_heap_address(I64, vmctx_heap, 0).unwrap() } diff --git a/cranelift/filetests/src/test_run.rs b/cranelift/filetests/src/test_run.rs index 504d3e0f2d28..f70a14c760f7 100644 --- a/cranelift/filetests/src/test_run.rs +++ b/cranelift/filetests/src/test_run.rs @@ -3,7 +3,7 @@ //! The `run` test command compiles each function on the host machine and executes it use crate::function_runner::SingleFunctionCompiler; -use crate::runtest_environment::{HeapMemory, RuntestEnvironment}; +use crate::runtest_environment::{HeapMemory, RuntestEnvironment, TableMemory}; use crate::subtest::{Context, SubTest}; use cranelift_codegen::data_value::DataValue; use cranelift_codegen::ir; @@ -76,7 +76,7 @@ impl SubTest for TestRun { command .run(|_, run_args| { test_env.validate_signature(&func)?; - let (_heaps, _ctx_struct, vmctx_ptr) = + let (_heaps, _tables, _ctx_struct, vmctx_ptr) = build_vmctx_struct(&test_env, context.isa.unwrap().pointer_type()); let mut args = Vec::with_capacity(run_args.len()); @@ -98,19 +98,25 @@ impl SubTest for TestRun { pub fn build_vmctx_struct( test_env: &RuntestEnvironment, ptr_ty: Type, -) -> (Vec, Vec, DataValue) { - let heaps = test_env.allocate_memory(); - - let context_struct: Vec = heaps - .iter() - .flat_map(|heap| [heap.as_ptr(), heap.as_ptr().wrapping_add(heap.len())]) - .map(|p| p as usize as u64) - .collect(); +) -> (Vec, Vec, Vec, DataValue) { + let mut heaps = Vec::new(); + let mut tables = Vec::new(); + let context_struct = test_env.runtime_struct( + |size| { + heaps.push(vec![0u8; size as usize]); + heaps.last().unwrap().as_ptr() as usize as u64 + }, + |size, count| { + let bytes = size * count; + tables.push(vec![0u8; bytes as usize]); + tables.last().unwrap().as_ptr() as usize as u64 + }, + ); let ptr = context_struct.as_ptr() as usize as i128; let ptr_dv = DataValue::from_integer(ptr, ptr_ty).expect("Failed to cast pointer to native target size"); // Return all these to make sure we don't deallocate the heaps too early - (heaps, context_struct, ptr_dv) + (heaps, tables, context_struct, ptr_dv) } diff --git a/cranelift/reader/src/lib.rs b/cranelift/reader/src/lib.rs index 33ac7b2e6754..1ee1f3698574 100644 --- a/cranelift/reader/src/lib.rs +++ b/cranelift/reader/src/lib.rs @@ -29,10 +29,12 @@ pub use crate::error::{Location, ParseError, ParseResult}; pub use crate::heap_command::{HeapCommand, HeapType}; pub use crate::isaspec::{parse_options, IsaSpec, ParseOptionError}; pub use crate::parser::{ - parse_functions, parse_heap_command, parse_run_command, parse_test, ParseOptions, + parse_functions, parse_heap_command, parse_run_command, parse_table_command, parse_test, + ParseOptions, }; pub use crate::run_command::{Comparison, Invocation, RunCommand}; pub use crate::sourcemap::SourceMap; +pub use crate::table_command::TableCommand; pub use crate::testcommand::{TestCommand, TestOption}; pub use crate::testfile::{Comment, Details, Feature, TestFile}; @@ -43,5 +45,6 @@ mod lexer; mod parser; mod run_command; mod sourcemap; +mod table_command; mod testcommand; mod testfile; diff --git a/cranelift/reader/src/parser.rs b/cranelift/reader/src/parser.rs index f1f279ef1593..148dea80521d 100644 --- a/cranelift/reader/src/parser.rs +++ b/cranelift/reader/src/parser.rs @@ -6,6 +6,7 @@ use crate::isaspec; use crate::lexer::{LexError, Lexer, LocatedError, LocatedToken, Token}; use crate::run_command::{Comparison, Invocation, RunCommand}; use crate::sourcemap::SourceMap; +use crate::table_command::TableCommand; use crate::testcommand::TestCommand; use crate::testfile::{Comment, Details, Feature, TestFile}; use cranelift_codegen::data_value::DataValue; @@ -190,7 +191,7 @@ pub fn parse_run_command<'a>(text: &str, signature: &Signature) -> ParseResult(text: &str) -> ParseResult> { @@ -205,6 +206,24 @@ pub fn parse_heap_command<'a>(text: &str) -> ParseResult> { } } +/// Parse a CLIF comment `text` as a table command. +/// +/// Return: +/// - `Ok(None)` if the comment is not intended to be a `TableCommand` (i.e. does not start with `table`) +/// - `Ok(Some(table))` if the comment is intended as a `TableCommand` and can be parsed to one +/// - `Err` otherwise. +pub fn parse_table_command<'a>(text: &str) -> ParseResult> { + 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 TableCommand 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("table")) => parser.parse_table_command().map(|c| Some(c)), + Some(_) | None => Ok(None), + } +} + pub struct Parser<'a> { lex: Lexer<'a>, @@ -2411,6 +2430,66 @@ impl<'a> Parser<'a> { } } + /// Parse a CLIF table command. + /// + /// table-command ::= "table" ":" entry-size "," entry-count { "," ptr-offset } { "," bound-offset } + /// entry-size ::= "entry_size" "=" UImm64(bytes) + /// entry-count ::= "count" "=" UImm64(bytes) + /// ptr-offset ::= "ptr" "=" "vmctx" "+" UImm64(bytes) + /// bound-offset ::= "bound" "=" "vmctx" "+" UImm64(bytes) + fn parse_table_command(&mut self) -> ParseResult { + self.match_token(Token::Identifier("table"), "expected a 'table:' command")?; + self.match_token(Token::Colon, "expected a ':' after table command")?; + + let mut table_command = TableCommand { + entry_size: Uimm64::new(0), + entry_count: Uimm64::new(0), + ptr_offset: None, + bound_offset: None, + }; + + loop { + let identifier = self.match_any_identifier("expected table attribute name")?; + self.match_token(Token::Equal, "expected '=' after table attribute name")?; + + match identifier { + "entry_size" => { + table_command.entry_size = self.match_uimm64("expected integer entry size")?; + } + "count" => { + table_command.entry_count = self.match_uimm64("expected integer count")?; + } + "ptr" => { + table_command.ptr_offset = Some(self.parse_vmctx_offset()?); + } + "bound" => { + table_command.bound_offset = Some(self.parse_vmctx_offset()?); + } + t => return err!(self.loc, "unknown table attribute '{}'", t), + } + + if !self.optional(Token::Comma) { + break; + } + } + + if table_command.entry_size == Uimm64::new(0) { + return err!( + self.loc, + self.error("Expected a table entry size to be specified") + ); + } + + if table_command.entry_count == Uimm64::new(0) { + return err!( + self.loc, + self.error("Expected a table entry count to be specified") + ); + } + + Ok(table_command) + } + /// Parse a CLIF heap command. /// /// heap-command ::= "heap" ":" heap-type { "," heap-attr } @@ -3845,6 +3924,34 @@ mod tests { assert!(parse("heap: static, size=10, bound=vmctx-10").is_err()); } + #[test] + fn parse_table_commands() { + fn parse(text: &str) -> ParseResult { + Parser::new(text).parse_table_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("table: entry_size=8, count=20"); + assert_roundtrip("table: entry_size=8, count=20, ptr=vmctx+10"); + assert_roundtrip("table: entry_size=8, count=20, bound=vmctx+10"); + assert_roundtrip("table: entry_size=8, count=20, ptr=vmctx+10, bound=vmctx+20"); + + let sample_table = + parse("table: entry_size=8, count=20, ptr=vmctx+10, bound=vmctx+20").unwrap(); + assert_eq!(sample_table.entry_size, Uimm64::new(8)); + assert_eq!(sample_table.entry_count, Uimm64::new(20)); + assert_eq!(sample_table.ptr_offset, Some(Uimm64::new(10))); + assert_eq!(sample_table.bound_offset, Some(Uimm64::new(20))); + + assert!(parse("table:").is_err()); + assert!(parse("table: entry_size=9").is_err()); + assert!(parse("table: count=9").is_err()); + } + #[test] fn parse_data_values() { fn parse(text: &str, ty: Type) -> DataValue { diff --git a/cranelift/reader/src/table_command.rs b/cranelift/reader/src/table_command.rs new file mode 100644 index 000000000000..b3d3d8e5c641 --- /dev/null +++ b/cranelift/reader/src/table_command.rs @@ -0,0 +1,54 @@ +//! Table commands. +//! +//! Functions in a `.clif` file can have *table commands* appended that control the tables allocated +//! by the `test run` and `test interpret` infrastructure. +//! +//! The general syntax is: +//! - `; table: entry_size=n, count=m` +//! +//! +//! `entry_size=n` indicates the size of each entry (in bytes) on the table. +//! +//! `count=m` indicates the number of entries allocated on the table. + +use cranelift_codegen::ir::immediates::Uimm64; +use std::fmt::{self, Display, Formatter}; + +/// A table command appearing in a test file. +/// +/// For parsing, see `Parser::parse_table_command` +#[derive(PartialEq, Debug, Clone)] +pub struct TableCommand { + /// Size of each entry on the table. + pub entry_size: Uimm64, + /// Number of entries on the table. + pub entry_count: Uimm64, + /// Offset of the table pointer from the vmctx base + /// + /// This is done for verification purposes only + pub ptr_offset: Option, + /// Offset of the table pointer from the vmctx base + /// + /// This is done for verification purposes only + pub bound_offset: Option, +} + +impl Display for TableCommand { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "table: entry_size={}, count={}", + self.entry_size, self.entry_count + )?; + + if let Some(offset) = self.ptr_offset { + write!(f, ", ptr=vmctx+{}", offset)? + } + + if let Some(offset) = self.bound_offset { + write!(f, ", bound=vmctx+{}", offset)? + } + + Ok(()) + } +}