Define a "table" concept.

"Table" is to WebAssembly tables as "Heap" is to WebAssembly linear
memories.
This commit is contained in:
Dan Gohman
2018-05-20 07:48:46 -07:00
parent cd75176f10
commit 1b30265c5c
23 changed files with 500 additions and 16 deletions

View File

@@ -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

View File

@@ -33,3 +33,6 @@ jump_table = EntityRefKind(
#: A reference to a heap declared in the function preamble. #: A reference to a heap declared in the function preamble.
heap = EntityRefKind('heap', 'A heap.') heap = EntityRefKind('heap', 'A heap.')
#: A reference to a table declared in the function preamble.
table = EntityRefKind('table', 'A table.')

View File

@@ -11,7 +11,7 @@ from cdsl.operands import VALUE, VARIABLE_ARGS
from .immediates import imm64, uimm8, uimm32, ieee32, ieee64, offset32 from .immediates import imm64, uimm8, uimm32, ieee32, ieee64, offset32
from .immediates import boolean, intcc, floatcc, memflags, regunit, trapcode from .immediates import boolean, intcc, floatcc, memflags, regunit, trapcode
from . import entities 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) Unary = InstructionFormat(VALUE)
UnaryImm = InstructionFormat(imm64) UnaryImm = InstructionFormat(imm64)
@@ -67,6 +67,9 @@ StackStore = InstructionFormat(VALUE, stack_slot, offset32)
# Accessing a WebAssembly heap. # Accessing a WebAssembly heap.
HeapAddr = InstructionFormat(heap, VALUE, uimm32) HeapAddr = InstructionFormat(heap, VALUE, uimm32)
# Accessing a WebAssembly table.
TableAddr = InstructionFormat(table, VALUE, offset32)
RegMove = InstructionFormat(VALUE, ('src', regunit), ('dst', regunit)) RegMove = InstructionFormat(VALUE, ('src', regunit), ('dst', regunit))
CopySpecial = InstructionFormat(('src', regunit), ('dst', regunit)) CopySpecial = InstructionFormat(('src', regunit), ('dst', regunit))
RegSpill = InstructionFormat( RegSpill = InstructionFormat(

View File

@@ -221,6 +221,11 @@ call_indirect = Instruction(
Call the function pointed to by `callee` with the given arguments. The Call the function pointed to by `callee` with the given arguments. The
called function must match the specified signature. 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) ins=(SIG, callee, args), outs=rvals, is_call=True)
@@ -531,6 +536,33 @@ heap_addr = Instruction(
""", """,
ins=(H, p, Size), outs=addr) 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. # Materializing constants.
# #

View File

@@ -65,6 +65,7 @@ expand_flags = XFormGroup('expand_flags', """
# Custom expansions for memory objects. # Custom expansions for memory objects.
expand.custom_legalize(insts.global_value, 'expand_global_value') expand.custom_legalize(insts.global_value, 'expand_global_value')
expand.custom_legalize(insts.heap_addr, 'expand_heap_addr') expand.custom_legalize(insts.heap_addr, 'expand_heap_addr')
expand.custom_legalize(insts.table_addr, 'expand_table_addr')
# Custom expansions for calls. # Custom expansions for calls.
expand.custom_legalize(insts.call, 'expand_call') expand.custom_legalize(insts.call, 'expand_call')

View File

@@ -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<Self> {
if n < u32::MAX {
Some(Table(n))
} else {
None
}
}
}
/// A reference to any of the entities defined in this module. /// A reference to any of the entities defined in this module.
#[derive(Copy, Clone, PartialEq, Eq, Hash)] #[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum AnyEntity { pub enum AnyEntity {
@@ -195,6 +213,8 @@ pub enum AnyEntity {
SigRef(SigRef), SigRef(SigRef),
/// A heap. /// A heap.
Heap(Heap), Heap(Heap),
/// A table.
Table(Table),
} }
impl fmt::Display for AnyEntity { impl fmt::Display for AnyEntity {
@@ -210,6 +230,7 @@ impl fmt::Display for AnyEntity {
AnyEntity::FuncRef(r) => r.fmt(f), AnyEntity::FuncRef(r) => r.fmt(f),
AnyEntity::SigRef(r) => r.fmt(f), AnyEntity::SigRef(r) => r.fmt(f),
AnyEntity::Heap(r) => r.fmt(f), AnyEntity::Heap(r) => r.fmt(f),
AnyEntity::Table(r) => r.fmt(f),
} }
} }
} }
@@ -274,6 +295,12 @@ impl From<Heap> for AnyEntity {
} }
} }
impl From<Table> for AnyEntity {
fn from(r: Table) -> Self {
AnyEntity::Table(r)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@@ -9,7 +9,7 @@ use ir;
use ir::{DataFlowGraph, ExternalName, Layout, Signature}; use ir::{DataFlowGraph, ExternalName, Layout, Signature};
use ir::{ use ir::{
Ebb, ExtFuncData, FuncRef, GlobalValue, GlobalValueData, Heap, HeapData, JumpTable, 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 ir::{EbbOffsets, InstEncodings, JumpTables, SourceLocs, StackSlots, ValueLocations};
use isa::{EncInfo, Encoding, Legalize, TargetIsa}; use isa::{EncInfo, Encoding, Legalize, TargetIsa};
@@ -42,6 +42,9 @@ pub struct Function {
/// Heaps referenced. /// Heaps referenced.
pub heaps: PrimaryMap<ir::Heap, ir::HeapData>, pub heaps: PrimaryMap<ir::Heap, ir::HeapData>,
/// Tables referenced.
pub tables: PrimaryMap<ir::Table, ir::TableData>,
/// Jump tables used in this function. /// Jump tables used in this function.
pub jump_tables: JumpTables, pub jump_tables: JumpTables,
@@ -82,6 +85,7 @@ impl Function {
stack_limit: None, stack_limit: None,
global_values: PrimaryMap::new(), global_values: PrimaryMap::new(),
heaps: PrimaryMap::new(), heaps: PrimaryMap::new(),
tables: PrimaryMap::new(),
jump_tables: PrimaryMap::new(), jump_tables: PrimaryMap::new(),
dfg: DataFlowGraph::new(), dfg: DataFlowGraph::new(),
layout: Layout::new(), layout: Layout::new(),
@@ -98,6 +102,7 @@ impl Function {
self.stack_slots.clear(); self.stack_slots.clear();
self.global_values.clear(); self.global_values.clear();
self.heaps.clear(); self.heaps.clear();
self.tables.clear();
self.jump_tables.clear(); self.jump_tables.clear();
self.dfg.clear(); self.dfg.clear();
self.layout.clear(); self.layout.clear();
@@ -157,6 +162,11 @@ impl Function {
self.heaps.push(data) 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. /// Return an object that can display this function with correct ISA-specific annotations.
pub fn display<'a, I: Into<Option<&'a TargetIsa>>>(&'a self, isa: I) -> DisplayFunction<'a> { pub fn display<'a, I: Into<Option<&'a TargetIsa>>>(&'a self, isa: I) -> DisplayFunction<'a> {
DisplayFunction(self, isa.into()) DisplayFunction(self, isa.into())

View File

@@ -18,6 +18,7 @@ mod memflags;
mod progpoint; mod progpoint;
mod sourceloc; mod sourceloc;
pub mod stackslot; pub mod stackslot;
mod table;
mod trapcode; mod trapcode;
pub mod types; pub mod types;
mod valueloc; mod valueloc;
@@ -25,7 +26,7 @@ mod valueloc;
pub use ir::builder::{InsertBuilder, InstBuilder, InstBuilderBase, InstInserterBase}; pub use ir::builder::{InsertBuilder, InstBuilder, InstBuilderBase, InstInserterBase};
pub use ir::dfg::{DataFlowGraph, ValueDef}; pub use ir::dfg::{DataFlowGraph, ValueDef};
pub use ir::entities::{ 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::extfunc::{AbiParam, ArgumentExtension, ArgumentPurpose, ExtFuncData, Signature};
pub use ir::extname::ExternalName; pub use ir::extname::ExternalName;
@@ -40,6 +41,7 @@ pub use ir::memflags::MemFlags;
pub use ir::progpoint::{ExpandedProgramPoint, ProgramOrder, ProgramPoint}; pub use ir::progpoint::{ExpandedProgramPoint, ProgramOrder, ProgramPoint};
pub use ir::sourceloc::SourceLoc; pub use ir::sourceloc::SourceLoc;
pub use ir::stackslot::{StackSlotData, StackSlotKind, StackSlots}; pub use ir::stackslot::{StackSlotData, StackSlotKind, StackSlots};
pub use ir::table::TableData;
pub use ir::trapcode::TrapCode; pub use ir::trapcode::TrapCode;
pub use ir::types::Type; pub use ir::types::Type;
pub use ir::valueloc::{ArgumentLoc, ValueLoc}; pub use ir::valueloc::{ArgumentLoc, ValueLoc};

View File

@@ -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
)
}
}

View File

@@ -16,10 +16,13 @@ pub enum TrapCode {
/// A `heap_addr` instruction detected an out-of-bounds error. /// 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 /// Note that not all out-of-bounds heap accesses are reported this way;
/// pages. /// some are detected by a segmentation fault on the heap guard pages.
HeapOutOfBounds, HeapOutOfBounds,
/// A `table_addr` instruction detected an out-of-bounds error.
TableOutOfBounds,
/// Other bounds checking error. /// Other bounds checking error.
OutOfBounds, OutOfBounds,
@@ -52,6 +55,7 @@ impl Display for TrapCode {
let identifier = match *self { let identifier = match *self {
StackOverflow => "stk_ovf", StackOverflow => "stk_ovf",
HeapOutOfBounds => "heap_oob", HeapOutOfBounds => "heap_oob",
TableOutOfBounds => "table_oob",
OutOfBounds => "oob", OutOfBounds => "oob",
IndirectCallToNull => "icall_null", IndirectCallToNull => "icall_null",
BadSignature => "bad_sig", BadSignature => "bad_sig",
@@ -73,6 +77,7 @@ impl FromStr for TrapCode {
match s { match s {
"stk_ovf" => Ok(StackOverflow), "stk_ovf" => Ok(StackOverflow),
"heap_oob" => Ok(HeapOutOfBounds), "heap_oob" => Ok(HeapOutOfBounds),
"table_oob" => Ok(TableOutOfBounds),
"oob" => Ok(OutOfBounds), "oob" => Ok(OutOfBounds),
"icall_null" => Ok(IndirectCallToNull), "icall_null" => Ok(IndirectCallToNull),
"bad_sig" => Ok(BadSignature), "bad_sig" => Ok(BadSignature),
@@ -92,9 +97,10 @@ mod tests {
use std::string::ToString; use std::string::ToString;
// Everything but user-defined codes. // Everything but user-defined codes.
const CODES: [TrapCode; 8] = [ const CODES: [TrapCode; 9] = [
TrapCode::StackOverflow, TrapCode::StackOverflow,
TrapCode::HeapOutOfBounds, TrapCode::HeapOutOfBounds,
TrapCode::TableOutOfBounds,
TrapCode::OutOfBounds, TrapCode::OutOfBounds,
TrapCode::IndirectCallToNull, TrapCode::IndirectCallToNull,
TrapCode::BadSignature, TrapCode::BadSignature,

View File

@@ -82,7 +82,7 @@ fn dynamic_addr(
} }
pos.ins().trapnz(oob, ir::TrapCode::HeapOutOfBounds); 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. /// Expand a `heap_addr` for a static heap.
@@ -134,13 +134,11 @@ fn static_addr(
pos.ins().trapnz(oob, ir::TrapCode::HeapOutOfBounds); 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. /// Emit code for the base address computation of a `heap_addr` instruction.
/// fn compute_addr(
///
fn offset_addr(
inst: ir::Inst, inst: ir::Inst,
heap: ir::Heap, heap: ir::Heap,
addr_ty: ir::Type, addr_ty: ir::Type,

View File

@@ -26,11 +26,13 @@ mod globalvalue;
mod heap; mod heap;
mod libcall; mod libcall;
mod split; mod split;
mod table;
use self::call::expand_call; use self::call::expand_call;
use self::globalvalue::expand_global_value; use self::globalvalue::expand_global_value;
use self::heap::expand_heap_addr; use self::heap::expand_heap_addr;
use self::libcall::expand_as_libcall; 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 /// Legalize `inst` for `isa`. Return true if any changes to the code were
/// made; return false if the instruction was successfully encoded as is. /// made; return false if the instruction was successfully encoded as is.

View File

@@ -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);
}
}

View File

@@ -349,6 +349,9 @@ impl<'a> Verifier<'a> {
HeapAddr { heap, .. } => { HeapAddr { heap, .. } => {
self.verify_heap(inst, heap)?; self.verify_heap(inst, heap)?;
} }
TableAddr { table, .. } => {
self.verify_table(inst, table)?;
}
RegSpill { dst, .. } => { RegSpill { dst, .. } => {
self.verify_stack_slot(inst, 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<()> { fn verify_value_list(&self, inst: Inst, l: &ValueList) -> VerifierResult<()> {
if !l.is_valid(&self.func.dfg.value_lists) { if !l.is_valid(&self.func.dfg.value_lists) {
err!(inst, "invalid value list reference {:?}", l) err!(inst, "invalid value list reference {:?}", l)

View File

@@ -440,6 +440,7 @@ pub fn write_operands(
.. ..
} => write!(w, " {}, {}{}", arg, stack_slot, offset), } => write!(w, " {}, {}{}", arg, stack_slot, offset),
HeapAddr { heap, arg, imm, .. } => write!(w, " {}, {}, {}", heap, arg, imm), HeapAddr { heap, arg, imm, .. } => write!(w, " {}, {}, {}", heap, arg, imm),
TableAddr { table, arg, .. } => write!(w, " {}, {}", table, arg),
Load { Load {
flags, arg, offset, .. flags, arg, offset, ..
} => write!(w, "{} {}{}", flags, arg, offset), } => write!(w, "{} {}{}", flags, arg, offset),

View File

@@ -36,6 +36,7 @@ pub enum Token<'a> {
StackSlot(u32), // ss3 StackSlot(u32), // ss3
GlobalValue(u32), // gv3 GlobalValue(u32), // gv3
Heap(u32), // heap2 Heap(u32), // heap2
Table(u32), // table2
JumpTable(u32), // jt2 JumpTable(u32), // jt2
FuncRef(u32), // fn2 FuncRef(u32), // fn2
SigRef(u32), // sig2 SigRef(u32), // sig2
@@ -340,6 +341,7 @@ impl<'a> Lexer<'a> {
"ss" => Some(Token::StackSlot(number)), "ss" => Some(Token::StackSlot(number)),
"gv" => Some(Token::GlobalValue(number)), "gv" => Some(Token::GlobalValue(number)),
"heap" => Some(Token::Heap(number)), "heap" => Some(Token::Heap(number)),
"table" => Some(Token::Table(number)),
"jt" => Some(Token::JumpTable(number)), "jt" => Some(Token::JumpTable(number)),
"fn" => Some(Token::FuncRef(number)), "fn" => Some(Token::FuncRef(number)),
"sig" => Some(Token::SigRef(number)), "sig" => Some(Token::SigRef(number)),

View File

@@ -9,7 +9,8 @@ use cranelift_codegen::ir::types::VOID;
use cranelift_codegen::ir::{ use cranelift_codegen::ir::{
AbiParam, ArgumentExtension, ArgumentLoc, Ebb, ExtFuncData, ExternalName, FuncRef, Function, AbiParam, ArgumentExtension, ArgumentLoc, Ebb, ExtFuncData, ExternalName, FuncRef, Function,
GlobalValue, GlobalValueData, Heap, HeapData, HeapStyle, JumpTable, JumpTableData, MemFlags, 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::isa::{self, Encoding, RegUnit, TargetIsa};
use cranelift_codegen::packed_option::ReservedValue; 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. // Allocate a new signature.
fn add_sig(&mut self, sig: SigRef, data: Signature, loc: Location) -> ParseResult<()> { fn add_sig(&mut self, sig: SigRef, data: Signature, loc: Location) -> ParseResult<()> {
self.map.def_sig(sig, loc)?; self.map.def_sig(sig, loc)?;
@@ -447,6 +471,17 @@ impl<'a> Parser<'a> {
err!(self.loc, err_msg) err!(self.loc, err_msg)
} }
// Match and consume a table reference.
fn match_table(&mut self, err_msg: &str) -> ParseResult<Table> {
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. // Match and consume a jump table reference.
fn match_jt(&mut self) -> ParseResult<JumpTable> { fn match_jt(&mut self) -> ParseResult<JumpTable> {
if let Some(Token::JumpTable(jt)) = self.token() { if let Some(Token::JumpTable(jt)) = self.token() {
@@ -1019,6 +1054,11 @@ impl<'a> Parser<'a> {
self.parse_heap_decl() self.parse_heap_decl()
.and_then(|(heap, dat)| ctx.add_heap(heap, dat, self.loc)) .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(..)) => { Some(Token::SigRef(..)) => {
self.start_gathering_comments(); self.start_gathering_comments();
self.parse_signature_decl(ctx.unique_isa) self.parse_signature_decl(ctx.unique_isa)
@@ -1129,7 +1169,7 @@ impl<'a> Parser<'a> {
// heap-style ::= "static" | "dynamic" // heap-style ::= "static" | "dynamic"
// heap-base ::= GlobalValue(base) // heap-base ::= GlobalValue(base)
// heap-attr ::= "min" Imm64(bytes) // heap-attr ::= "min" Imm64(bytes)
// | "max" Imm64(bytes) // | "bound" Imm64(bytes)
// | "guard" Imm64(bytes) // | "guard" Imm64(bytes)
// //
fn parse_heap_decl(&mut self) -> ParseResult<(Heap, HeapData)> { fn parse_heap_decl(&mut self) -> ParseResult<(Heap, HeapData)> {
@@ -1187,6 +1227,66 @@ impl<'a> Parser<'a> {
Ok((heap, data)) 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. // Parse a signature decl.
// //
// signature-decl ::= SigRef(sigref) "=" signature // signature-decl ::= SigRef(sigref) "=" signature
@@ -2155,6 +2255,20 @@ impl<'a> Parser<'a> {
imm, 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 => { InstructionFormat::Load => {
let flags = self.optional_memflags(); let flags = self.optional_memflags();
let addr = self.match_value("expected SSA value address")?; let addr = self.match_value("expected SSA value address")?;

View File

@@ -7,7 +7,9 @@
//! to parser clients. //! to parser clients.
use cranelift_codegen::ir::entities::AnyEntity; 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 error::{Location, ParseResult};
use lexer::split_entity_name; use lexer::split_entity_name;
use std::collections::HashMap; use std::collections::HashMap;
@@ -46,6 +48,11 @@ impl SourceMap {
self.locations.contains_key(&heap.into()) 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. /// Look up a signature entity.
pub fn contains_sig(&self, sig: SigRef) -> bool { pub fn contains_sig(&self, sig: SigRef) -> bool {
self.locations.contains_key(&sig.into()) self.locations.contains_key(&sig.into())
@@ -100,6 +107,13 @@ impl SourceMap {
Some(heap.into()) 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| { "sig" => SigRef::with_number(num).and_then(|sig| {
if !self.contains_sig(sig) { if !self.contains_sig(sig) {
None None
@@ -164,6 +178,11 @@ impl SourceMap {
self.def_entity(entity.into(), loc) 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`. /// Define the signature `entity`.
pub fn def_sig(&mut self, entity: SigRef, loc: Location) -> ParseResult<()> { pub fn def_sig(&mut self, entity: SigRef, loc: Location) -> ParseResult<()> {
self.def_entity(entity.into(), loc) self.def_entity(entity.into(), loc)

View File

@@ -176,6 +176,12 @@ pub enum SerInstData {
heap: String, heap: String,
imm: String, imm: String,
}, },
TableAddr {
opcode: String,
arg: String,
table: String,
offset: String,
},
RegMove { RegMove {
opcode: String, opcode: String,
arg: String, arg: String,
@@ -572,6 +578,17 @@ pub fn get_inst_data(inst_index: Inst, func: &Function) -> SerInstData {
heap: heap.to_string(), heap: heap.to_string(),
imm: imm.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 { InstructionData::RegMove {
opcode, opcode,
arg, arg,

View File

@@ -377,10 +377,12 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
// `index` is the index of the function's signature and `table_index` is the index of // `index` is the index of the function's signature and `table_index` is the index of
// the table to search the function in. // the table to search the function in.
let (sigref, num_args) = state.get_indirect_sig(builder.func, index, environ); 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 callee = state.pop1();
let call = environ.translate_call_indirect( let call = environ.translate_call_indirect(
builder.cursor(), builder.cursor(),
table_index as TableIndex, table_index as TableIndex,
table,
index as SignatureIndex, index as SignatureIndex,
sigref, sigref,
callee, callee,

View File

@@ -1,6 +1,7 @@
//! "Dummy" environment for testing wasm translation. //! "Dummy" environment for testing wasm translation.
use cranelift_codegen::cursor::FuncCursor; use cranelift_codegen::cursor::FuncCursor;
use cranelift_codegen::ir::immediates::Imm64;
use cranelift_codegen::ir::types::*; use cranelift_codegen::ir::types::*;
use cranelift_codegen::ir::{self, InstBuilder}; use cranelift_codegen::ir::{self, InstBuilder};
use cranelift_codegen::settings; 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 { 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`. // 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 { func.create_heap(ir::HeapData {
base: gv, 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 { 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 // A real implementation would probably change the calling convention and add `vmctx` and
// signature index arguments. // signature index arguments.
@@ -204,6 +232,7 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
&mut self, &mut self,
mut pos: FuncCursor, mut pos: FuncCursor,
_table_index: TableIndex, _table_index: TableIndex,
_table: ir::Table,
_sig_index: SignatureIndex, _sig_index: SignatureIndex,
sig_ref: ir::SigRef, sig_ref: ir::SigRef,
callee: ir::Value, callee: ir::Value,

View File

@@ -89,6 +89,11 @@ pub trait FuncEnvironment {
ir::Type::int(u16::from(self.triple().pointer_width().unwrap().bits())).unwrap() 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 /// Set up the necessary preamble definitions in `func` to access the global variable
/// identified by `index`. /// identified by `index`.
/// ///
@@ -104,6 +109,12 @@ pub trait FuncEnvironment {
/// The index space covers both imported and locally declared memories. /// The index space covers both imported and locally declared memories.
fn make_heap(&mut self, func: &mut ir::Function, index: MemoryIndex) -> ir::Heap; 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 /// Set up a signature definition in the preamble of `func` that can be used for an indirect
/// call with signature `index`. /// call with signature `index`.
/// ///
@@ -141,6 +152,7 @@ pub trait FuncEnvironment {
&mut self, &mut self,
pos: FuncCursor, pos: FuncCursor,
table_index: TableIndex, table_index: TableIndex,
table: ir::Table,
sig_index: SignatureIndex, sig_index: SignatureIndex,
sig_ref: ir::SigRef, sig_ref: ir::SigRef,
callee: ir::Value, callee: ir::Value,

View File

@@ -7,7 +7,7 @@ use cranelift_codegen::ir::{self, Ebb, Inst, Value};
use environ::{FuncEnvironment, GlobalVariable}; use environ::{FuncEnvironment, GlobalVariable};
use std::collections::HashMap; use std::collections::HashMap;
use std::vec::Vec; 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 /// A control stack frame can be an `if`, a `block` or a `loop`, each one having the following
/// fields: /// fields:
@@ -140,6 +140,9 @@ pub struct TranslationState {
// Map of heaps that have been created by `FuncEnvironment::make_heap`. // Map of heaps that have been created by `FuncEnvironment::make_heap`.
heaps: HashMap<MemoryIndex, ir::Heap>, heaps: HashMap<MemoryIndex, ir::Heap>,
// Map of tables that have been created by `FuncEnvironment::make_table`.
tables: HashMap<TableIndex, ir::Table>,
// Map of indirect call signatures that have been created by // Map of indirect call signatures that have been created by
// `FuncEnvironment::make_indirect_sig()`. // `FuncEnvironment::make_indirect_sig()`.
// Stores both the signature reference and the number of WebAssembly arguments // Stores both the signature reference and the number of WebAssembly arguments
@@ -159,6 +162,7 @@ impl TranslationState {
reachable: true, reachable: true,
globals: HashMap::new(), globals: HashMap::new(),
heaps: HashMap::new(), heaps: HashMap::new(),
tables: HashMap::new(),
signatures: HashMap::new(), signatures: HashMap::new(),
functions: HashMap::new(), functions: HashMap::new(),
} }
@@ -303,6 +307,21 @@ impl TranslationState {
.or_insert_with(|| environ.make_heap(func, index)) .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<FE: FuncEnvironment + ?Sized>(
&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 /// 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. /// `index`. Also return the number of WebAssembly arguments in the signature.
/// ///