diff --git a/cranelift/filetests/isa/x86/legalize-table.clif b/cranelift/filetests/isa/x86/legalize-table.clif index afad7d8e21..f86913115d 100644 --- a/cranelift/filetests/isa/x86/legalize-table.clif +++ b/cranelift/filetests/isa/x86/legalize-table.clif @@ -8,7 +8,7 @@ target x86_64 function %test0(i64 vmctx, i64) -> i64 { gv0 = vmctx+12 gv1 = vmctx+14 - table0 = dynamic gv0, min 20, bound gv1, element_size 4 + table0 = dynamic gv0, min 20, bound gv1, element_size 4, index_type i64 ebb0(v0: i64, v1: i64): v2 = table_addr.i64 table0, v1, +3 diff --git a/cranelift/filetests/isa/x86/legalize-tables.clif b/cranelift/filetests/isa/x86/legalize-tables.clif new file mode 100644 index 0000000000..a9882f3cf9 --- /dev/null +++ b/cranelift/filetests/isa/x86/legalize-tables.clif @@ -0,0 +1,68 @@ +test legalizer +target x86_64 + +; Test legalization for various forms of table addresses. + +function %table_addrs(i32, i64, i64 vmctx) { + gv0 = vmctx+72 + gv1 = vmctx+80 + gv2 = vmctx+88 + gv3 = deref(gv2): i32 + + table0 = dynamic gv0, min 0x1_0000, bound gv3, element_size 1, index_type i32 + table1 = dynamic gv0, bound gv3, element_size 16, index_type i32 + table2 = dynamic gv0, min 0x1_0000, bound gv1, element_size 1, index_type i64 + table3 = dynamic gv0, bound gv1, element_size 16, index_type i64 + + ; check: table0 = dynamic gv0, min 0x0001_0000, bound gv3, element_size 1, index_type i32 + ; check: table1 = dynamic gv0, min 0, bound gv3, element_size 16, index_type i32 + ; check: table2 = dynamic gv0, min 0x0001_0000, bound gv1, element_size 1, index_type i64 + ; check: table3 = dynamic gv0, min 0, bound gv1, element_size 16, index_type i64 + +ebb0(v0: i32, v1: i64, v3: i64): + v4 = table_addr.i64 table0, v0, +0 + ; check: v12 = iadd_imm v3, 88 + ; check: v13 = load.i32 notrap aligned v12 + ; check: v8 = iadd_imm v13, 0 + ; check: v9 = icmp uge v0, v8 + ; check: brz v9, ebb1 + ; check: trap table_oob + ; check: ebb1: + ; check: v10 = uextend.i64 v0 + ; check: v11 = iadd_imm.i64 v3, 72 + ; check: v4 = iadd v11, v10 + + v5 = table_addr.i64 table1, v0, +0 + ; check: v19 = iadd_imm.i64 v3, 88 + ; check: v20 = load.i32 notrap aligned v19 + ; check: v14 = iadd_imm v20, 0 + ; check: v15 = icmp.i32 uge v0, v14 + ; check: brz v15, ebb2 + ; check: trap table_oob + ; check: ebb2: + ; check: v16 = uextend.i64 v0 + ; check: v17 = iadd_imm.i64 v3, 72 + ; check: v18 = ishl_imm v16, 4 + ; check: v5 = iadd v17, v18 + + v6 = table_addr.i64 table2, v1, +0 + ; check: v21 = iadd_imm.i64 v3, 80 + ; check: v22 = icmp.i64 uge v1, v21 + ; check: brz v22, ebb3 + ; check: trap table_oob + ; check: ebb3: + ; check: v23 = iadd_imm.i64 v3, 72 + ; check: v6 = iadd v23, v1 + + v7 = table_addr.i64 table3, v1, +0 + ; check: v24 = iadd_imm.i64 v3, 80 + ; check: v25 = icmp.i64 uge v1, v24 + ; check: brz v25, ebb4 + ; check: trap table_oob + ; check: ebb4: + ; check: v26 = iadd_imm.i64 v3, 72 + ; check: v27 = ishl_imm.i64 v1, 4 + ; check: v7 = iadd v26, v27 + + return +} diff --git a/cranelift/filetests/verifier/table.clif b/cranelift/filetests/verifier/table.clif new file mode 100644 index 0000000000..10fdaaf87f --- /dev/null +++ b/cranelift/filetests/verifier/table.clif @@ -0,0 +1,46 @@ +test verifier +target x86_64 + +function %table_base_type(i64 vmctx) { + gv0 = vmctx+0 + gv1 = deref(gv0): i32 + table0 = dynamic gv1, element_size 1, bound gv1, index_type i32 ; error: table base has type i32, which is not the pointer type i64 + +ebb0(v0: i64): + return +} + +function %invalid_base(i64 vmctx) { + gv0 = vmctx+0 + table0 = dynamic gv1, bound gv0, element_size 1, index_type i64 ; error: invalid base global value gv1 + +ebb0(v0: i64): + return +} + +function %invalid_bound(i64 vmctx) { + gv0 = vmctx+0 + table0 = dynamic gv0, bound gv1, element_size 1, index_type i64 ; error: invalid bound global value gv1 + +ebb0(v0: i64): + return +} + +function %table_bound_type(i64 vmctx) { + gv0 = vmctx+0 + gv1 = deref(gv0): i16 + table0 = dynamic gv0, bound gv1, element_size 1, index_type i32 ; error: table index type i32 differs from the type of its bound, i16 + +ebb0(v0: i64): + return +} + +function %table_addr_index_type(i64 vmctx, i64) { + gv0 = vmctx+0 + gv1 = deref(gv0): i32 + table0 = dynamic gv0, element_size 1, bound gv1, index_type i32 + +ebb0(v0: i64, v1: i64): + v2 = table_addr.i64 table0, v1, +0; error: index type i64 differs from table index type i32 + return +} diff --git a/lib/codegen/src/ir/table.rs b/lib/codegen/src/ir/table.rs index edfe18aec7..5db2402793 100644 --- a/lib/codegen/src/ir/table.rs +++ b/lib/codegen/src/ir/table.rs @@ -1,7 +1,7 @@ //! Tables. use ir::immediates::Imm64; -use ir::GlobalValue; +use ir::{GlobalValue, Type}; use std::fmt; /// Information about a table declaration. @@ -19,14 +19,18 @@ pub struct TableData { /// The size of a table element, in bytes. pub element_size: Imm64, + + /// The index type for the table. + pub index_type: Type, } impl fmt::Display for TableData { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("dynamic")?; write!( f, - "{}, min {}, bound {}, element_size {}", - self.base_gv, self.min_size, self.bound_gv, self.element_size + " {}, min {}, bound {}, element_size {}, index_type {}", + self.base_gv, self.min_size, self.bound_gv, self.element_size, self.index_type ) } } diff --git a/lib/codegen/src/legalizer/table.rs b/lib/codegen/src/legalizer/table.rs index fe017eb096..d7633780f2 100644 --- a/lib/codegen/src/legalizer/table.rs +++ b/lib/codegen/src/legalizer/table.rs @@ -49,7 +49,7 @@ fn dynamic_addr( pos.use_srcloc(inst); // Start with the bounds check. Trap if `index + 1 > bound`. - let bound = pos.ins().global_value(addr_ty, bound_gv); + let bound = pos.ins().global_value(index_ty, bound_gv); // `index > bound - 1` is the same as `index >= bound`. let oob = pos diff --git a/lib/codegen/src/verifier/mod.rs b/lib/codegen/src/verifier/mod.rs index eed8bc2d5b..33ec402449 100644 --- a/lib/codegen/src/verifier/mod.rs +++ b/lib/codegen/src/verifier/mod.rs @@ -433,6 +433,48 @@ impl<'a> Verifier<'a> { Ok(()) } + fn verify_tables(&self, errors: &mut VerifierErrors) -> VerifierStepResult<()> { + if let Some(isa) = self.isa { + for (table, table_data) in &self.func.tables { + let base = table_data.base_gv; + if !self.func.global_values.is_valid(base) { + return nonfatal!(errors, table, "invalid base global value {}", base); + } + + let pointer_type = isa.pointer_type(); + let base_type = self.func.global_values[base].global_type(isa); + if base_type != pointer_type { + report!( + errors, + table, + "table base has type {}, which is not the pointer type {}", + base_type, + pointer_type + ); + } + + let bound_gv = table_data.bound_gv; + if !self.func.global_values.is_valid(bound_gv) { + return nonfatal!(errors, table, "invalid bound global value {}", bound_gv); + } + + let index_type = table_data.index_type; + let bound_type = self.func.global_values[bound_gv].global_type(isa); + if index_type != bound_type { + report!( + errors, + table, + "table index type {} differs from the type of its bound, {}", + index_type, + bound_type + ); + } + } + } + + Ok(()) + } + fn ebb_integrity( &self, ebb: Ebb, @@ -1385,6 +1427,19 @@ impl<'a> Verifier<'a> { ); } } + ir::InstructionData::TableAddr { table, arg, .. } => { + let index_type = self.func.dfg.value_type(arg); + let table_index_type = self.func.tables[table].index_type; + if index_type != table_index_type { + return nonfatal!( + errors, + inst, + "index type {} differs from table index type {}", + index_type, + table_index_type + ); + } + } ir::InstructionData::UnaryGlobalValue { global_value, .. } => { if let Some(isa) = self.isa { let inst_type = self.func.dfg.value_type(self.func.dfg.first_result(inst)); @@ -1611,6 +1666,7 @@ impl<'a> Verifier<'a> { pub fn run(&self, errors: &mut VerifierErrors) -> VerifierStepResult<()> { self.verify_global_values(errors)?; self.verify_heaps(errors)?; + self.verify_tables(errors)?; self.typecheck_entry_block_params(errors)?; for ebb in self.func.layout.ebbs() { diff --git a/lib/codegen/src/write.rs b/lib/codegen/src/write.rs index 2d2fcf5621..71714727ae 100644 --- a/lib/codegen/src/write.rs +++ b/lib/codegen/src/write.rs @@ -46,6 +46,11 @@ pub trait FuncWriter { self.write_entity_definition(w, func, heap.into(), heap_data)?; } + for (table, table_data) in &func.tables { + any = true; + self.write_entity_definition(w, func, table.into(), table_data)?; + } + // Write out all signatures before functions since function declarations can refer to // signatures. for (sig, sig_data) in &func.dfg.signatures { diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index b7980c34be..3f607f1d15 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -198,6 +198,7 @@ impl<'a> Context<'a> { min_size: Imm64::new(0), bound_gv: GlobalValue::reserved_value(), element_size: Imm64::new(0), + index_type: VOID, }); } self.function.tables[table] = data; @@ -1161,6 +1162,7 @@ impl<'a> Parser<'a> { // heap-attr ::= "min" Imm64(bytes) // | "bound" Imm64(bytes) // | "guard" Imm64(bytes) + // | "index_type" type // fn parse_heap_decl(&mut self) -> ParseResult<(Heap, HeapData)> { let heap = self.match_heap("expected heap number: heap«n»")?; @@ -1230,6 +1232,7 @@ impl<'a> Parser<'a> { // table-attr ::= "min" Imm64(bytes) // | "bound" Imm64(bytes) // | "element_size" Imm64(bytes) + // | "index_type" type // fn parse_table_decl(&mut self) -> ParseResult<(Table, TableData)> { let table = self.match_table("expected table number: table«n»")?; @@ -1253,6 +1256,7 @@ impl<'a> Parser<'a> { min_size: 0.into(), bound_gv: GlobalValue::reserved_value(), element_size: 0.into(), + index_type: ir::types::I32, }; // table-desc ::= * { "," table-attr } @@ -1270,6 +1274,9 @@ impl<'a> Parser<'a> { "element_size" => { data.element_size = self.match_imm64("expected integer element size")?; } + "index_type" => { + data.index_type = self.match_type("expected index type")?; + } t => return err!(self.loc, "unknown table attribute '{}'", t), } } diff --git a/lib/wasm/src/environ/dummy.rs b/lib/wasm/src/environ/dummy.rs index 0010a41beb..767ae2c9d9 100644 --- a/lib/wasm/src/environ/dummy.rs +++ b/lib/wasm/src/environ/dummy.rs @@ -213,6 +213,7 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ min_size: Imm64::new(0), bound_gv, element_size: Imm64::new(i64::from(self.pointer_bytes()) * 2), + index_type: I32, }) }