wasmtime_cranelift/translate/
table.rs

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