diff --git a/cranelift/filetests/isa/x86/legalize-table.clif b/cranelift/filetests/isa/x86/legalize-table.clif new file mode 100644 index 0000000000..afad7d8e21 --- /dev/null +++ b/cranelift/filetests/isa/x86/legalize-table.clif @@ -0,0 +1,27 @@ +; Test legalization of tables +test legalizer +target x86_64 + +; regex: V=v\d+ +; regex: EBB=ebb\d+ + +function %test0(i64 vmctx, i64) -> i64 { + gv0 = vmctx+12 + gv1 = vmctx+14 + table0 = dynamic gv0, min 20, bound gv1, element_size 4 + +ebb0(v0: i64, v1: i64): + v2 = table_addr.i64 table0, v1, +3 + return v2 +} + +; check: $(bound=$V) = iadd_imm $(input=$V), 14 +; nextln: $(cond=$V) = icmp uge $(limit=$V), $bound +; nextln: brz $cond, ebb1 +; nextln: trap table_oob +; nextln: +; nextln: ebb1: +; nextln: $(base=$V) = iadd_imm.i64 $(vmctx=$V), 12 +; nextln: $(scaled=$V) = ishl_imm.i64 $(index=$V), 2 +; nextln: $(elem_addr=$V) = iadd $base, $scaled +; nextln: $(field_addr=$V) = iadd_imm $elem_addr, 3 diff --git a/lib/codegen/meta-python/base/entities.py b/lib/codegen/meta-python/base/entities.py index d0a0bc470f..7b11810f48 100644 --- a/lib/codegen/meta-python/base/entities.py +++ b/lib/codegen/meta-python/base/entities.py @@ -33,3 +33,6 @@ jump_table = EntityRefKind( #: A reference to a heap declared in the function preamble. heap = EntityRefKind('heap', 'A heap.') + +#: A reference to a table declared in the function preamble. +table = EntityRefKind('table', 'A table.') diff --git a/lib/codegen/meta-python/base/formats.py b/lib/codegen/meta-python/base/formats.py index 2642db6085..c008306409 100644 --- a/lib/codegen/meta-python/base/formats.py +++ b/lib/codegen/meta-python/base/formats.py @@ -11,7 +11,7 @@ from cdsl.operands import VALUE, VARIABLE_ARGS from .immediates import imm64, uimm8, uimm32, ieee32, ieee64, offset32 from .immediates import boolean, intcc, floatcc, memflags, regunit, trapcode from . import entities -from .entities import ebb, sig_ref, func_ref, stack_slot, heap +from .entities import ebb, sig_ref, func_ref, stack_slot, heap, table Unary = InstructionFormat(VALUE) UnaryImm = InstructionFormat(imm64) @@ -67,6 +67,9 @@ StackStore = InstructionFormat(VALUE, stack_slot, offset32) # Accessing a WebAssembly heap. HeapAddr = InstructionFormat(heap, VALUE, uimm32) +# Accessing a WebAssembly table. +TableAddr = InstructionFormat(table, VALUE, offset32) + RegMove = InstructionFormat(VALUE, ('src', regunit), ('dst', regunit)) CopySpecial = InstructionFormat(('src', regunit), ('dst', regunit)) RegSpill = InstructionFormat( diff --git a/lib/codegen/meta-python/base/instructions.py b/lib/codegen/meta-python/base/instructions.py index f2b9782ec3..84c704f888 100644 --- a/lib/codegen/meta-python/base/instructions.py +++ b/lib/codegen/meta-python/base/instructions.py @@ -221,6 +221,11 @@ call_indirect = Instruction( Call the function pointed to by `callee` with the given arguments. The called function must match the specified signature. + + Note that this is different from WebAssembly's ``call_indirect``; the + callee is a native address, rather than a table index. For WebAssembly, + :inst:`table_addr` and :inst:`load` are used to obtain a native address + from a table. """, ins=(SIG, callee, args), outs=rvals, is_call=True) @@ -531,6 +536,33 @@ heap_addr = Instruction( """, ins=(H, p, Size), outs=addr) +# +# WebAssembly bounds-checked table accesses. +# + +TableOffset = TypeVar('TableOffset', 'An unsigned table offset', ints=(32, 64)) + +T = Operand('T', entities.table) +p = Operand('p', TableOffset) +Offset = Operand('Offset', offset32, 'Byte offset from element address') + +table_addr = Instruction( + 'table_addr', r""" + Bounds check and compute absolute address of a table entry. + + Verify that the offset ``p`` is in bounds for the table T, and generate + an absolute address that is safe to dereference. + + ``Offset`` must be less than the size of a table element. + + 1. If ``p`` is not greater than the table bound, return an absolute + address corresponding to a byte offset of ``p`` from the table's + base address. + 2. If ``p`` is greater than the table bound, generate a trap. + """, + ins=(T, p, Offset), outs=addr) + + # # Materializing constants. # diff --git a/lib/codegen/meta-python/base/legalize.py b/lib/codegen/meta-python/base/legalize.py index ca4c5b9a6e..c63dde9289 100644 --- a/lib/codegen/meta-python/base/legalize.py +++ b/lib/codegen/meta-python/base/legalize.py @@ -65,6 +65,7 @@ expand_flags = XFormGroup('expand_flags', """ # Custom expansions for memory objects. expand.custom_legalize(insts.global_value, 'expand_global_value') expand.custom_legalize(insts.heap_addr, 'expand_heap_addr') +expand.custom_legalize(insts.table_addr, 'expand_table_addr') # Custom expansions for calls. expand.custom_legalize(insts.call, 'expand_call') diff --git a/lib/codegen/src/ir/entities.rs b/lib/codegen/src/ir/entities.rs index 7196c78f31..185af8b9e7 100644 --- a/lib/codegen/src/ir/entities.rs +++ b/lib/codegen/src/ir/entities.rs @@ -172,6 +172,24 @@ impl Heap { } } +/// A reference to a table. +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct Table(u32); +entity_impl!(Table, "table"); + +impl Table { + /// Create a new table reference from its number. + /// + /// This method is for use by the parser. + pub fn with_number(n: u32) -> Option { + if n < u32::MAX { + Some(Table(n)) + } else { + None + } + } +} + /// A reference to any of the entities defined in this module. #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum AnyEntity { @@ -195,6 +213,8 @@ pub enum AnyEntity { SigRef(SigRef), /// A heap. Heap(Heap), + /// A table. + Table(Table), } impl fmt::Display for AnyEntity { @@ -210,6 +230,7 @@ impl fmt::Display for AnyEntity { AnyEntity::FuncRef(r) => r.fmt(f), AnyEntity::SigRef(r) => r.fmt(f), AnyEntity::Heap(r) => r.fmt(f), + AnyEntity::Table(r) => r.fmt(f), } } } @@ -274,6 +295,12 @@ impl From for AnyEntity { } } +impl From for AnyEntity { + fn from(r: Table) -> Self { + AnyEntity::Table(r) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/lib/codegen/src/ir/function.rs b/lib/codegen/src/ir/function.rs index 3898709317..b6d0c80b43 100644 --- a/lib/codegen/src/ir/function.rs +++ b/lib/codegen/src/ir/function.rs @@ -9,7 +9,7 @@ use ir; use ir::{DataFlowGraph, ExternalName, Layout, Signature}; use ir::{ Ebb, ExtFuncData, FuncRef, GlobalValue, GlobalValueData, Heap, HeapData, JumpTable, - JumpTableData, SigRef, StackSlot, StackSlotData, + JumpTableData, SigRef, StackSlot, StackSlotData, Table, TableData, }; use ir::{EbbOffsets, InstEncodings, JumpTables, SourceLocs, StackSlots, ValueLocations}; use isa::{EncInfo, Encoding, Legalize, TargetIsa}; @@ -42,6 +42,9 @@ pub struct Function { /// Heaps referenced. pub heaps: PrimaryMap, + /// Tables referenced. + pub tables: PrimaryMap, + /// Jump tables used in this function. pub jump_tables: JumpTables, @@ -82,6 +85,7 @@ impl Function { stack_limit: None, global_values: PrimaryMap::new(), heaps: PrimaryMap::new(), + tables: PrimaryMap::new(), jump_tables: PrimaryMap::new(), dfg: DataFlowGraph::new(), layout: Layout::new(), @@ -98,6 +102,7 @@ impl Function { self.stack_slots.clear(); self.global_values.clear(); self.heaps.clear(); + self.tables.clear(); self.jump_tables.clear(); self.dfg.clear(); self.layout.clear(); @@ -157,6 +162,11 @@ impl Function { self.heaps.push(data) } + /// Declares a table accessible to the function. + pub fn create_table(&mut self, data: TableData) -> Table { + self.tables.push(data) + } + /// Return an object that can display this function with correct ISA-specific annotations. pub fn display<'a, I: Into>>(&'a self, isa: I) -> DisplayFunction<'a> { DisplayFunction(self, isa.into()) diff --git a/lib/codegen/src/ir/mod.rs b/lib/codegen/src/ir/mod.rs index f93ed993b4..5421911886 100644 --- a/lib/codegen/src/ir/mod.rs +++ b/lib/codegen/src/ir/mod.rs @@ -18,6 +18,7 @@ mod memflags; mod progpoint; mod sourceloc; pub mod stackslot; +mod table; mod trapcode; pub mod types; mod valueloc; @@ -25,7 +26,7 @@ mod valueloc; pub use ir::builder::{InsertBuilder, InstBuilder, InstBuilderBase, InstInserterBase}; pub use ir::dfg::{DataFlowGraph, ValueDef}; pub use ir::entities::{ - Ebb, FuncRef, GlobalValue, Heap, Inst, JumpTable, SigRef, StackSlot, Value, + Ebb, FuncRef, GlobalValue, Heap, Inst, JumpTable, SigRef, StackSlot, Table, Value, }; pub use ir::extfunc::{AbiParam, ArgumentExtension, ArgumentPurpose, ExtFuncData, Signature}; pub use ir::extname::ExternalName; @@ -40,6 +41,7 @@ pub use ir::memflags::MemFlags; pub use ir::progpoint::{ExpandedProgramPoint, ProgramOrder, ProgramPoint}; pub use ir::sourceloc::SourceLoc; pub use ir::stackslot::{StackSlotData, StackSlotKind, StackSlots}; +pub use ir::table::TableData; pub use ir::trapcode::TrapCode; pub use ir::types::Type; pub use ir::valueloc::{ArgumentLoc, ValueLoc}; diff --git a/lib/codegen/src/ir/table.rs b/lib/codegen/src/ir/table.rs new file mode 100644 index 0000000000..edfe18aec7 --- /dev/null +++ b/lib/codegen/src/ir/table.rs @@ -0,0 +1,32 @@ +//! Tables. + +use ir::immediates::Imm64; +use ir::GlobalValue; +use std::fmt; + +/// Information about a table declaration. +#[derive(Clone)] +pub struct TableData { + /// Global value giving the address of the start of the table. + pub base_gv: GlobalValue, + + /// Guaranteed minimum table size in elements. Table accesses before `min_size` don't need + /// bounds checking. + pub min_size: Imm64, + + /// Global value giving the current bound of the table, in elements. + pub bound_gv: GlobalValue, + + /// The size of a table element, in bytes. + pub element_size: Imm64, +} + +impl fmt::Display for TableData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}, min {}, bound {}, element_size {}", + self.base_gv, self.min_size, self.bound_gv, self.element_size + ) + } +} diff --git a/lib/codegen/src/ir/trapcode.rs b/lib/codegen/src/ir/trapcode.rs index fe712b7ffa..5bdb5a541d 100644 --- a/lib/codegen/src/ir/trapcode.rs +++ b/lib/codegen/src/ir/trapcode.rs @@ -16,10 +16,13 @@ pub enum TrapCode { /// A `heap_addr` instruction detected an out-of-bounds error. /// - /// Some out-of-bounds heap accesses are detected by a segmentation fault on the heap guard - /// pages. + /// Note that not all out-of-bounds heap accesses are reported this way; + /// some are detected by a segmentation fault on the heap guard pages. HeapOutOfBounds, + /// A `table_addr` instruction detected an out-of-bounds error. + TableOutOfBounds, + /// Other bounds checking error. OutOfBounds, @@ -52,6 +55,7 @@ impl Display for TrapCode { let identifier = match *self { StackOverflow => "stk_ovf", HeapOutOfBounds => "heap_oob", + TableOutOfBounds => "table_oob", OutOfBounds => "oob", IndirectCallToNull => "icall_null", BadSignature => "bad_sig", @@ -73,6 +77,7 @@ impl FromStr for TrapCode { match s { "stk_ovf" => Ok(StackOverflow), "heap_oob" => Ok(HeapOutOfBounds), + "table_oob" => Ok(TableOutOfBounds), "oob" => Ok(OutOfBounds), "icall_null" => Ok(IndirectCallToNull), "bad_sig" => Ok(BadSignature), @@ -92,9 +97,10 @@ mod tests { use std::string::ToString; // Everything but user-defined codes. - const CODES: [TrapCode; 8] = [ + const CODES: [TrapCode; 9] = [ TrapCode::StackOverflow, TrapCode::HeapOutOfBounds, + TrapCode::TableOutOfBounds, TrapCode::OutOfBounds, TrapCode::IndirectCallToNull, TrapCode::BadSignature, diff --git a/lib/codegen/src/legalizer/heap.rs b/lib/codegen/src/legalizer/heap.rs index c23b5d73b9..44f434ff8a 100644 --- a/lib/codegen/src/legalizer/heap.rs +++ b/lib/codegen/src/legalizer/heap.rs @@ -82,7 +82,7 @@ fn dynamic_addr( } pos.ins().trapnz(oob, ir::TrapCode::HeapOutOfBounds); - offset_addr(inst, heap, addr_ty, offset, offset_ty, pos.func); + compute_addr(inst, heap, addr_ty, offset, offset_ty, pos.func); } /// Expand a `heap_addr` for a static heap. @@ -134,13 +134,11 @@ fn static_addr( pos.ins().trapnz(oob, ir::TrapCode::HeapOutOfBounds); } - offset_addr(inst, heap, addr_ty, offset, offset_ty, pos.func); + compute_addr(inst, heap, addr_ty, offset, offset_ty, pos.func); } /// Emit code for the base address computation of a `heap_addr` instruction. -/// -/// -fn offset_addr( +fn compute_addr( inst: ir::Inst, heap: ir::Heap, addr_ty: ir::Type, diff --git a/lib/codegen/src/legalizer/mod.rs b/lib/codegen/src/legalizer/mod.rs index f11a652828..7c24a86723 100644 --- a/lib/codegen/src/legalizer/mod.rs +++ b/lib/codegen/src/legalizer/mod.rs @@ -26,11 +26,13 @@ mod globalvalue; mod heap; mod libcall; mod split; +mod table; use self::call::expand_call; use self::globalvalue::expand_global_value; use self::heap::expand_heap_addr; use self::libcall::expand_as_libcall; +use self::table::expand_table_addr; /// Legalize `inst` for `isa`. Return true if any changes to the code were /// made; return false if the instruction was successfully encoded as is. diff --git a/lib/codegen/src/legalizer/table.rs b/lib/codegen/src/legalizer/table.rs new file mode 100644 index 0000000000..fe017eb096 --- /dev/null +++ b/lib/codegen/src/legalizer/table.rs @@ -0,0 +1,115 @@ +//! Legalization of tables. +//! +//! This module exports the `expand_table_addr` function which transforms a `table_addr` +//! instruction into code that depends on the kind of table referenced. + +use cursor::{Cursor, FuncCursor}; +use flowgraph::ControlFlowGraph; +use ir::condcodes::IntCC; +use ir::immediates::Offset32; +use ir::{self, InstBuilder}; +use isa::TargetIsa; + +/// Expand a `table_addr` instruction according to the definition of the table. +pub fn expand_table_addr( + inst: ir::Inst, + func: &mut ir::Function, + _cfg: &mut ControlFlowGraph, + _isa: &TargetIsa, +) { + // Unpack the instruction. + let (table, index, element_offset) = match func.dfg[inst] { + ir::InstructionData::TableAddr { + opcode, + table, + arg, + offset, + } => { + debug_assert_eq!(opcode, ir::Opcode::TableAddr); + (table, arg, offset) + } + _ => panic!("Wanted table_addr: {}", func.dfg.display_inst(inst, None)), + }; + + dynamic_addr(inst, table, index, element_offset, func); +} + +/// Expand a `table_addr` for a dynamic table. +fn dynamic_addr( + inst: ir::Inst, + table: ir::Table, + index: ir::Value, + element_offset: Offset32, + func: &mut ir::Function, +) { + let bound_gv = func.tables[table].bound_gv; + let index_ty = func.dfg.value_type(index); + let addr_ty = func.dfg.value_type(func.dfg.first_result(inst)); + let mut pos = FuncCursor::new(func).at_inst(inst); + pos.use_srcloc(inst); + + // Start with the bounds check. Trap if `index + 1 > bound`. + let bound = pos.ins().global_value(addr_ty, bound_gv); + + // `index > bound - 1` is the same as `index >= bound`. + let oob = pos + .ins() + .icmp(IntCC::UnsignedGreaterThanOrEqual, index, bound); + pos.ins().trapnz(oob, ir::TrapCode::TableOutOfBounds); + + compute_addr( + inst, + table, + addr_ty, + index, + index_ty, + element_offset, + pos.func, + ); +} + +/// Emit code for the base address computation of a `table_addr` instruction. +fn compute_addr( + inst: ir::Inst, + table: ir::Table, + addr_ty: ir::Type, + mut index: ir::Value, + index_ty: ir::Type, + element_offset: Offset32, + func: &mut ir::Function, +) { + let mut pos = FuncCursor::new(func).at_inst(inst); + pos.use_srcloc(inst); + + // Convert `index` to `addr_ty`. + if index_ty != addr_ty { + index = pos.ins().uextend(addr_ty, index); + } + + // Add the table base address base + let base_gv = pos.func.tables[table].base_gv; + let base = pos.ins().global_value(addr_ty, base_gv); + + let element_size = pos.func.tables[table].element_size; + let mut offset; + let element_size_i64: i64 = element_size.into(); + debug_assert!(element_size_i64 >= 0); + let element_size_u64 = element_size_i64 as u64; + if element_size_u64 == 1 { + offset = index; + } else if element_size_u64.is_power_of_two() { + offset = pos + .ins() + .ishl_imm(index, i64::from(element_size_u64.trailing_zeros())); + } else { + offset = pos.ins().imul_imm(index, element_size); + } + + if element_offset == Offset32::new(0) { + pos.func.dfg.replace(inst).iadd(base, offset); + } else { + let imm: i64 = element_offset.into(); + offset = pos.ins().iadd(base, offset); + pos.func.dfg.replace(inst).iadd_imm(offset, imm); + } +} diff --git a/lib/codegen/src/verifier/mod.rs b/lib/codegen/src/verifier/mod.rs index e0ff926bbc..2888d5336d 100644 --- a/lib/codegen/src/verifier/mod.rs +++ b/lib/codegen/src/verifier/mod.rs @@ -349,6 +349,9 @@ impl<'a> Verifier<'a> { HeapAddr { heap, .. } => { self.verify_heap(inst, heap)?; } + TableAddr { table, .. } => { + self.verify_table(inst, table)?; + } RegSpill { dst, .. } => { self.verify_stack_slot(inst, dst)?; } @@ -445,6 +448,14 @@ impl<'a> Verifier<'a> { } } + fn verify_table(&self, inst: Inst, table: ir::Table) -> VerifierResult<()> { + if !self.func.tables.is_valid(table) { + err!(inst, "invalid table {}", table) + } else { + Ok(()) + } + } + fn verify_value_list(&self, inst: Inst, l: &ValueList) -> VerifierResult<()> { if !l.is_valid(&self.func.dfg.value_lists) { err!(inst, "invalid value list reference {:?}", l) diff --git a/lib/codegen/src/write.rs b/lib/codegen/src/write.rs index 2f0f6ba25f..dc9282a07e 100644 --- a/lib/codegen/src/write.rs +++ b/lib/codegen/src/write.rs @@ -440,6 +440,7 @@ pub fn write_operands( .. } => write!(w, " {}, {}{}", arg, stack_slot, offset), HeapAddr { heap, arg, imm, .. } => write!(w, " {}, {}, {}", heap, arg, imm), + TableAddr { table, arg, .. } => write!(w, " {}, {}", table, arg), Load { flags, arg, offset, .. } => write!(w, "{} {}{}", flags, arg, offset), diff --git a/lib/reader/src/lexer.rs b/lib/reader/src/lexer.rs index 41e1a37b8b..5a2a0124a2 100644 --- a/lib/reader/src/lexer.rs +++ b/lib/reader/src/lexer.rs @@ -36,6 +36,7 @@ pub enum Token<'a> { StackSlot(u32), // ss3 GlobalValue(u32), // gv3 Heap(u32), // heap2 + Table(u32), // table2 JumpTable(u32), // jt2 FuncRef(u32), // fn2 SigRef(u32), // sig2 @@ -340,6 +341,7 @@ impl<'a> Lexer<'a> { "ss" => Some(Token::StackSlot(number)), "gv" => Some(Token::GlobalValue(number)), "heap" => Some(Token::Heap(number)), + "table" => Some(Token::Table(number)), "jt" => Some(Token::JumpTable(number)), "fn" => Some(Token::FuncRef(number)), "sig" => Some(Token::SigRef(number)), diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 2c73d099d9..62154a6944 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -9,7 +9,8 @@ use cranelift_codegen::ir::types::VOID; use cranelift_codegen::ir::{ AbiParam, ArgumentExtension, ArgumentLoc, Ebb, ExtFuncData, ExternalName, FuncRef, Function, GlobalValue, GlobalValueData, Heap, HeapData, HeapStyle, JumpTable, JumpTableData, MemFlags, - Opcode, SigRef, Signature, StackSlot, StackSlotData, StackSlotKind, Type, Value, ValueLoc, + Opcode, SigRef, Signature, StackSlot, StackSlotData, StackSlotKind, Table, TableData, Type, + Value, ValueLoc, }; use cranelift_codegen::isa::{self, Encoding, RegUnit, TargetIsa}; use cranelift_codegen::packed_option::ReservedValue; @@ -188,6 +189,29 @@ impl<'a> Context<'a> { } } + // Allocate a table slot. + fn add_table(&mut self, table: Table, data: TableData, loc: Location) -> ParseResult<()> { + while self.function.tables.next_key().index() <= table.index() { + self.function.create_table(TableData { + base_gv: GlobalValue::reserved_value(), + min_size: Imm64::new(0), + bound_gv: GlobalValue::reserved_value(), + element_size: Imm64::new(0), + }); + } + self.function.tables[table] = data; + self.map.def_table(table, loc) + } + + // Resolve a reference to a table. + fn check_table(&self, table: Table, loc: &Location) -> ParseResult<()> { + if !self.map.contains_table(table) { + err!(loc, "undefined table {}", table) + } else { + Ok(()) + } + } + // Allocate a new signature. fn add_sig(&mut self, sig: SigRef, data: Signature, loc: Location) -> ParseResult<()> { self.map.def_sig(sig, loc)?; @@ -447,6 +471,17 @@ impl<'a> Parser<'a> { err!(self.loc, err_msg) } + // Match and consume a table reference. + fn match_table(&mut self, err_msg: &str) -> ParseResult
{ + if let Some(Token::Table(table)) = self.token() { + self.consume(); + if let Some(table) = Table::with_number(table) { + return Ok(table); + } + } + err!(self.loc, err_msg) + } + // Match and consume a jump table reference. fn match_jt(&mut self) -> ParseResult { if let Some(Token::JumpTable(jt)) = self.token() { @@ -1019,6 +1054,11 @@ impl<'a> Parser<'a> { self.parse_heap_decl() .and_then(|(heap, dat)| ctx.add_heap(heap, dat, self.loc)) } + Some(Token::Table(..)) => { + self.start_gathering_comments(); + self.parse_table_decl() + .and_then(|(table, dat)| ctx.add_table(table, dat, self.loc)) + } Some(Token::SigRef(..)) => { self.start_gathering_comments(); self.parse_signature_decl(ctx.unique_isa) @@ -1129,7 +1169,7 @@ impl<'a> Parser<'a> { // heap-style ::= "static" | "dynamic" // heap-base ::= GlobalValue(base) // heap-attr ::= "min" Imm64(bytes) - // | "max" Imm64(bytes) + // | "bound" Imm64(bytes) // | "guard" Imm64(bytes) // fn parse_heap_decl(&mut self) -> ParseResult<(Heap, HeapData)> { @@ -1187,6 +1227,66 @@ impl<'a> Parser<'a> { Ok((heap, data)) } + // Parse a table decl. + // + // table-decl ::= * Table(table) "=" table-desc + // table-desc ::= table-style table-base { "," table-attr } + // table-style ::= "dynamic" + // table-base ::= GlobalValue(base) + // table-attr ::= "min" Imm64(bytes) + // | "bound" Imm64(bytes) + // | "element_size" Imm64(bytes) + // + fn parse_table_decl(&mut self) -> ParseResult<(Table, TableData)> { + let table = self.match_table("expected table number: table«n»")?; + self.match_token(Token::Equal, "expected '=' in table declaration")?; + + let style_name = self.match_any_identifier("expected 'static' or 'dynamic'")?; + + // table-desc ::= table-style * table-base { "," table-attr } + // table-base ::= * GlobalValue(base) + let base = match self.token() { + Some(Token::GlobalValue(base_num)) => match GlobalValue::with_number(base_num) { + Some(gv) => gv, + None => return err!(self.loc, "invalid global value number for table base"), + }, + _ => return err!(self.loc, "expected table base"), + }; + self.consume(); + + let mut data = TableData { + base_gv: base, + min_size: 0.into(), + bound_gv: GlobalValue::reserved_value(), + element_size: 0.into(), + }; + + // table-desc ::= * { "," table-attr } + while self.optional(Token::Comma) { + match self.match_any_identifier("expected table attribute name")? { + "min" => { + data.min_size = self.match_imm64("expected integer min size")?; + } + "bound" => { + data.bound_gv = match style_name { + "dynamic" => self.match_gv("expected gv bound")?, + t => return err!(self.loc, "unknown table style '{}'", t), + }; + } + "element_size" => { + data.element_size = self.match_imm64("expected integer element size")?; + } + t => return err!(self.loc, "unknown table attribute '{}'", t), + } + } + + // Collect any trailing comments. + self.token(); + self.claim_gathered_comments(table); + + Ok((table, data)) + } + // Parse a signature decl. // // signature-decl ::= SigRef(sigref) "=" signature @@ -2155,6 +2255,20 @@ impl<'a> Parser<'a> { imm, } } + InstructionFormat::TableAddr => { + let table = self.match_table("expected table identifier")?; + ctx.check_table(table, &self.loc)?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let arg = self.match_value("expected SSA value table address")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let offset = self.optional_offset32()?; + InstructionData::TableAddr { + opcode, + table, + arg, + offset, + } + } InstructionFormat::Load => { let flags = self.optional_memflags(); let addr = self.match_value("expected SSA value address")?; diff --git a/lib/reader/src/sourcemap.rs b/lib/reader/src/sourcemap.rs index 17276d325d..4103d08450 100644 --- a/lib/reader/src/sourcemap.rs +++ b/lib/reader/src/sourcemap.rs @@ -7,7 +7,9 @@ //! to parser clients. use cranelift_codegen::ir::entities::AnyEntity; -use cranelift_codegen::ir::{Ebb, FuncRef, GlobalValue, Heap, JumpTable, SigRef, StackSlot, Value}; +use cranelift_codegen::ir::{ + Ebb, FuncRef, GlobalValue, Heap, JumpTable, SigRef, StackSlot, Table, Value, +}; use error::{Location, ParseResult}; use lexer::split_entity_name; use std::collections::HashMap; @@ -46,6 +48,11 @@ impl SourceMap { self.locations.contains_key(&heap.into()) } + /// Look up a table entity. + pub fn contains_table(&self, table: Table) -> bool { + self.locations.contains_key(&table.into()) + } + /// Look up a signature entity. pub fn contains_sig(&self, sig: SigRef) -> bool { self.locations.contains_key(&sig.into()) @@ -100,6 +107,13 @@ impl SourceMap { Some(heap.into()) } }), + "table" => Table::with_number(num).and_then(|table| { + if !self.contains_table(table) { + None + } else { + Some(table.into()) + } + }), "sig" => SigRef::with_number(num).and_then(|sig| { if !self.contains_sig(sig) { None @@ -164,6 +178,11 @@ impl SourceMap { self.def_entity(entity.into(), loc) } + /// Define the table `entity`. + pub fn def_table(&mut self, entity: Table, loc: Location) -> ParseResult<()> { + self.def_entity(entity.into(), loc) + } + /// Define the signature `entity`. pub fn def_sig(&mut self, entity: SigRef, loc: Location) -> ParseResult<()> { self.def_entity(entity.into(), loc) diff --git a/lib/serde/src/serde_clif_json.rs b/lib/serde/src/serde_clif_json.rs index d466e4d0d0..9bd44b5992 100644 --- a/lib/serde/src/serde_clif_json.rs +++ b/lib/serde/src/serde_clif_json.rs @@ -176,6 +176,12 @@ pub enum SerInstData { heap: String, imm: String, }, + TableAddr { + opcode: String, + arg: String, + table: String, + offset: String, + }, RegMove { opcode: String, arg: String, @@ -572,6 +578,17 @@ pub fn get_inst_data(inst_index: Inst, func: &Function) -> SerInstData { heap: heap.to_string(), imm: imm.to_string(), }, + InstructionData::TableAddr { + opcode, + arg, + table, + offset, + } => SerInstData::TableAddr { + opcode: opcode.to_string(), + arg: arg.to_string(), + table: table.to_string(), + offset: offset.to_string(), + }, InstructionData::RegMove { opcode, arg, diff --git a/lib/wasm/src/code_translator.rs b/lib/wasm/src/code_translator.rs index dec6dda896..dacae0a2c9 100644 --- a/lib/wasm/src/code_translator.rs +++ b/lib/wasm/src/code_translator.rs @@ -377,10 +377,12 @@ pub fn translate_operator( // `index` is the index of the function's signature and `table_index` is the index of // the table to search the function in. let (sigref, num_args) = state.get_indirect_sig(builder.func, index, environ); + let table = state.get_table(builder.func, table_index, environ); let callee = state.pop1(); let call = environ.translate_call_indirect( builder.cursor(), table_index as TableIndex, + table, index as SignatureIndex, sigref, callee, diff --git a/lib/wasm/src/environ/dummy.rs b/lib/wasm/src/environ/dummy.rs index 68e7e2f556..1fdbbe7df3 100644 --- a/lib/wasm/src/environ/dummy.rs +++ b/lib/wasm/src/environ/dummy.rs @@ -1,6 +1,7 @@ //! "Dummy" environment for testing wasm translation. use cranelift_codegen::cursor::FuncCursor; +use cranelift_codegen::ir::immediates::Imm64; use cranelift_codegen::ir::types::*; use cranelift_codegen::ir::{self, InstBuilder}; use cranelift_codegen::settings; @@ -169,7 +170,11 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ fn make_heap(&mut self, func: &mut ir::Function, _index: MemoryIndex) -> ir::Heap { // Create a static heap whose base address is stored at `vmctx+0`. - let gv = func.create_global_value(ir::GlobalValueData::VMContext { offset: 0.into() }); + let addr = func.create_global_value(ir::GlobalValueData::VMContext { offset: 0.into() }); + let gv = func.create_global_value(ir::GlobalValueData::Deref { + base: addr, + offset: 0.into(), + }); func.create_heap(ir::HeapData { base: gv, @@ -181,6 +186,29 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ }) } + fn make_table(&mut self, func: &mut ir::Function, _index: TableIndex) -> ir::Table { + // Create a table whose base address is stored at `vmctx+0`. + let base_gv_addr = + func.create_global_value(ir::GlobalValueData::VMContext { offset: 0.into() }); + let base_gv = func.create_global_value(ir::GlobalValueData::Deref { + base: base_gv_addr, + offset: 0.into(), + }); + let bound_gv_addr = + func.create_global_value(ir::GlobalValueData::VMContext { offset: 0.into() }); + let bound_gv = func.create_global_value(ir::GlobalValueData::Deref { + base: bound_gv_addr, + offset: 0.into(), + }); + + func.create_table(ir::TableData { + base_gv, + min_size: Imm64::new(0), + bound_gv, + element_size: Imm64::new(i64::from(self.pointer_bytes()) * 2), + }) + } + fn make_indirect_sig(&mut self, func: &mut ir::Function, index: SignatureIndex) -> ir::SigRef { // A real implementation would probably change the calling convention and add `vmctx` and // signature index arguments. @@ -204,6 +232,7 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ &mut self, mut pos: FuncCursor, _table_index: TableIndex, + _table: ir::Table, _sig_index: SignatureIndex, sig_ref: ir::SigRef, callee: ir::Value, diff --git a/lib/wasm/src/environ/spec.rs b/lib/wasm/src/environ/spec.rs index 4852790834..1a002d5da1 100644 --- a/lib/wasm/src/environ/spec.rs +++ b/lib/wasm/src/environ/spec.rs @@ -89,6 +89,11 @@ pub trait FuncEnvironment { ir::Type::int(u16::from(self.triple().pointer_width().unwrap().bits())).unwrap() } + /// Get the size of a native pointer, in bytes. + fn pointer_bytes(&self) -> u8 { + self.triple().pointer_width().unwrap().bytes() + } + /// Set up the necessary preamble definitions in `func` to access the global variable /// identified by `index`. /// @@ -104,6 +109,12 @@ pub trait FuncEnvironment { /// The index space covers both imported and locally declared memories. fn make_heap(&mut self, func: &mut ir::Function, index: MemoryIndex) -> ir::Heap; + /// Set up the necessary preamble definitions in `func` to access the table identified + /// by `index`. + /// + /// The index space covers both imported and locally declared tables. + fn make_table(&mut self, func: &mut ir::Function, index: TableIndex) -> ir::Table; + /// Set up a signature definition in the preamble of `func` that can be used for an indirect /// call with signature `index`. /// @@ -141,6 +152,7 @@ pub trait FuncEnvironment { &mut self, pos: FuncCursor, table_index: TableIndex, + table: ir::Table, sig_index: SignatureIndex, sig_ref: ir::SigRef, callee: ir::Value, diff --git a/lib/wasm/src/state.rs b/lib/wasm/src/state.rs index ff7ced6b78..d81234fbde 100644 --- a/lib/wasm/src/state.rs +++ b/lib/wasm/src/state.rs @@ -7,7 +7,7 @@ use cranelift_codegen::ir::{self, Ebb, Inst, Value}; use environ::{FuncEnvironment, GlobalVariable}; use std::collections::HashMap; use std::vec::Vec; -use translation_utils::{FunctionIndex, GlobalIndex, MemoryIndex, SignatureIndex}; +use translation_utils::{FunctionIndex, GlobalIndex, MemoryIndex, SignatureIndex, TableIndex}; /// A control stack frame can be an `if`, a `block` or a `loop`, each one having the following /// fields: @@ -140,6 +140,9 @@ pub struct TranslationState { // Map of heaps that have been created by `FuncEnvironment::make_heap`. heaps: HashMap, + // Map of tables that have been created by `FuncEnvironment::make_table`. + tables: HashMap, + // Map of indirect call signatures that have been created by // `FuncEnvironment::make_indirect_sig()`. // Stores both the signature reference and the number of WebAssembly arguments @@ -159,6 +162,7 @@ impl TranslationState { reachable: true, globals: HashMap::new(), heaps: HashMap::new(), + tables: HashMap::new(), signatures: HashMap::new(), functions: HashMap::new(), } @@ -303,6 +307,21 @@ impl TranslationState { .or_insert_with(|| environ.make_heap(func, index)) } + /// Get the `Table` reference that should be used to access table `index`. + /// Create the reference if necessary. + pub fn get_table( + &mut self, + func: &mut ir::Function, + index: u32, + environ: &mut FE, + ) -> ir::Table { + let index = index as TableIndex; + *self + .tables + .entry(index) + .or_insert_with(|| environ.make_table(func, index)) + } + /// Get the `SigRef` reference that should be used to make an indirect call with signature /// `index`. Also return the number of WebAssembly arguments in the signature. ///