cranelift_wasm/
table.rs

1use cranelift_codegen::cursor::FuncCursor;
2use cranelift_codegen::ir::{self, condcodes::IntCC, immediates::Imm64, InstBuilder};
3use cranelift_frontend::FunctionBuilder;
4
5/// Size of a WebAssembly table, in elements.
6#[derive(Clone)]
7pub enum TableSize {
8    /// Non-resizable table.
9    Static {
10        /// Non-resizable tables have a constant size known at compile time.
11        bound: u32,
12    },
13    /// Resizable table.
14    Dynamic {
15        /// Resizable tables declare a Cranelift global value to load the
16        /// current size from.
17        bound_gv: ir::GlobalValue,
18    },
19}
20
21impl TableSize {
22    /// Get a CLIF value representing the current bounds of this table.
23    pub fn bound(&self, mut pos: FuncCursor, index_ty: ir::Type) -> ir::Value {
24        match *self {
25            TableSize::Static { bound } => pos.ins().iconst(index_ty, Imm64::new(i64::from(bound))),
26            TableSize::Dynamic { bound_gv } => pos.ins().global_value(index_ty, bound_gv),
27        }
28    }
29}
30
31/// An implementation of a WebAssembly table.
32#[derive(Clone)]
33pub struct TableData {
34    /// Global value giving the address of the start of the table.
35    pub base_gv: ir::GlobalValue,
36
37    /// The size of the table, in elements.
38    pub bound: TableSize,
39
40    /// The size of a table element, in bytes.
41    pub element_size: u32,
42}
43
44impl TableData {
45    /// Return a CLIF value containing a native pointer to the beginning of the
46    /// given index within this table.
47    pub fn prepare_table_addr(
48        &self,
49        pos: &mut FunctionBuilder,
50        mut index: ir::Value,
51        addr_ty: ir::Type,
52        enable_table_access_spectre_mitigation: bool,
53    ) -> (ir::Value, ir::MemFlags) {
54        let index_ty = pos.func.dfg.value_type(index);
55
56        // Start with the bounds check. Trap if `index + 1 > bound`.
57        let bound = self.bound.bound(pos.cursor(), index_ty);
58
59        // `index > bound - 1` is the same as `index >= bound`.
60        let oob = pos
61            .ins()
62            .icmp(IntCC::UnsignedGreaterThanOrEqual, index, bound);
63
64        if !enable_table_access_spectre_mitigation {
65            pos.ins().trapnz(oob, ir::TrapCode::TableOutOfBounds);
66        }
67
68        // Convert `index` to `addr_ty`.
69        if index_ty != addr_ty {
70            index = pos.ins().uextend(addr_ty, index);
71        }
72
73        // Add the table base address base
74        let base = pos.ins().global_value(addr_ty, self.base_gv);
75
76        let element_size = self.element_size;
77        let offset = if element_size == 1 {
78            index
79        } else if element_size.is_power_of_two() {
80            pos.ins()
81                .ishl_imm(index, i64::from(element_size.trailing_zeros()))
82        } else {
83            pos.ins().imul_imm(index, element_size as i64)
84        };
85
86        let element_addr = pos.ins().iadd(base, offset);
87
88        let base_flags = ir::MemFlags::new()
89            .with_aligned()
90            .with_alias_region(Some(ir::AliasRegion::Table));
91        if enable_table_access_spectre_mitigation {
92            // Short-circuit the computed table element address to a null pointer
93            // when out-of-bounds. The consumer of this address will trap when
94            // trying to access it.
95            let zero = pos.ins().iconst(addr_ty, 0);
96            (
97                pos.ins().select_spectre_guard(oob, zero, element_addr),
98                base_flags.with_trap_code(Some(ir::TrapCode::TableOutOfBounds)),
99            )
100        } else {
101            (element_addr, base_flags.with_trap_code(None))
102        }
103    }
104}